diff --git a/Luna b/Luna
index 2e984d9c..cb294f47 160000
--- a/Luna
+++ b/Luna
@@ -1 +1 @@
-Subproject commit 2e984d9c21370c778d172ab955def18c0dbe8c7d
+Subproject commit cb294f476476f7a3d8b56a0072dbd300b3d54c4f
diff --git a/Penumbra/Communication/SelectTab.cs b/Penumbra/Communication/SelectTab.cs
index 144b362a..8030badd 100644
--- a/Penumbra/Communication/SelectTab.cs
+++ b/Penumbra/Communication/SelectTab.cs
@@ -10,7 +10,7 @@ public sealed class SelectTab(Logger log) : EventBase
- ConfigTabBar = 0,
+ MainTabBar = 0,
}
/// The arguments for a SelectTab event.
diff --git a/Penumbra/EphemeralConfig.cs b/Penumbra/EphemeralConfig.cs
index caf34027..dba07d29 100644
--- a/Penumbra/EphemeralConfig.cs
+++ b/Penumbra/EphemeralConfig.cs
@@ -10,6 +10,7 @@ using Penumbra.UI;
using Penumbra.UI.ResourceWatcher;
using Penumbra.UI.Tabs;
using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs;
+using TabType = Penumbra.Api.Enums.TabType;
namespace Penumbra;
diff --git a/Penumbra/Penumbra.cs b/Penumbra/Penumbra.cs
index b169a9a4..ee9c0d75 100644
--- a/Penumbra/Penumbra.cs
+++ b/Penumbra/Penumbra.cs
@@ -134,7 +134,7 @@ public class Penumbra : IDalamudPlugin
AsyncTask.Run(() =>
{
var system = _services.GetService();
- system.Window.Setup(this, _services.GetService());
+ system.Window.Setup(this, _services.GetService());
_services.GetService();
if (!_disposed)
{
@@ -199,8 +199,12 @@ public class Penumbra : IDalamudPlugin
{
ReadOnlySpan relevantPlugins =
[
- "Glamourer", "MareSynchronos", "CustomizePlus", "SimpleHeels", "VfxEditor", "heliosphere-plugin", "Ktisis", "Brio", "DynamicBridge",
- "IllusioVitae", "Aetherment", "LoporritSync", "GagSpeak", "ProjectGagSpeak", "RoleplayingVoiceDalamud", "AQuestReborn",
+ "Glamourer", "CustomizePlus", "SimpleHeels",
+ "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().InstalledPlugins
.GroupBy(p => p.InternalName)
diff --git a/Penumbra/Services/ConfigMigrationService.cs b/Penumbra/Services/ConfigMigrationService.cs
index 8369e1d6..36688027 100644
--- a/Penumbra/Services/ConfigMigrationService.cs
+++ b/Penumbra/Services/ConfigMigrationService.cs
@@ -14,6 +14,7 @@ using Penumbra.UI;
using Penumbra.UI.Classes;
using Penumbra.UI.ResourceWatcher;
using Penumbra.UI.Tabs;
+using TabType = Penumbra.Api.Enums.TabType;
namespace Penumbra.Services;
diff --git a/Penumbra/UI/AdvancedWindow/ItemSwapTab.cs b/Penumbra/UI/AdvancedWindow/ItemSwapTab.cs
index 57e72dd1..70a88c00 100644
--- a/Penumbra/UI/AdvancedWindow/ItemSwapTab.cs
+++ b/Penumbra/UI/AdvancedWindow/ItemSwapTab.cs
@@ -4,7 +4,6 @@ using ImSharp;
using Luna;
using OtterGui;
using OtterGui.Raii;
-using OtterGui.Text;
using OtterGui.Widgets;
using Penumbra.Api.Enums;
using Penumbra.Collections.Manager;
@@ -25,6 +24,7 @@ using Penumbra.Mods.SubMods;
using Penumbra.Services;
using Penumbra.UI.Classes;
using Penumbra.UI.ModsTab;
+using ITab = OtterGui.Widgets.ITab;
using MouseWheelType = OtterGui.Widgets.MouseWheelType;
namespace Penumbra.UI.AdvancedWindow;
diff --git a/Penumbra/UI/ConfigWindow.cs b/Penumbra/UI/ConfigWindow.cs
index 9f2c458a..f054a09b 100644
--- a/Penumbra/UI/ConfigWindow.cs
+++ b/Penumbra/UI/ConfigWindow.cs
@@ -1,10 +1,10 @@
using Dalamud.Plugin;
using ImSharp;
using Luna;
-using Penumbra.Api.Enums;
using Penumbra.Services;
using Penumbra.UI.Classes;
using Penumbra.UI.Tabs;
+using TabType = Penumbra.Api.Enums.TabType;
namespace Penumbra.UI;
@@ -14,7 +14,7 @@ public sealed class ConfigWindow : Window
private readonly Configuration _config;
private readonly ValidityChecker _validityChecker;
private Penumbra? _penumbra;
- private ConfigTabBar _configTabs = null!;
+ private MainTabBar _configTabs = null!;
private string? _lastException;
public ConfigWindow(IDalamudPluginInterface pi, Configuration config, ValidityChecker checker,
@@ -32,15 +32,15 @@ public sealed class ConfigWindow : Window
public void OpenSettings()
{
- _configTabs.SelectTab = TabType.Settings;
- IsOpen = true;
+ _configTabs.NextTab = TabType.Settings;
+ IsOpen = true;
}
- public void Setup(Penumbra penumbra, ConfigTabBar configTabs)
+ public void Setup(Penumbra penumbra, MainTabBar configTabs)
{
- _penumbra = penumbra;
- _configTabs = configTabs;
- _configTabs.SelectTab = _config.Ephemeral.SelectedTab;
+ _penumbra = penumbra;
+ _configTabs = configTabs;
+ _configTabs.NextTab = _config.Ephemeral.SelectedTab;
}
public override bool DrawConditions()
@@ -98,12 +98,7 @@ public sealed class ConfigWindow : Window
}
else
{
- var type = _configTabs.Draw();
- if (type != _config.Ephemeral.SelectedTab)
- {
- _config.Ephemeral.SelectedTab = type;
- _config.Ephemeral.Save();
- }
+ _configTabs.Draw();
}
_lastException = null;
diff --git a/Penumbra/UI/IncognitoService.cs b/Penumbra/UI/IncognitoService.cs
index 411a5145..9d930790 100644
--- a/Penumbra/UI/IncognitoService.cs
+++ b/Penumbra/UI/IncognitoService.cs
@@ -15,18 +15,18 @@ public class IncognitoService(TutorialService tutorial, Configuration config) :
var color = ColorId.FolderExpanded.Value();
using (ImStyleBorder.Frame.Push(color))
{
- var tt = IncognitoMode ? "Toggle incognito mode off."u8 : "Toggle incognito mode on."u8;
- var icon = IncognitoMode ? LunaStyle.IncognitoOn : LunaStyle.IncognitoOff;
+ var tt = IncognitoMode ? "Toggle incognito mode off."u8 : "Toggle incognito mode on."u8;
+ var icon = IncognitoMode ? LunaStyle.IncognitoOn : LunaStyle.IncognitoOff;
if (ImEx.Icon.Button(icon, tt, size: new Vector2(width, Im.Style.FrameHeight), textColor: color) && hold)
{
config.Ephemeral.IncognitoMode = !IncognitoMode;
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);
}
}
diff --git a/Penumbra/UI/ModsTab/ModPanelChangedItemsTab.cs b/Penumbra/UI/ModsTab/ModPanelChangedItemsTab.cs
index 55582130..d4e7a79e 100644
--- a/Penumbra/UI/ModsTab/ModPanelChangedItemsTab.cs
+++ b/Penumbra/UI/ModsTab/ModPanelChangedItemsTab.cs
@@ -6,7 +6,6 @@ using Luna;
using OtterGui;
using OtterGui.Services;
using OtterGui.Text;
-using OtterGui.Widgets;
using Penumbra.GameData.Data;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
@@ -23,7 +22,7 @@ public class ModPanelChangedItemsTab(
ImGuiCacheService cacheService,
Configuration config,
ModDataEditor dataEditor)
- : ITab, Luna.IUiService
+ : ITab
{
private readonly ImGuiCacheService.CacheId _cacheId = cacheService.GetNewId();
@@ -209,6 +208,9 @@ public class ModPanelChangedItemsTab(
public ReadOnlySpan Label
=> "Changed Items"u8;
+ public ModPanelTab Identifier
+ => ModPanelTab.ChangedItems;
+
public bool IsVisible
=> selector.Selected!.ChangedItems.Count > 0;
diff --git a/Penumbra/UI/ModsTab/ModPanelCollectionsTab.cs b/Penumbra/UI/ModsTab/ModPanelCollectionsTab.cs
index 81615c32..52992deb 100644
--- a/Penumbra/UI/ModsTab/ModPanelCollectionsTab.cs
+++ b/Penumbra/UI/ModsTab/ModPanelCollectionsTab.cs
@@ -1,9 +1,6 @@
using Dalamud.Bindings.ImGui;
-using Dalamud.Interface.Utility;
using ImSharp;
-using OtterGui.Raii;
-using OtterGui.Text;
-using OtterGui.Widgets;
+using Luna;
using Penumbra.Collections;
using Penumbra.Collections.Manager;
using Penumbra.Mods;
@@ -11,7 +8,7 @@ using Penumbra.UI.Classes;
namespace Penumbra.UI.ModsTab;
-public class ModPanelCollectionsTab(CollectionManager manager, ModFileSystemSelector selector) : ITab, Luna.IUiService
+public class ModPanelCollectionsTab(CollectionManager manager, ModFileSystemSelector selector) : ITab
{
private enum ModState
{
@@ -24,19 +21,22 @@ public class ModPanelCollectionsTab(CollectionManager manager, ModFileSystemSele
public ReadOnlySpan Label
=> "Collections"u8;
+
+ public ModPanelTab Identifier
+ => ModPanelTab.Collections;
public void DrawContent()
{
var (direct, inherited) = CountUsage(selector.Selected!);
Im.Line.New();
- if (direct == 1)
- ImUtf8.Text("This Mod is directly configured in 1 collection."u8);
- else if (direct == 0)
- ImUtf8.Text("This mod is entirely unused."u8, Colors.RegexWarningBorder);
- else
- ImUtf8.Text($"This Mod is directly configured in {direct} collections.");
+ switch (direct)
+ {
+ case 1: Im.Text("This Mod is directly configured in 1 collection."u8); break;
+ case 0: Im.Text("This mod is entirely unused."u8, Colors.RegexWarningBorder); break;
+ default: Im.Text($"This Mod is directly configured in {direct} collections."); break;
+ }
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.Separator();
@@ -45,7 +45,7 @@ public class ModPanelCollectionsTab(CollectionManager manager, ModFileSystemSele
if (!table)
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;
table.SetupColumn("Collection"u8, TableColumnFlags.WidthFixed, collectionSize);
table.SetupColumn("State"u8, TableColumnFlags.WidthFixed, size);
@@ -54,21 +54,21 @@ public class ModPanelCollectionsTab(CollectionManager manager, ModFileSystemSele
ImGui.TableHeadersRow();
foreach (var (idx, (collection, parent, color, state)) in _cache.Index())
{
- using var id = ImUtf8.PushId(idx);
- ImUtf8.DrawTableColumn(collection.Identity.Name);
+ using var id = Im.Id.Push(idx);
+ table.DrawColumn(collection.Identity.Name);
- ImGui.TableNextColumn();
- ImUtf8.Text(ToText(state), color);
+ table.NextColumn();
+ Im.Text(ToText(state), color);
- using (var context = ImUtf8.PopupContextItem("Context"u8))
+ using (var context = Im.Popup.BeginContextItem("Context"u8))
{
if (context)
{
- ImUtf8.Text(collection.Identity.Name);
+ Im.Text(collection.Identity.Name);
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)
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)
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);
}
}
}
- ImUtf8.DrawTableColumn(parent == collection ? string.Empty : parent.Identity.Name);
+ table.DrawColumn(parent == collection ? StringU8.Empty : parent.Identity.Name);
}
}
diff --git a/Penumbra/UI/ModsTab/ModPanelConflictsTab.cs b/Penumbra/UI/ModsTab/ModPanelConflictsTab.cs
index 68e6912d..d8a6fc52 100644
--- a/Penumbra/UI/ModsTab/ModPanelConflictsTab.cs
+++ b/Penumbra/UI/ModsTab/ModPanelConflictsTab.cs
@@ -13,11 +13,14 @@ using Penumbra.UI.Classes;
namespace Penumbra.UI.ModsTab;
-public class ModPanelConflictsTab(CollectionManager collectionManager, ModFileSystemSelector selector) : ITab, IUiService
+public class ModPanelConflictsTab(CollectionManager collectionManager, ModFileSystemSelector selector) : ITab
{
public ReadOnlySpan Label
=> "Conflicts"u8;
+ public ModPanelTab Identifier
+ => ModPanelTab.Conflicts;
+
public bool IsVisible
=> collectionManager.Active.Current.Conflicts(selector.Selected!).Any(c => !GetPriority(c).IsHidden);
diff --git a/Penumbra/UI/ModsTab/ModPanelDescriptionTab.cs b/Penumbra/UI/ModsTab/ModPanelDescriptionTab.cs
index d956b004..a0fe226f 100644
--- a/Penumbra/UI/ModsTab/ModPanelDescriptionTab.cs
+++ b/Penumbra/UI/ModsTab/ModPanelDescriptionTab.cs
@@ -1,7 +1,6 @@
using Dalamud.Bindings.ImGui;
using ImSharp;
-using OtterGui.Raii;
-using OtterGui;
+using Luna;
using OtterGui.Widgets;
using Penumbra.Mods.Manager;
@@ -12,23 +11,25 @@ public class ModPanelDescriptionTab(
TutorialService tutorial,
ModManager modManager,
PredefinedTagManager predefinedTagsConfig)
- : ITab, Luna.IUiService
+ : ITab
{
private readonly TagButtons _localTags = new();
private readonly TagButtons _modTags = new();
public ReadOnlySpan Label
=> "Description"u8;
+
+ public ModPanelTab Identifier
+ => ModPanelTab.Description;
public void DrawContent()
{
- using var child = ImRaii.Child("##description");
+ using var child = Im.Child.Begin("##description"u8);
if (!child)
return;
- ImGui.Dummy(ImEx.ScaledVector(2));
-
- ImGui.Dummy(ImEx.ScaledVector(2));
+ Im.ScaledDummy(2, 2);
+ Im.ScaledDummy(2, 2);
var (predefinedTagsEnabled, predefinedTagButtonOffset) = predefinedTagsConfig.Enabled
? (true, Im.Style.FrameHeight + Im.Style.WindowPadding.X + (ImGui.GetScrollMaxY() > 0 ? Im.Style.ScrollbarSize : 0))
: (false, 0);
@@ -49,9 +50,9 @@ public class ModPanelDescriptionTab(
selector.Selected!.ModTags, out _, false,
ImGui.CalcTextSize("Local ").X - ImGui.CalcTextSize("Mod ").X);
- ImGui.Dummy(ImEx.ScaledVector(2));
+ Im.ScaledDummy(2, 2);
Im.Separator();
- ImGuiUtil.TextWrapped(selector.Selected!.Description);
+ Im.TextWrapped(selector.Selected!.Description);
}
}
diff --git a/Penumbra/UI/ModsTab/ModPanelEditTab.cs b/Penumbra/UI/ModsTab/ModPanelEditTab.cs
index 19d62b8d..42b9fee2 100644
--- a/Penumbra/UI/ModsTab/ModPanelEditTab.cs
+++ b/Penumbra/UI/ModsTab/ModPanelEditTab.cs
@@ -1,370 +1,335 @@
-using Dalamud.Interface;
-using Dalamud.Interface.Components;
-using Dalamud.Interface.ImGuiNotification;
-using Dalamud.Bindings.ImGui;
-using ImSharp;
-using Luna;
-using OtterGui;
-using OtterGui.Raii;
-using OtterGui.Widgets;
-using OtterGui.Text;
-using Penumbra.Mods;
-using Penumbra.Mods.Editor;
-using Penumbra.Mods.Manager;
-using Penumbra.Services;
-using Penumbra.Mods.Settings;
-using Penumbra.UI.ModsTab.Groups;
-
-namespace Penumbra.UI.ModsTab;
-
-public class ModPanelEditTab(
- ModManager modManager,
- ModFileSystemSelector selector,
- ModFileSystem fileSystem,
- Services.MessageService messager,
- FilenameService filenames,
- ModExportManager modExportManager,
- Configuration config,
- PredefinedTagManager predefinedTagManager,
- ModGroupEditDrawer groupEditDrawer,
- DescriptionEditPopup descriptionPopup,
- AddGroupDrawer addGroupDrawer)
- : ITab, IUiService
-{
- private readonly TagButtons _modTags = new();
-
- private ModFileSystem.Leaf _leaf = null!;
- private Mod _mod = null!;
-
- public ReadOnlySpan Label
- => "Edit Mod"u8;
-
- public void DrawContent()
- {
- using var child = ImRaii.Child("##editChild", -Vector2.One);
- if (!child)
- return;
-
- _leaf = selector.SelectedLeaf!;
- _mod = selector.Selected!;
-
- EditButtons();
- EditRegularMeta();
- UiHelpers.DefaultLineSpace();
- EditLocalData();
- UiHelpers.DefaultLineSpace();
-
- if (Input.Text("Mod Path", Input.Path, Input.None, _leaf.FullName(), out var newPath, 256, UiHelpers.InputTextWidth.X))
- try
- {
- fileSystem.RenameAndMove(_leaf, newPath);
- }
- catch (Exception e)
- {
- messager.NotificationMessage(e.Message, NotificationType.Warning, false);
- }
-
- UiHelpers.DefaultLineSpace();
-
- FeatureChecker.DrawFeatureFlagInput(modManager.DataEditor, _mod, UiHelpers.InputTextWidth.X);
-
- UiHelpers.DefaultLineSpace();
- var sharedTagsEnabled = predefinedTagManager.Enabled;
- var sharedTagButtonOffset = sharedTagsEnabled ? Im.Style.FrameHeight + Im.Style.FramePadding.X : 0;
- var tagIdx = _modTags.Draw("Mod Tags: ", "Edit tags by clicking them, or add new tags. Empty tags are removed.", _mod.ModTags,
- out var editedTag, rightEndOffset: sharedTagButtonOffset);
- if (tagIdx >= 0)
- modManager.DataEditor.ChangeModTag(_mod, tagIdx, editedTag);
-
- if (sharedTagsEnabled)
- predefinedTagManager.DrawAddFromSharedTagsAndUpdateTags(selector.Selected!.LocalTags, selector.Selected!.ModTags, false,
- selector.Selected!);
-
-
- UiHelpers.DefaultLineSpace();
- addGroupDrawer.Draw(_mod, UiHelpers.InputTextWidth.X);
- UiHelpers.DefaultLineSpace();
-
- groupEditDrawer.Draw(_mod);
- descriptionPopup.Draw();
- }
-
- public void Reset()
- {
- MoveDirectory.Reset();
- Input.Reset();
- }
-
- /// The general edit row for non-detailed mod edits.
- private void EditButtons()
- {
- var buttonSize = new Vector2(150 * Im.Style.GlobalScale, 0);
- var folderExists = Directory.Exists(_mod.ModPath.FullName);
- var tt = folderExists
- ? $"Open \"{_mod.ModPath.FullName}\" in the file explorer of your choice."
- : $"Mod directory \"{_mod.ModPath.FullName}\" does not exist.";
- if (ImGuiUtil.DrawDisabledButton("Open Mod Directory", buttonSize, tt, !folderExists))
- Process.Start(new ProcessStartInfo(_mod.ModPath.FullName) { UseShellExecute = true });
-
- Im.Line.Same();
- 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.",
- false))
- modManager.ReloadMod(_mod);
-
- BackupButtons(buttonSize);
- MoveDirectory.Draw(modManager, _mod, buttonSize);
-
- UiHelpers.DefaultLineSpace();
- }
-
- private void BackupButtons(Vector2 buttonSize)
- {
- var backup = new ModBackup(modExportManager, _mod);
- var tt = ModBackup.CreatingBackup
- ? "Already exporting a mod."
- : backup.Exists
- ? $"Overwrite current exported mod \"{backup.Name}\" with current mod."
- : $"Create exported archive of current mod at \"{backup.Name}\".";
- if (ImUtf8.ButtonEx("Export Mod"u8, tt, buttonSize, ModBackup.CreatingBackup))
- backup.CreateAsync();
-
- if (Im.Item.RightClicked())
- ImUtf8.OpenPopup("context"u8);
-
- Im.Line.Same();
- tt = backup.Exists
- ? $"Delete existing mod export \"{backup.Name}\" (hold {config.DeleteModModifier} while clicking)."
- : $"Exported mod \"{backup.Name}\" does not exist.";
- if (ImUtf8.ButtonEx("Delete Export"u8, tt, buttonSize, !backup.Exists || !config.DeleteModModifier.IsActive()))
- backup.Delete();
-
- tt = backup.Exists
- ? $"Restore mod from exported file \"{backup.Name}\" (hold {config.DeleteModModifier} while clicking)."
- : $"Exported mod \"{backup.Name}\" does not exist.";
- Im.Line.Same();
- if (ImUtf8.ButtonEx("Restore From Export"u8, tt, buttonSize, !backup.Exists || !config.DeleteModModifier.IsActive()))
- backup.Restore(modManager);
- if (backup.Exists)
- {
- Im.Line.Same();
- using (ImRaii.PushFont(UiBuilder.IconFont))
- {
- ImUtf8.Text(FontAwesomeIcon.CheckCircle.ToIconString());
- }
-
- Im.Tooltip.OnHover($"Export exists in \"{backup.Name}\".");
- }
-
- using var context = ImUtf8.Popup("context"u8);
- if (!context)
- return;
-
- if (ImUtf8.Selectable("Open Backup Directory"u8))
- Process.Start(new ProcessStartInfo(modExportManager.ExportDirectory.FullName) { UseShellExecute = true });
- }
-
- /// Anything about editing the regular meta information about the mod.
- private void EditRegularMeta()
- {
- if (Input.Text("Name", Input.Name, Input.None, _mod.Name, out var newName, 256, UiHelpers.InputTextWidth.X))
- modManager.DataEditor.ChangeModName(_mod, newName);
-
- if (Input.Text("Author", Input.Author, Input.None, _mod.Author, out var newAuthor, 256, UiHelpers.InputTextWidth.X))
- modManager.DataEditor.ChangeModAuthor(_mod, newAuthor);
-
- if (Input.Text("Version", Input.Version, Input.None, _mod.Version, out var newVersion, 32,
- UiHelpers.InputTextWidth.X))
- modManager.DataEditor.ChangeModVersion(_mod, newVersion);
-
- if (Input.Text("Website", Input.Website, Input.None, _mod.Website, out var newWebsite, 256,
- UiHelpers.InputTextWidth.X))
- modManager.DataEditor.ChangeModWebsite(_mod, newWebsite);
-
- using var style = ImStyleDouble.ItemSpacing.Push(new Vector2(Im.Style.GlobalScale * 3));
-
- var reducedSize = new Vector2(UiHelpers.InputTextMinusButton3, 0);
- if (ImGui.Button("Edit Description", reducedSize))
- descriptionPopup.Open(_mod);
-
-
- Im.Line.Same();
- var fileExists = File.Exists(filenames.ModMetaPath(_mod));
- var tt = fileExists
- ? "Open the metadata json file in the text editor of your choice."
- : "The metadata json file does not exist.";
- if (ImGuiUtil.DrawDisabledButton($"{FontAwesomeIcon.FileExport.ToIconString()}##metaFile", UiHelpers.IconButtonSize, tt,
- !fileExists, true))
- Process.Start(new ProcessStartInfo(filenames.ModMetaPath(_mod)) { UseShellExecute = true });
-
- DrawOpenDefaultMod();
- }
-
- private void EditLocalData()
- {
- DrawImportDate();
- DrawOpenLocalData();
- }
-
- private void DrawImportDate()
- {
- ImEx.TextFramed($"{DateTimeOffset.FromUnixTimeMilliseconds(_mod.ImportDate).ToLocalTime():yyyy/MM/dd HH:mm}",
- new Vector2(UiHelpers.InputTextMinusButton3, 0), ImGuiColor.FrameBackground.Get(0.5f));
- Im.Line.Same(0, 3 * Im.Style.GlobalScale);
-
- var canRefresh = config.DeleteModModifier.IsActive();
- var tt = canRefresh
- ? "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.";
-
- if (ImUtf8.IconButton(FontAwesomeIcon.Sync, tt, disabled: !canRefresh))
- modManager.DataEditor.ResetModImportDate(_mod);
- Im.Line.SameInner();
- ImUtf8.Text("Import Date"u8);
- }
-
- private void DrawOpenLocalData()
- {
- var file = filenames.LocalDataFile(_mod);
- var fileExists = File.Exists(file);
- var tt = fileExists
- ? "Open the local mod data file in the text editor of your choice."u8
- : "The local mod data file does not exist."u8;
- if (ImUtf8.ButtonEx("Open Local Data"u8, tt, UiHelpers.InputTextWidth, !fileExists))
- Process.Start(new ProcessStartInfo(file) { UseShellExecute = true });
- }
-
- private void DrawOpenDefaultMod()
- {
- var file = filenames.OptionGroupFile(_mod, -1, false);
- var fileExists = File.Exists(file);
- var tt = fileExists
- ? "Open the default mod data file in the text editor of your choice."
- : "The default mod data file does not exist.";
- if (ImGuiUtil.DrawDisabledButton("Open Default Data", UiHelpers.InputTextWidth, tt, !fileExists))
- Process.Start(new ProcessStartInfo(file) { UseShellExecute = true });
- }
-
-
- /// A text input for the new directory name and a button to apply the move.
- private static class MoveDirectory
- {
- private static string? _currentModDirectory;
- private static NewDirectoryState _state = NewDirectoryState.Identical;
-
- public static void Reset()
- {
- _currentModDirectory = null;
- _state = NewDirectoryState.Identical;
- }
-
- public static void Draw(ModManager modManager, Mod mod, Vector2 buttonSize)
- {
- Im.Item.SetNextWidth(buttonSize.X * 2 + Im.Style.ItemSpacing.X);
- var tmp = _currentModDirectory ?? mod.ModPath.Name;
- if (ImGui.InputText("##newModMove", ref tmp, 64))
- {
- _currentModDirectory = tmp;
- _state = modManager.NewDirectoryValid(mod.ModPath.Name, _currentModDirectory, out _);
- }
-
- var (disabled, tt) = _state switch
- {
- NewDirectoryState.Identical => (true, "Current directory name is identical to new one."),
- NewDirectoryState.Empty => (true, "Please enter a new directory name first."),
- NewDirectoryState.NonExisting => (false, $"Move mod from {mod.ModPath.Name} to {_currentModDirectory}."),
- NewDirectoryState.ExistsEmpty => (false, $"Move mod from {mod.ModPath.Name} to {_currentModDirectory}."),
- NewDirectoryState.ExistsNonEmpty => (true, $"{_currentModDirectory} already exists and is not empty."),
- NewDirectoryState.ExistsAsFile => (true, $"{_currentModDirectory} exists as a file."),
- NewDirectoryState.ContainsInvalidSymbols => (true,
- $"{_currentModDirectory} contains invalid symbols for FFXIV."),
- _ => (true, "Unknown error."),
- };
- Im.Line.Same();
- if (ImGuiUtil.DrawDisabledButton("Rename Mod Directory", buttonSize, tt, disabled) && _currentModDirectory != null)
- {
- modManager.MoveModDirectory(mod, _currentModDirectory);
- Reset();
- }
-
- Im.Line.Same();
- ImGuiComponents.HelpMarker(
- "The mod directory name is used to correspond stored settings and sort orders, otherwise it has no influence on anything that is displayed.\n"
- + "This can currently not be used on pre-existing folders and does not support merges or overwriting.");
- }
- }
-
- /// Handles input text and integers in separate fields without buffers for every single one.
- private static class Input
- {
- // Special field indices to reuse the same string buffer.
- public const int None = -1;
- public const int Name = -2;
- public const int Author = -3;
- public const int Version = -4;
- public const int Website = -5;
- public const int Path = -6;
- public const int Description = -7;
-
- // Temporary strings
- private static string? _currentEdit;
- private static ModPriority? _currentGroupPriority;
- private static int _currentField = None;
- private static int _optionIndex = None;
-
- public static void Reset()
- {
- _currentEdit = null;
- _currentGroupPriority = null;
- _currentField = None;
- _optionIndex = None;
- }
-
- public static bool Text(string label, int field, int option, string oldValue, out string value, uint maxLength, float width)
- {
- var tmp = field == _currentField && option == _optionIndex ? _currentEdit ?? oldValue : oldValue;
- Im.Item.SetNextWidth(width);
-
- if (ImGui.InputText(label, ref tmp))
- {
- _currentEdit = tmp;
- _optionIndex = option;
- _currentField = field;
- }
-
- 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;
- }
- }
-}
+using Dalamud.Interface;
+using Dalamud.Interface.ImGuiNotification;
+using ImSharp;
+using Luna;
+using OtterGui.Widgets;
+using Penumbra.Mods;
+using Penumbra.Mods.Editor;
+using Penumbra.Mods.Manager;
+using Penumbra.Services;
+using Penumbra.UI.ModsTab.Groups;
+
+namespace Penumbra.UI.ModsTab;
+
+public class ModPanelEditTab(
+ ModManager modManager,
+ ModFileSystemSelector selector,
+ ModFileSystem fileSystem,
+ Services.MessageService messager,
+ FilenameService filenames,
+ ModExportManager modExportManager,
+ Configuration config,
+ PredefinedTagManager predefinedTagManager,
+ ModGroupEditDrawer groupEditDrawer,
+ DescriptionEditPopup descriptionPopup,
+ AddGroupDrawer addGroupDrawer)
+ : ITab
+{
+ private readonly TagButtons _modTags = new();
+
+ private ModFileSystem.Leaf _leaf = null!;
+ private Mod _mod = null!;
+
+ public ReadOnlySpan Label
+ => "Edit Mod"u8;
+
+ public ModPanelTab Identifier
+ => ModPanelTab.Edit;
+
+ public void DrawContent()
+ {
+ using var child = Im.Child.Begin("##editChild"u8, Im.ContentRegion.Available);
+ if (!child)
+ return;
+
+ _leaf = selector.SelectedLeaf!;
+ _mod = selector.Selected!;
+
+ EditButtons();
+ EditRegularMeta();
+ UiHelpers.DefaultLineSpace();
+ EditLocalData();
+ UiHelpers.DefaultLineSpace();
+
+ if (Input.Text("Mod Path"u8, Input.Path, Input.None, _leaf.FullName(), out var newPath, UiHelpers.InputTextWidth.X))
+ try
+ {
+ fileSystem.RenameAndMove(_leaf, newPath);
+ }
+ catch (Exception e)
+ {
+ messager.NotificationMessage(e.Message, NotificationType.Warning, false);
+ }
+
+ UiHelpers.DefaultLineSpace();
+
+ FeatureChecker.DrawFeatureFlagInput(modManager.DataEditor, _mod, UiHelpers.InputTextWidth.X);
+
+ UiHelpers.DefaultLineSpace();
+ var sharedTagsEnabled = predefinedTagManager.Enabled;
+ var sharedTagButtonOffset = sharedTagsEnabled ? Im.Style.FrameHeight + Im.Style.FramePadding.X : 0;
+ var tagIdx = _modTags.Draw("Mod Tags: ", "Edit tags by clicking them, or add new tags. Empty tags are removed.", _mod.ModTags,
+ out var editedTag, rightEndOffset: sharedTagButtonOffset);
+ if (tagIdx >= 0)
+ modManager.DataEditor.ChangeModTag(_mod, tagIdx, editedTag);
+
+ if (sharedTagsEnabled)
+ predefinedTagManager.DrawAddFromSharedTagsAndUpdateTags(selector.Selected!.LocalTags, selector.Selected!.ModTags, false,
+ selector.Selected!);
+
+
+ UiHelpers.DefaultLineSpace();
+ addGroupDrawer.Draw(_mod, UiHelpers.InputTextWidth.X);
+ UiHelpers.DefaultLineSpace();
+
+ groupEditDrawer.Draw(_mod);
+ descriptionPopup.Draw();
+ }
+
+ public void Reset()
+ {
+ MoveDirectory.Reset();
+ Input.Reset();
+ }
+
+ /// The general edit row for non-detailed mod edits.
+ private void EditButtons()
+ {
+ var buttonSize = new Vector2(150 * Im.Style.GlobalScale, 0);
+ var folderExists = Directory.Exists(_mod.ModPath.FullName);
+ if (ImEx.Button("Open Mod Directory"u8, buttonSize, folderExists
+ ? $"Open \"{_mod.ModPath.FullName}\" in the file explorer of your choice."
+ : $"Mod directory \"{_mod.ModPath.FullName}\" does not exist.", !folderExists))
+ Process.Start(new ProcessStartInfo(_mod.ModPath.FullName) { UseShellExecute = true });
+
+ Im.Line.Same();
+ if (ImEx.Button("Reload Mod"u8, buttonSize, "Reload the current mod from its files.\n"u8
+ + "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))
+ modManager.ReloadMod(_mod);
+
+ BackupButtons(buttonSize);
+ MoveDirectory.Draw(modManager, _mod, buttonSize);
+
+ UiHelpers.DefaultLineSpace();
+ }
+
+ private void BackupButtons(Vector2 buttonSize)
+ {
+ var backup = new ModBackup(modExportManager, _mod);
+ if (ImEx.Button("Export Mod"u8, buttonSize, ModBackup.CreatingBackup
+ ? "Already exporting a mod."
+ : backup.Exists
+ ? $"Overwrite current exported mod \"{backup.Name}\" with current mod."
+ : $"Create exported archive of current mod at \"{backup.Name}\".", ModBackup.CreatingBackup))
+ backup.CreateAsync();
+
+ if (Im.Item.RightClicked())
+ Im.Popup.Open("context"u8);
+
+ Im.Line.Same();
+ if (ImEx.Button("Delete Export"u8, buttonSize, backup.Exists
+ ? $"Delete existing mod export \"{backup.Name}\" (hold {config.DeleteModModifier} while clicking)."
+ : $"Exported mod \"{backup.Name}\" does not exist.", !backup.Exists || !config.DeleteModModifier.IsActive()))
+ backup.Delete();
+
+ Im.Line.Same();
+ if (ImEx.Button("Restore From Export"u8, buttonSize, backup.Exists
+ ? $"Restore mod from exported file \"{backup.Name}\" (hold {config.DeleteModModifier} while clicking)."
+ : $"Exported mod \"{backup.Name}\" does not exist.", !backup.Exists || !config.DeleteModModifier.IsActive()))
+ backup.Restore(modManager);
+ if (backup.Exists)
+ {
+ Im.Line.Same();
+ ImEx.Icon.Draw(FontAwesomeIcon.CheckCircle.Icon());
+ Im.Tooltip.OnHover($"Export exists in \"{backup.Name}\".");
+ }
+
+ using var context = Im.Popup.Begin("context"u8);
+ if (!context)
+ return;
+
+ if (Im.Selectable("Open Backup Directory"u8))
+ Process.Start(new ProcessStartInfo(modExportManager.ExportDirectory.FullName) { UseShellExecute = true });
+ }
+
+ /// Anything about editing the regular meta information about the mod.
+ private void EditRegularMeta()
+ {
+ if (Input.Text("Name"u8, Input.Name, Input.None, _mod.Name, out var newName, UiHelpers.InputTextWidth.X))
+ modManager.DataEditor.ChangeModName(_mod, newName);
+
+ if (Input.Text("Author"u8, Input.Author, Input.None, _mod.Author, out var newAuthor, UiHelpers.InputTextWidth.X))
+ modManager.DataEditor.ChangeModAuthor(_mod, newAuthor);
+
+ if (Input.Text("Version"u8, Input.Version, Input.None, _mod.Version, out var newVersion,
+ UiHelpers.InputTextWidth.X))
+ modManager.DataEditor.ChangeModVersion(_mod, newVersion);
+
+ if (Input.Text("Website"u8, Input.Website, Input.None, _mod.Website, out var newWebsite,
+ UiHelpers.InputTextWidth.X))
+ modManager.DataEditor.ChangeModWebsite(_mod, newWebsite);
+
+ using var style = ImStyleDouble.ItemSpacing.Push(new Vector2(Im.Style.GlobalScale * 3));
+
+ var reducedSize = new Vector2(UiHelpers.InputTextMinusButton3, 0);
+ if (Im.Button("Edit Description"u8, reducedSize))
+ descriptionPopup.Open(_mod);
+
+
+ Im.Line.Same();
+ var fileExists = File.Exists(filenames.ModMetaPath(_mod));
+ var tt = fileExists
+ ? "Open the metadata json file in the text editor of your choice."u8
+ : "The metadata json file does not exist."u8;
+ using (Im.Id.Push("meta"))
+ {
+ if (ImEx.Icon.Button(LunaStyle.FileExportIcon, tt, !fileExists))
+ Process.Start(new ProcessStartInfo(filenames.ModMetaPath(_mod)) { UseShellExecute = true });
+ }
+
+ DrawOpenDefaultMod();
+ }
+
+ private void EditLocalData()
+ {
+ DrawImportDate();
+ DrawOpenLocalData();
+ }
+
+ private void DrawImportDate()
+ {
+ ImEx.TextFramed($"{DateTimeOffset.FromUnixTimeMilliseconds(_mod.ImportDate).ToLocalTime():yyyy/MM/dd HH:mm}",
+ new Vector2(UiHelpers.InputTextMinusButton3, 0), ImGuiColor.FrameBackground.Get(0.5f));
+ Im.Line.Same(0, 3 * Im.Style.GlobalScale);
+
+ var canRefresh = config.DeleteModModifier.IsActive();
+ if (ImEx.Icon.Button(LunaStyle.RefreshIcon, canRefresh
+ ? "Reset the import date to the current date and time."u8
+ : $"Reset the import date to the current date and time.\nHold {config.DeleteModModifier} while clicking to refresh.",
+ !canRefresh))
+ modManager.DataEditor.ResetModImportDate(_mod);
+ Im.Line.SameInner();
+ Im.Text("Import Date"u8);
+ }
+
+ private void DrawOpenLocalData()
+ {
+ var file = filenames.LocalDataFile(_mod);
+ var fileExists = File.Exists(file);
+ var tt = fileExists
+ ? "Open the local mod data file in the text editor of your choice."u8
+ : "The local mod data file does not exist."u8;
+ if (ImEx.Button("Open Local Data"u8, UiHelpers.InputTextWidth, tt, !fileExists))
+ Process.Start(new ProcessStartInfo(file) { UseShellExecute = true });
+ }
+
+ private void DrawOpenDefaultMod()
+ {
+ var file = filenames.OptionGroupFile(_mod, -1, false);
+ var fileExists = File.Exists(file);
+ var tt = fileExists
+ ? "Open the default mod data file in the text editor of your choice."u8
+ : "The default mod data file does not exist."u8;
+ if (ImEx.Button("Open Default Data"u8, UiHelpers.InputTextWidth, tt, !fileExists))
+ Process.Start(new ProcessStartInfo(file) { UseShellExecute = true });
+ }
+
+
+ /// A text input for the new directory name and a button to apply the move.
+ private static class MoveDirectory
+ {
+ private static string? _currentModDirectory;
+ private static NewDirectoryState _state = NewDirectoryState.Identical;
+
+ public static void Reset()
+ {
+ _currentModDirectory = null;
+ _state = NewDirectoryState.Identical;
+ }
+
+ public static void Draw(ModManager modManager, Mod mod, Vector2 buttonSize)
+ {
+ Im.Item.SetNextWidth(buttonSize.X * 2 + Im.Style.ItemSpacing.X);
+ var tmp = _currentModDirectory ?? mod.ModPath.Name;
+ if (Im.Input.Text("##newModMove"u8, ref tmp))
+ {
+ _currentModDirectory = tmp;
+ _state = modManager.NewDirectoryValid(mod.ModPath.Name, _currentModDirectory, out _);
+ }
+
+ var (disabled, tt) = _state switch
+ {
+ NewDirectoryState.Identical => (true, "Current directory name is identical to new one."),
+ NewDirectoryState.Empty => (true, "Please enter a new directory name first."),
+ NewDirectoryState.NonExisting => (false, $"Move mod from {mod.ModPath.Name} to {_currentModDirectory}."),
+ NewDirectoryState.ExistsEmpty => (false, $"Move mod from {mod.ModPath.Name} to {_currentModDirectory}."),
+ NewDirectoryState.ExistsNonEmpty => (true, $"{_currentModDirectory} already exists and is not empty."),
+ NewDirectoryState.ExistsAsFile => (true, $"{_currentModDirectory} exists as a file."),
+ NewDirectoryState.ContainsInvalidSymbols => (true,
+ $"{_currentModDirectory} contains invalid symbols for FFXIV."),
+ _ => (true, "Unknown error."),
+ };
+ Im.Line.Same();
+ if (ImEx.Button("Rename Mod Directory"u8, buttonSize, tt, disabled) && _currentModDirectory is not null)
+ {
+ modManager.MoveModDirectory(mod, _currentModDirectory);
+ Reset();
+ }
+
+ Im.Line.Same();
+ if (LunaStyle.DrawAlignedHelpMarker())
+ Im.Tooltip.Set(
+ "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
+ + "This can currently not be used on pre-existing folders and does not support merges or overwriting."u8);
+ }
+ }
+
+ /// Handles input text and integers in separate fields without buffers for every single one.
+ private static class Input
+ {
+ // Special field indices to reuse the same string buffer.
+ public const int None = -1;
+ public const int Name = -2;
+ public const int Author = -3;
+ public const int Version = -4;
+ public const int Website = -5;
+ public const int Path = -6;
+
+ // Temporary strings
+ private static string? _currentEdit;
+ private static int _currentField = None;
+ private static int _optionIndex = None;
+
+ public static void Reset()
+ {
+ _currentEdit = null;
+ _currentField = None;
+ _optionIndex = None;
+ }
+
+ public static bool Text(ReadOnlySpan label, int field, int option, string oldValue, out string value, float width)
+ {
+ var tmp = field == _currentField && option == _optionIndex ? _currentEdit ?? oldValue : oldValue;
+ Im.Item.SetNextWidth(width);
+
+ if (Im.Input.Text(label, ref tmp))
+ {
+ _currentEdit = tmp;
+ _optionIndex = option;
+ _currentField = field;
+ }
+
+ if (Im.Item.DeactivatedAfterEdit && _currentEdit is not null)
+ {
+ var ret = _currentEdit != oldValue;
+ value = _currentEdit;
+ Reset();
+ return ret;
+ }
+
+ value = string.Empty;
+ return false;
+ }
+ }
+}
diff --git a/Penumbra/UI/ModsTab/ModPanelSettingsTab.cs b/Penumbra/UI/ModsTab/ModPanelSettingsTab.cs
index b17551f4..92343c63 100644
--- a/Penumbra/UI/ModsTab/ModPanelSettingsTab.cs
+++ b/Penumbra/UI/ModsTab/ModPanelSettingsTab.cs
@@ -1,8 +1,8 @@
using Dalamud.Bindings.ImGui;
using ImSharp;
+using Luna;
using OtterGui.Raii;
using OtterGui.Text;
-using OtterGui.Widgets;
using Penumbra.UI.Classes;
using Penumbra.Collections.Manager;
using Penumbra.Communication;
@@ -22,7 +22,7 @@ public class ModPanelSettingsTab(
CommunicatorService communicator,
ModGroupDrawer modGroupDrawer,
Configuration config)
- : ITab, Luna.IUiService
+ : ITab
{
private bool _inherited;
private bool _temporary;
@@ -31,8 +31,11 @@ public class ModPanelSettingsTab(
public ReadOnlySpan Label
=> "Settings"u8;
+
+ public ModPanelTab Identifier
+ => ModPanelTab.Settings;
- public void DrawHeader()
+ public void PostTabButton()
=> tutorial.OpenTutorial(BasicTutorialSteps.ModOptions);
public void Reset()
diff --git a/Penumbra/UI/ModsTab/ModPanelTabBar.cs b/Penumbra/UI/ModsTab/ModPanelTabBar.cs
index daeb2394..3ebe7999 100644
--- a/Penumbra/UI/ModsTab/ModPanelTabBar.cs
+++ b/Penumbra/UI/ModsTab/ModPanelTabBar.cs
@@ -1,10 +1,5 @@
-using Dalamud.Bindings.ImGui;
-using Dalamud.Interface;
using ImSharp;
using Luna;
-using OtterGui;
-using OtterGui.Raii;
-using OtterGui.Widgets;
using Penumbra.Mods;
using Penumbra.Mods.Manager;
using Penumbra.UI.AdvancedWindow;
@@ -12,138 +7,88 @@ using ImGuiColor = ImSharp.ImGuiColor;
namespace Penumbra.UI.ModsTab;
-public class ModPanelTabBar : IUiService
+public enum ModPanelTab
{
- private enum ModPanelTabType
- {
- Description,
- Settings,
- ChangedItems,
- Conflicts,
- Collections,
- Edit,
- };
+ Description,
+ Settings,
+ ChangedItems,
+ Conflicts,
+ Collections,
+ Edit,
+};
- public readonly ModPanelSettingsTab Settings;
- public readonly ModPanelDescriptionTab Description;
- public readonly ModPanelCollectionsTab Collections;
- public readonly ModPanelConflictsTab Conflicts;
- public readonly ModPanelChangedItemsTab ChangedItems;
- public readonly ModPanelEditTab Edit;
- private readonly ModEditWindowFactory _modEditWindowFactory;
- private readonly ModManager _modManager;
- private readonly TutorialService _tutorial;
+public class ModPanelTabBar : TabBar
+{
+ public readonly ModPanelSettingsTab Settings;
+ public readonly ModPanelEditTab Edit;
+ private readonly ModManager _modManager;
+ private readonly TutorialService _tutorial;
- public readonly ITab[] Tabs;
- private ModPanelTabType _preferredTab = ModPanelTabType.Settings;
- private Mod? _lastMod;
+ private Mod? _lastMod;
public ModPanelTabBar(ModEditWindowFactory modEditWindowFactory, ModPanelSettingsTab settings, ModPanelDescriptionTab description,
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;
- Settings = settings;
- Description = description;
- Conflicts = conflicts;
- ChangedItems = changedItems;
- Edit = edit;
- _modManager = modManager;
- _tutorial = tutorial;
- Collections = collections;
+ Flags = TabBarFlags.NoTooltip;
+ Settings = settings;
+ Edit = edit;
+ _modManager = modManager;
+ _tutorial = tutorial;
+ Buttons.AddButton(new AdvancedEditingButton(this, modEditWindowFactory), 0);
+ }
- Tabs =
- [
- Settings,
- Description,
- Conflicts,
- ChangedItems,
- Collections,
- Edit,
- ];
+ private sealed class AdvancedEditingButton(ModPanelTabBar parent, ModEditWindowFactory editFactory) : BaseButton
+ {
+ public override ReadOnlySpan Label
+ => "Advanced Editing"u8;
+
+ public override void OnClick()
+ {
+ 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)
{
- var tabBarHeight = ImGui.GetCursorPosY();
- if (_lastMod != mod)
- {
- _lastMod = mod;
- TabBar.Draw(string.Empty, ImGuiTabBarFlags.NoTooltip, ToLabel(_preferredTab), out _, () => DrawAdvancedEditingButton(mod), Tabs);
- }
- else
- {
- TabBar.Draw(string.Empty, ImGuiTabBarFlags.NoTooltip, ReadOnlySpan.Empty, out var label, () => DrawAdvancedEditingButton(mod),
- Tabs);
- _preferredTab = ToType(label);
- }
+ var tabBarHeight = Im.Cursor.Y;
+ _lastMod = mod;
+ base.Draw();
DrawFavoriteButton(mod, tabBarHeight);
}
- private ReadOnlySpan 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.Empty,
- };
-
- private ModPanelTabType ToType(ReadOnlySpan 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)
{
var size = ImEx.Icon.CalculateSize(LunaStyle.FavoriteIcon) + Im.Style.FramePadding * 2;
- var newPos = new Vector2(ImGui.GetWindowWidth() - size.X - Im.Style.ItemSpacing.X, height);
- if (ImGui.GetScrollMaxX() > 0)
- newPos.X += ImGui.GetScrollX();
+ var newPos = new Vector2(Im.Window.Width - size.X - Im.Style.ItemSpacing.X, height);
+ if (Im.Scroll.MaximumX > 0)
+ newPos.X += Im.Scroll.X;
- var rectUpper = ImGui.GetWindowPos() + newPos;
- var color = ImGui.IsMouseHoveringRect(rectUpper, rectUpper + size) ? Im.Style[ImGuiColor.Text] :
- mod.Favorite ? LunaStyle.FavoriteColor : Im.Style[ImGuiColor.TextDisabled];
+ var rectUpper = Im.Window.Position + newPos;
+ var color = Im.Mouse.IsHoveringRectangle(rectUpper, rectUpper + size) ? Im.Style[ImGuiColor.Text] :
+ mod.Favorite ? LunaStyle.FavoriteColor : Im.Style[ImGuiColor.TextDisabled];
using var c = ImGuiColor.Text.Push(color)
.Push(ImGuiColor.Button, Vector4.Zero)
.Push(ImGuiColor.ButtonHovered, Vector4.Zero)
.Push(ImGuiColor.ButtonActive, Vector4.Zero);
- ImGui.SetCursorPos(newPos);
+ Im.Cursor.Position = newPos;
if (ImEx.Icon.Button(LunaStyle.FavoriteIcon))
_modManager.DataEditor.ChangeModFavorite(mod, !mod.Favorite);
@@ -151,6 +96,6 @@ public class ModPanelTabBar : IUiService
_tutorial.OpenTutorial(BasicTutorialSteps.Favorites);
if (hovered)
- ImGui.SetTooltip("Favorite");
+ Im.Tooltip.Set("Favorite"u8);
}
}
diff --git a/Penumbra/UI/ResourceWatcher/ResourceWatcher.cs b/Penumbra/UI/ResourceWatcher/ResourceWatcher.cs
index eede4474..ae43c0bd 100644
--- a/Penumbra/UI/ResourceWatcher/ResourceWatcher.cs
+++ b/Penumbra/UI/ResourceWatcher/ResourceWatcher.cs
@@ -2,7 +2,7 @@ using Dalamud.Bindings.ImGui;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using FFXIVClientStructs.FFXIV.Client.System.Resource;
using ImSharp;
-using OtterGui.Widgets;
+using Luna;
using Penumbra.Api.Enums;
using Penumbra.Collections;
using Penumbra.GameData.Actors;
@@ -16,7 +16,7 @@ using Penumbra.UI.Classes;
namespace Penumbra.UI.ResourceWatcher;
-public sealed class ResourceWatcher : IDisposable, ITab, Luna.IUiService
+public sealed class ResourceWatcher : IDisposable, ITab
{
public const int DefaultMaxEntries = 1024;
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 Label
=> "Resource Logger"u8;
+ public TabType Identifier
+ => TabType.ResourceWatcher;
+
public void DrawContent()
{
UpdateRecords();
diff --git a/Penumbra/UI/ResourceWatcher/ResourceWatcherTable.cs b/Penumbra/UI/ResourceWatcher/ResourceWatcherTable.cs
index ccc0d6d8..52fd14b9 100644
--- a/Penumbra/UI/ResourceWatcher/ResourceWatcherTable.cs
+++ b/Penumbra/UI/ResourceWatcher/ResourceWatcherTable.cs
@@ -2,10 +2,7 @@ using Dalamud.Bindings.ImGui;
using Dalamud.Interface;
using ImSharp;
using Luna;
-using OtterGui;
-using OtterGui.Raii;
using OtterGui.Table;
-using OtterGui.Text;
using Penumbra.Enums;
using Penumbra.Interop.Structs;
using Penumbra.String;
@@ -54,16 +51,16 @@ internal sealed class ResourceWatcherTable : Table
=> 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)
return;
- var size = ImUtf8.CalcTextSize(path.Span);
+ var size = Im.Font.CalculateSize(path.Span);
var clicked = false;
if (size.X <= length)
{
- clicked = ImUtf8.Selectable(path.Span);
+ clicked = Im.Selectable(path.Span);
}
else
{
@@ -71,10 +68,11 @@ internal sealed class ResourceWatcherTable : Table
using (Im.Group())
{
CiByteString shortPath;
- if (fileName != -1)
+ var icon = FontAwesomeIcon.EllipsisH.Icon();
+ if (fileName is not -1)
{
- using var font = ImRaii.PushFont(UiBuilder.IconFont);
- clicked = ImUtf8.Selectable(FontAwesomeIcon.EllipsisH.ToIconString());
+ using var font = AwesomeIcon.Font.Push();
+ clicked = Im.Selectable(icon.Span);
Im.Line.SameInner();
shortPath = path.Substring(fileName, path.Length - fileName);
}
@@ -83,14 +81,14 @@ internal sealed class ResourceWatcherTable : Table
shortPath = path;
}
- clicked |= ImUtf8.Selectable(shortPath.Span, false, ImGuiSelectableFlags.AllowItemOverlap);
+ clicked |= Im.Selectable(shortPath.Span, false, SelectableFlags.AllowOverlap);
}
Im.Tooltip.OnHover(path.Span);
}
if (clicked)
- ImUtf8.SetClipboardText(path.Span);
+ Im.Clipboard.Set(path.Span);
}
private sealed class RecordTypeColumn : ColumnFlags
@@ -153,13 +151,13 @@ internal sealed class ResourceWatcherTable : Table
public override float Width
=> UiBuilder.MonoFont.GetCharAdvance('0') * 17;
- public override unsafe string ToName(Record item)
- => item.Crc64 != 0 ? $"{item.Crc64:X16}" : string.Empty;
+ public override string ToName(Record item)
+ => item.Crc64 is not 0 ? $"{item.Crc64:X16}" : string.Empty;
public override unsafe void DrawColumn(Record item, int _)
{
- using var font = ImRaii.PushFont(UiBuilder.MonoFont, item.Handle != null);
- ImUtf8.Text(ToName(item));
+ using var font = item.Handle is null ? null : Im.Font.PushMono();
+ Im.Text(ToName(item));
}
}
@@ -334,17 +332,17 @@ internal sealed class ResourceWatcherTable : Table
var (icon, color, tt) = item.LoadState switch
{
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(),
- $"Dependencies failed to load ({(byte)item.LoadState})."),
+ new StringU8($"Dependencies failed to load ({(byte)item.LoadState}).")),
<= LoadState.Constructed => (FontAwesomeIcon.QuestionCircle, ColorId.UndefinedMod.Value(),
- $"Not yet loaded ({(byte)item.LoadState})."),
- < LoadState.Success => (FontAwesomeIcon.Clock, ColorId.FolderLine.Value(), $"Loading asynchronously ({(byte)item.LoadState})."),
+ new StringU8($"Not yet loaded ({(byte)item.LoadState}).")),
+ < LoadState.Success => (FontAwesomeIcon.Clock, ColorId.FolderLine.Value(), new StringU8($"Loading asynchronously ({(byte)item.LoadState}).")),
> 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);
- ImGuiUtil.HoverTooltip(tt);
+ Im.Tooltip.OnHover(tt);
}
public override int Compare(Record lhs, Record rhs)
@@ -361,8 +359,8 @@ internal sealed class ResourceWatcherTable : Table
public override unsafe void DrawColumn(Record item, int _)
{
- using var font = ImRaii.PushFont(UiBuilder.MonoFont, item.Handle != null);
- ImGuiUtil.RightAlign(ToName(item));
+ using var font = item.Handle is null ? null : Im.Font.PushMono();
+ ImEx.TextRightAligned(ToName(item));
}
}
@@ -447,7 +445,7 @@ internal sealed class ResourceWatcherTable : Table
=> 30 * Im.Style.GlobalScale;
public override void DrawColumn(Record item, int _)
- => ImGuiUtil.RightAlign(item.RefCount.ToString());
+ => ImEx.TextRightAligned($"{item.RefCount}");
public override int Compare(Record lhs, Record rhs)
=> lhs.RefCount.CompareTo(rhs.RefCount);
@@ -462,7 +460,7 @@ internal sealed class ResourceWatcherTable : Table
=> item.OsThreadId.ToString();
public override void DrawColumn(Record item, int _)
- => ImGuiUtil.RightAlign(ToName(item));
+ => ImEx.TextRightAligned($"{item.OsThreadId}");
public override int Compare(Record lhs, Record rhs)
=> lhs.OsThreadId.CompareTo(rhs.OsThreadId);
diff --git a/Penumbra/UI/Tabs/ChangedItemsTab.cs b/Penumbra/UI/Tabs/ChangedItemsTab.cs
index bb5193ea..3ff58171 100644
--- a/Penumbra/UI/Tabs/ChangedItemsTab.cs
+++ b/Penumbra/UI/Tabs/ChangedItemsTab.cs
@@ -1,9 +1,9 @@
using Dalamud.Bindings.ImGui;
using ImSharp;
+using Luna;
using OtterGui;
using OtterGui.Raii;
using OtterGui.Text;
-using OtterGui.Widgets;
using Penumbra.Api.Enums;
using Penumbra.Collections.Manager;
using Penumbra.Communication;
@@ -15,19 +15,22 @@ using Penumbra.UI.Classes;
namespace Penumbra.UI.Tabs;
-public class ChangedItemsTab(
+public sealed class ChangedItemsTab(
CollectionManager collectionManager,
CollectionSelectHeader collectionHeader,
ChangedItemDrawer drawer,
CommunicatorService communicator)
- : ITab, Luna.IUiService
+ : ITab
{
public ReadOnlySpan Label
=> "Changed Items"u8;
- private string _changedItemFilter = string.Empty;
- private string _changedItemModFilter = string.Empty;
- private Vector2 _buttonSize;
+ public TabType Identifier
+ => TabType.ChangedItems;
+
+ private string _changedItemFilter = string.Empty;
+ private string _changedItemModFilter = string.Empty;
+ private Vector2 _buttonSize;
public void DrawContent()
{
@@ -105,7 +108,7 @@ public class ChangedItemsTab(
if (ImUtf8.Selectable(first.Name, false, ImGuiSelectableFlags.None, _buttonSize with { X = 0 })
&& ImGui.GetIO().KeyCtrl
&& 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())
return;
diff --git a/Penumbra/UI/Tabs/CollectionsTab.cs b/Penumbra/UI/Tabs/CollectionsTab.cs
index e6025052..8af56cf7 100644
--- a/Penumbra/UI/Tabs/CollectionsTab.cs
+++ b/Penumbra/UI/Tabs/CollectionsTab.cs
@@ -2,8 +2,9 @@ using Dalamud.Bindings.ImGui;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Plugin;
using ImSharp;
+using Luna;
using OtterGui.Raii;
-using OtterGui.Widgets;
+using Penumbra.Api.Enums;
using Penumbra.Collections.Manager;
using Penumbra.GameData.Actors;
using Penumbra.Mods.Manager;
@@ -12,7 +13,7 @@ using Penumbra.UI.CollectionTab;
namespace Penumbra.UI.Tabs;
-public sealed class CollectionsTab : IDisposable, ITab, Luna.IUiService
+public sealed class CollectionsTab : ITab, IDisposable
{
private readonly EphemeralConfig _config;
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,
CollectionManager collectionManager, ModStorage modStorage, ActorManager actors, ITargetManager targets, TutorialService tutorial, SaveService saveService)
{
diff --git a/Penumbra/UI/Tabs/ConfigTabBar.cs b/Penumbra/UI/Tabs/ConfigTabBar.cs
deleted file mode 100644
index 52a95940..00000000
--- a/Penumbra/UI/Tabs/ConfigTabBar.cs
+++ /dev/null
@@ -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;
-
- /// The tab to select on the next Draw call, if any.
- 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 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.Empty,
- };
-
- private TabType FromLabel(ReadOnlySpan 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;
- }
-}
diff --git a/Penumbra/UI/Tabs/Debug/DebugTab.cs b/Penumbra/UI/Tabs/Debug/DebugTab.cs
index c14662d4..d62328f5 100644
--- a/Penumbra/UI/Tabs/Debug/DebugTab.cs
+++ b/Penumbra/UI/Tabs/Debug/DebugTab.cs
@@ -14,8 +14,8 @@ using Luna;
using Microsoft.Extensions.DependencyInjection;
using OtterGui;
using OtterGui.Text;
-using OtterGui.Widgets;
using Penumbra.Api;
+using Penumbra.Api.Enums;
using Penumbra.Collections.Manager;
using Penumbra.GameData.Actors;
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
{
private readonly Configuration _config;
private readonly CollectionManager _collectionManager;
@@ -173,6 +173,9 @@ public class DebugTab : Window, ITab
public bool IsVisible
=> _config is { DebugMode: true, Ephemeral.DebugSeparateWindow: false };
+ public TabType Identifier
+ => TabType.Debug;
+
#if DEBUG
private const string DebugVersionString = "(Debug)";
#else
diff --git a/Penumbra/UI/Tabs/EffectiveTab.cs b/Penumbra/UI/Tabs/EffectiveTab.cs
index 33f5e120..98b53c11 100644
--- a/Penumbra/UI/Tabs/EffectiveTab.cs
+++ b/Penumbra/UI/Tabs/EffectiveTab.cs
@@ -1,10 +1,11 @@
using Dalamud.Bindings.ImGui;
using Dalamud.Interface;
using ImSharp;
+using Luna;
using OtterGui;
using OtterGui.Raii;
using OtterGui.Text;
-using OtterGui.Widgets;
+using Penumbra.Api.Enums;
using Penumbra.Collections;
using Penumbra.Collections.Cache;
using Penumbra.Collections.Manager;
@@ -15,12 +16,15 @@ using Penumbra.UI.Classes;
namespace Penumbra.UI.Tabs;
-public class EffectiveTab(CollectionManager collectionManager, CollectionSelectHeader collectionHeader)
- : ITab, Luna.IUiService
+public sealed class EffectiveTab(CollectionManager collectionManager, CollectionSelectHeader collectionHeader)
+ : ITab
{
public ReadOnlySpan Label
=> "Effective Changes"u8;
+ public TabType Identifier
+ => TabType.EffectiveChanges;
+
public void DrawContent()
{
SetupEffectiveSizes();
diff --git a/Penumbra/UI/Tabs/MainTabBar.cs b/Penumbra/UI/Tabs/MainTabBar.cs
new file mode 100644
index 00000000..60526d34
--- /dev/null
+++ b/Penumbra/UI/Tabs/MainTabBar.cs
@@ -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, 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();
+ }
+}
diff --git a/Penumbra/UI/Tabs/MessagesTab.cs b/Penumbra/UI/Tabs/MessagesTab.cs
index 4e0de2f2..de28ffa0 100644
--- a/Penumbra/UI/Tabs/MessagesTab.cs
+++ b/Penumbra/UI/Tabs/MessagesTab.cs
@@ -1,9 +1,10 @@
-using OtterGui.Widgets;
-using Penumbra.Services;
+using Luna;
+using Penumbra.Api.Enums;
+using MessageService = Penumbra.Services.MessageService;
namespace Penumbra.UI.Tabs;
-public class MessagesTab(MessageService messages) : ITab, Luna.IUiService
+public sealed class MessagesTab(MessageService messages) : ITab
{
public ReadOnlySpan Label
=> "Messages"u8;
@@ -13,4 +14,7 @@ public class MessagesTab(MessageService messages) : ITab, Luna.IUiService
public void DrawContent()
=> messages.DrawNotificationLog();
+
+ public TabType Identifier
+ => TabType.Messages;
}
diff --git a/Penumbra/UI/Tabs/ModsTab.cs b/Penumbra/UI/Tabs/ModsTab.cs
index 988a8253..53ff8121 100644
--- a/Penumbra/UI/Tabs/ModsTab.cs
+++ b/Penumbra/UI/Tabs/ModsTab.cs
@@ -7,7 +7,7 @@ using Dalamud.Interface;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game;
using ImSharp;
-using OtterGui.Widgets;
+using Luna;
using Penumbra.Api.Enums;
using Penumbra.Interop.Services;
using Penumbra.Mods;
@@ -19,7 +19,7 @@ using Penumbra.GameData.Interop;
namespace Penumbra.UI.Tabs;
-public class ModsTab(
+public sealed class ModsTab(
ModManager modManager,
CollectionManager collectionManager,
ModFileSystemSelector selector,
@@ -31,7 +31,7 @@ public class ModsTab(
CollectionSelectHeader collectionHeader,
ITargetManager targets,
ObjectManager objects)
- : ITab, Luna.IUiService
+ : ITab
{
private readonly ActiveCollections _activeCollections = collectionManager.Active;
@@ -41,7 +41,10 @@ public class ModsTab(
public ReadOnlySpan Label
=> "Mods"u8;
- public void DrawHeader()
+ public TabType Identifier
+ => TabType.Mods;
+
+ public void PostTabButton()
=> tutorial.OpenTutorial(BasicTutorialSteps.Mods);
public Mod SelectMod
@@ -60,7 +63,8 @@ public class ModsTab(
collectionHeader.Draw(false);
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))
{
style.Pop();
diff --git a/Penumbra/UI/Tabs/OnScreenTab.cs b/Penumbra/UI/Tabs/OnScreenTab.cs
index 4125eb22..c39eaba8 100644
--- a/Penumbra/UI/Tabs/OnScreenTab.cs
+++ b/Penumbra/UI/Tabs/OnScreenTab.cs
@@ -1,9 +1,10 @@
-using OtterGui.Widgets;
+using Luna;
+using Penumbra.Api.Enums;
using Penumbra.UI.AdvancedWindow;
namespace Penumbra.UI.Tabs;
-public class OnScreenTab(ResourceTreeViewerFactory resourceTreeViewerFactory) : ITab, Luna.IUiService
+public sealed class OnScreenTab(ResourceTreeViewerFactory resourceTreeViewerFactory) : ITab
{
private readonly ResourceTreeViewer _viewer = resourceTreeViewerFactory.Create(0, delegate { }, delegate { });
@@ -12,4 +13,7 @@ public class OnScreenTab(ResourceTreeViewerFactory resourceTreeViewerFactory) :
public void DrawContent()
=> _viewer.Draw();
+
+ public TabType Identifier
+ => TabType.OnScreen;
}
diff --git a/Penumbra/UI/Tabs/ResourceTab.cs b/Penumbra/UI/Tabs/ResourceTab.cs
index 058ae1ed..909ff406 100644
--- a/Penumbra/UI/Tabs/ResourceTab.cs
+++ b/Penumbra/UI/Tabs/ResourceTab.cs
@@ -4,17 +4,21 @@ using FFXIVClientStructs.FFXIV.Client.System.Resource;
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
using FFXIVClientStructs.STD;
using ImSharp;
+using Luna;
using OtterGui;
using OtterGui.Raii;
-using OtterGui.Widgets;
+using Penumbra.Api.Enums;
using Penumbra.Interop.Hooks.ResourceLoading;
using Penumbra.String.Classes;
namespace Penumbra.UI.Tabs;
-public class ResourceTab(Configuration config, ResourceManagerService resourceManager, ISigScanner sigScanner)
- : ITab, Luna.IUiService
+public sealed class ResourceTab(Configuration config, ResourceManagerService resourceManager, ISigScanner sigScanner)
+ : ITab
{
+ public TabType Identifier
+ => TabType.ResourceManager;
+
public ReadOnlySpan Label
=> "Resource Manager"u8;
@@ -52,7 +56,8 @@ public class ResourceTab(Configuration config, ResourceManagerService resourceMa
private string _resourceManagerFilter = string.Empty;
/// Draw a single resource map.
- private unsafe void DrawResourceMap(ResourceCategory category, uint ext, StdMap>* map)
+ private unsafe void DrawResourceMap(ResourceCategory category, uint ext,
+ StdMap>* map)
{
if (map == null)
return;
diff --git a/Penumbra/UI/Tabs/SettingsTab.cs b/Penumbra/UI/Tabs/SettingsTab.cs
index ba014096..f1407bad 100644
--- a/Penumbra/UI/Tabs/SettingsTab.cs
+++ b/Penumbra/UI/Tabs/SettingsTab.cs
@@ -1,7 +1,6 @@
using Dalamud.Bindings.ImGui;
using Dalamud.Interface;
using Dalamud.Interface.Components;
-using Dalamud.Interface.Utility;
using Dalamud.Plugin;
using Dalamud.Plugin.Services;
using Dalamud.Utility;
@@ -12,6 +11,7 @@ using OtterGui.Raii;
using OtterGui.Text;
using OtterGui.Widgets;
using Penumbra.Api;
+using Penumbra.Api.Enums;
using Penumbra.Collections;
using Penumbra.Interop;
using Penumbra.Interop.Hooks.PostProcessing;
@@ -23,10 +23,13 @@ using Penumbra.UI.ModsTab;
namespace Penumbra.UI.Tabs;
-public class SettingsTab : ITab, IUiService
+public sealed class SettingsTab : ITab
{
public const int RootDirectoryMaxLength = 64;
+ public TabType Identifier
+ => TabType.Settings;
+
public ReadOnlySpan Label
=> "Settings"u8;