Update some files for Luna.

This commit is contained in:
Ottermandias 2025-10-19 22:24:52 +02:00
parent 3f48cd6910
commit a4302c9145
23 changed files with 1669 additions and 1712 deletions

2
Luna

@ -1 +1 @@
Subproject commit 2542a4665aca0ee7a66ad940638ff84cd04e35b5 Subproject commit 1ffa8c5de118f94b609f7d6352e3b63de463398c

@ -1 +1 @@
Subproject commit fcb443b79b0954967a959aa2f498699bd1a54923 Subproject commit 3a9406bc634228cc0815ddb6fe5e20419cafb864

View file

@ -1,4 +1,5 @@
using Dalamud.Bindings.ImGui; using Dalamud.Bindings.ImGui;
using ImSharp;
using OtterGui; using OtterGui;
using OtterGui.Raii; using OtterGui.Raii;
using Penumbra.Import.Structs; using Penumbra.Import.Structs;
@ -89,12 +90,12 @@ public partial class TexToolsImporter
ImGui.TableNextColumn(); ImGui.TableNextColumn();
if (ex == null) if (ex == null)
{ {
using var color = ImRaii.PushColor(ImGuiCol.Text, ColorId.FolderExpanded.Value()); using var color = ImGuiColor.Text.Push(ColorId.FolderExpanded.Value());
ImGui.TextUnformatted(dir?.FullName[(_baseDirectory.FullName.Length + 1)..] ?? "Unknown Directory"); ImGui.TextUnformatted(dir?.FullName[(_baseDirectory.FullName.Length + 1)..] ?? "Unknown Directory");
} }
else else
{ {
using var color = ImRaii.PushColor(ImGuiCol.Text, ColorId.ConflictingMod.Value()); using var color = ImGuiColor.Text.Push(ColorId.ConflictingMod.Value());
ImGui.TextUnformatted(ex.Message); ImGui.TextUnformatted(ex.Message);
ImGuiUtil.HoverTooltip(ex.ToString()); ImGuiUtil.HoverTooltip(ex.ToString());
} }

View file

@ -125,9 +125,9 @@ public static class TextureDrawer
{ {
var (path, game, isOnPlayer) = Items[globalIdx]; var (path, game, isOnPlayer) = Items[globalIdx];
bool ret; bool ret;
using (var color = ImRaii.PushColor(ImGuiCol.Text, ColorId.FolderExpanded.Value(), game)) using (var color = ImGuiColor.Text.Push(ColorId.FolderExpanded.Value(), game))
{ {
color.Push(ImGuiCol.Text, ColorId.HandledConflictMod.Value(), isOnPlayer); color.Push(ImGuiColor.Text, ColorId.HandledConflictMod.Value(), isOnPlayer);
var equals = string.Equals(CurrentSelection.Path, path, StringComparison.OrdinalIgnoreCase); var equals = string.Equals(CurrentSelection.Path, path, StringComparison.OrdinalIgnoreCase);
var p = game ? $"--> {path}" : path[_skipPrefix..]; var p = game ? $"--> {path}" : path[_skipPrefix..];
ret = ImGui.Selectable(p, selected) && !equals; ret = ImGui.Selectable(p, selected) && !equals;

View file

@ -56,25 +56,24 @@ public static class FeatureChecker
var size = new Vector2((width - (numButtons - 1) * innerSpacing.X) / numButtons, 0); var size = new Vector2((width - (numButtons - 1) * innerSpacing.X) / numButtons, 0);
var buttonColor = ImGui.GetColorU32(ImGuiCol.FrameBg); var buttonColor = ImGui.GetColorU32(ImGuiCol.FrameBg);
var textColor = ImGui.GetColorU32(ImGuiCol.TextDisabled); var textColor = ImGui.GetColorU32(ImGuiCol.TextDisabled);
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, innerSpacing) using (var style = ImStyleBorder.Frame.Push(ColorId.FolderLine.Value(), 0)
.Push(ImGuiStyleVar.FrameBorderSize, 0); .Push(ImStyleDouble.ItemSpacing, innerSpacing)
using (var color = ImRaii.PushColor(ImGuiCol.Border, ColorId.FolderLine.Value()) .Push(ImGuiColor.Button, buttonColor)
.Push(ImGuiCol.Button, buttonColor) .Push(ImGuiColor.Text, textColor))
.Push(ImGuiCol.Text, textColor))
{ {
foreach (var flag in SupportedFlags.Values) foreach (var flag in SupportedFlags.Values)
{ {
if (mod.RequiredFeatures.HasFlag(flag)) if (mod.RequiredFeatures.HasFlag(flag))
{ {
style.Push(ImGuiStyleVar.FrameBorderSize, ImUtf8.GlobalScale); style.Push(ImStyleSingle.FrameBorderThickness, ImUtf8.GlobalScale);
color.Pop(2); style.PopColor(2);
if (ImUtf8.Button($"{flag}", size)) if (Im.Button($"{flag}", size))
editor.ChangeRequiredFeatures(mod, mod.RequiredFeatures & ~flag); editor.ChangeRequiredFeatures(mod, mod.RequiredFeatures & ~flag);
color.Push(ImGuiCol.Button, buttonColor) style.Push(ImGuiColor.Button, buttonColor)
.Push(ImGuiCol.Text, textColor); .Push(ImGuiColor.Text, textColor);
style.Pop(); style.PopStyle();
} }
else if (ImUtf8.Button($"{flag}", size)) else if (Im.Button($"{flag}", size))
{ {
editor.ChangeRequiredFeatures(mod, mod.RequiredFeatures | flag); editor.ChangeRequiredFeatures(mod, mod.RequiredFeatures | flag);
} }

View file

@ -1,5 +1,6 @@
using Dalamud.Bindings.ImGui; using Dalamud.Bindings.ImGui;
using Dalamud.Interface; using Dalamud.Interface;
using Dalamud.Interface.Colors;
using Dalamud.Interface.ImGuiNotification; using Dalamud.Interface.ImGuiNotification;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using ImSharp; using ImSharp;
@ -297,7 +298,7 @@ public class FileEditor<T>(
{ {
var file = Items[globalIdx]; var file = Items[globalIdx];
bool ret; bool ret;
using (var c = ImRaii.PushColor(ImGuiCol.Text, ColorId.HandledConflictMod.Value(), file.IsOnPlayer)) using (var c = ImGuiColor.Text.Push(ColorId.HandledConflictMod.Value(), file.IsOnPlayer))
{ {
ret = ImGui.Selectable(file.RelPath.ToString(), selected); ret = ImGui.Selectable(file.RelPath.ToString(), selected);
} }
@ -313,7 +314,7 @@ public class FileEditor<T>(
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImUtf8.Text(gamePath.Path.Span); ImUtf8.Text(gamePath.Path.Span);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
using var color = ImRaii.PushColor(ImGuiCol.Text, ColorId.ItemId.Value()); using var color = ImGuiColor.Text.Push(ColorId.ItemId.Value());
ImGui.TextUnformatted(option.GetFullName()); ImGui.TextUnformatted(option.GetFullName());
} }
} }
@ -321,7 +322,7 @@ public class FileEditor<T>(
if (file.SubModUsage.Count > 0) if (file.SubModUsage.Count > 0)
{ {
Im.Line.Same(); Im.Line.Same();
using var color = ImRaii.PushColor(ImGuiCol.Text, ColorId.ItemId.Value()); using var color = ImGuiColor.Text.Push(ColorId.ItemId.Value());
ImGuiUtil.RightAlign(file.SubModUsage[0].Item2.Path.ToString()); ImGuiUtil.RightAlign(file.SubModUsage[0].Item2.Path.ToString());
} }

View file

@ -159,9 +159,9 @@ public class ItemSwapTab : IDisposable, ITab, IUiService
{ {
var (_, inMod, inCollection) = Items[globalIdx]; var (_, inMod, inCollection) = Items[globalIdx];
using var color = inMod using var color = inMod
? ImRaii.PushColor(ImGuiCol.Text, ColorId.ResTreeLocalPlayer.Value()) ? ImGuiColor.Text.Push(ColorId.ResTreeLocalPlayer.Value())
: inCollection.Count > 0 : inCollection.Count > 0
? ImRaii.PushColor(ImGuiCol.Text, ColorId.ResTreeNonNetworked.Value()) ? ImGuiColor.Text.Push(ColorId.ResTreeNonNetworked.Value())
: null; : null;
var ret = base.DrawSelectable(globalIdx, selected); var ret = base.DrawSelectable(globalIdx, selected);
if (inCollection.Count > 0) if (inCollection.Count > 0)

View file

@ -58,12 +58,12 @@ public class ModMergeTab(ModMerger modMerger) : Luna.IUiService
ImGui.SameLine(0, 0); ImGui.SameLine(0, 0);
if (size - textSize < minComboSize) if (size - textSize < minComboSize)
{ {
ImUtf8.Text("selected mod"u8, ColorId.FolderLine.Value()); Im.Text("selected mod"u8, ColorId.FolderLine.Value());
ImUtf8.HoverTooltip(modMerger.MergeFromMod!.Name); ImUtf8.HoverTooltip(modMerger.MergeFromMod!.Name);
} }
else else
{ {
ImUtf8.Text(modMerger.MergeFromMod!.Name, ColorId.FolderLine.Value()); Im.Text(modMerger.MergeFromMod!.Name, ColorId.FolderLine.Value());
} }
ImGui.SameLine(0, 0); ImGui.SameLine(0, 0);

View file

@ -1,317 +1,314 @@
using Dalamud.Interface; using Dalamud.Interface;
using Dalamud.Interface.Textures; using Dalamud.Interface.Textures;
using Dalamud.Interface.Textures.TextureWraps; using Dalamud.Interface.Textures.TextureWraps;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using Dalamud.Utility; using Dalamud.Utility;
using Dalamud.Bindings.ImGui; using ImSharp;
using ImSharp; using Lumina.Data.Files;
using Lumina.Data.Files; using Luna;
using OtterGui; using Penumbra.Communication;
using OtterGui.Raii; using Penumbra.GameData.Data;
using OtterGui.Text; using Penumbra.Services;
using Penumbra.Communication; using Penumbra.UI.Classes;
using Penumbra.GameData.Data; using MouseButton = Penumbra.Api.Enums.MouseButton;
using Penumbra.Services;
using Penumbra.UI.Classes; namespace Penumbra.UI;
using MouseButton = Penumbra.Api.Enums.MouseButton;
public class ChangedItemDrawer : IDisposable, IUiService
namespace Penumbra.UI; {
private static readonly string[] LowerNames = ChangedItemFlagExtensions.Order.Select(f => f.ToDescription().ToString().ToLowerInvariant()).ToArray();
public class ChangedItemDrawer : IDisposable, Luna.IUiService
{ public static bool TryParseIndex(ReadOnlySpan<char> input, out ChangedItemIconFlag slot)
private static readonly string[] LowerNames = ChangedItemFlagExtensions.Order.Select(f => f.ToDescription().ToLowerInvariant()).ToArray(); {
// Handle numeric cases before TryParse because numbers
public static bool TryParseIndex(ReadOnlySpan<char> input, out ChangedItemIconFlag slot) // are not logical otherwise.
{ if (int.TryParse(input, out var idx))
// Handle numeric cases before TryParse because numbers {
// are not logical otherwise. // We assume users will use 1-based index, but if they enter 0, just use the first.
if (int.TryParse(input, out var idx)) if (idx == 0)
{ {
// We assume users will use 1-based index, but if they enter 0, just use the first. slot = ChangedItemFlagExtensions.Order[0];
if (idx == 0) return true;
{ }
slot = ChangedItemFlagExtensions.Order[0];
return true; // Use 1-based index.
} --idx;
if (idx >= 0 && idx < ChangedItemFlagExtensions.Order.Count)
// Use 1-based index. {
--idx; slot = ChangedItemFlagExtensions.Order[idx];
if (idx >= 0 && idx < ChangedItemFlagExtensions.Order.Count) return true;
{ }
slot = ChangedItemFlagExtensions.Order[idx]; }
return true;
} slot = 0;
} return false;
}
slot = 0;
return false; public static bool TryParsePartial(string lowerInput, out ChangedItemIconFlag slot)
} {
if (TryParseIndex(lowerInput, out slot))
public static bool TryParsePartial(string lowerInput, out ChangedItemIconFlag slot) return true;
{
if (TryParseIndex(lowerInput, out slot)) slot = 0;
return true; foreach (var (item, flag) in LowerNames.Zip(ChangedItemFlagExtensions.Order))
{
slot = 0; if (item.Contains(lowerInput, StringComparison.Ordinal))
foreach (var (item, flag) in LowerNames.Zip(ChangedItemFlagExtensions.Order)) slot |= flag;
{ }
if (item.Contains(lowerInput, StringComparison.Ordinal))
slot |= flag; return slot != 0;
} }
return slot != 0;
} private readonly Configuration _config;
private readonly CommunicatorService _communicator;
private readonly Dictionary<ChangedItemIconFlag, IDalamudTextureWrap> _icons = new(16);
private readonly Configuration _config; private float _smallestIconWidth;
private readonly CommunicatorService _communicator;
private readonly Dictionary<ChangedItemIconFlag, IDalamudTextureWrap> _icons = new(16); public static Vector2 TypeFilterIconSize
private float _smallestIconWidth; => new(2 * Im.Style.TextHeight);
public static Vector2 TypeFilterIconSize public ChangedItemDrawer(IUiBuilder uiBuilder, IDataManager gameData, ITextureProvider textureProvider, CommunicatorService communicator,
=> new(2 * ImGui.GetTextLineHeight()); Configuration config)
{
public ChangedItemDrawer(IUiBuilder uiBuilder, IDataManager gameData, ITextureProvider textureProvider, CommunicatorService communicator, uiBuilder.RunWhenUiPrepared(() => CreateEquipSlotIcons(uiBuilder, gameData, textureProvider), true);
Configuration config) _communicator = communicator;
{ _config = config;
uiBuilder.RunWhenUiPrepared(() => CreateEquipSlotIcons(uiBuilder, gameData, textureProvider), true); }
_communicator = communicator;
_config = config; public void Dispose()
} {
foreach (var wrap in _icons.Values.Distinct())
public void Dispose() wrap.Dispose();
{ _icons.Clear();
foreach (var wrap in _icons.Values.Distinct()) }
wrap.Dispose();
_icons.Clear(); /// <summary> Check if a changed item should be drawn based on its category. </summary>
} public bool FilterChangedItem(string name, IIdentifiedObjectData data, string filter)
=> (_config.Ephemeral.ChangedItemFilter == ChangedItemFlagExtensions.AllFlags
/// <summary> Check if a changed item should be drawn based on its category. </summary> || _config.Ephemeral.ChangedItemFilter.HasFlag(data.GetIcon().ToFlag()))
public bool FilterChangedItem(string name, IIdentifiedObjectData data, string filter) && (filter.Length is 0 || !data.IsFilteredOut(name, filter));
=> (_config.Ephemeral.ChangedItemFilter == ChangedItemFlagExtensions.AllFlags
|| _config.Ephemeral.ChangedItemFilter.HasFlag(data.GetIcon().ToFlag())) /// <summary> Draw the icon corresponding to the category of a changed item. </summary>
&& (filter.Length is 0 || !data.IsFilteredOut(name, filter)); public void DrawCategoryIcon(IIdentifiedObjectData data, float height)
=> DrawCategoryIcon(data.GetIcon().ToFlag(), height);
/// <summary> Draw the icon corresponding to the category of a changed item. </summary>
public void DrawCategoryIcon(IIdentifiedObjectData data, float height) public void DrawCategoryIcon(ChangedItemIconFlag iconFlagType)
=> DrawCategoryIcon(data.GetIcon().ToFlag(), height); => DrawCategoryIcon(iconFlagType, Im.Style.FrameHeight);
public void DrawCategoryIcon(ChangedItemIconFlag iconFlagType) public void DrawCategoryIcon(ChangedItemIconFlag iconFlagType, float height)
=> DrawCategoryIcon(iconFlagType, ImGui.GetFrameHeight()); {
if (!_icons.TryGetValue(iconFlagType, out var icon))
public void DrawCategoryIcon(ChangedItemIconFlag iconFlagType, float height) {
{ Im.Dummy(0, height);
if (!_icons.TryGetValue(iconFlagType, out var icon)) return;
{ }
ImGui.Dummy(new Vector2(height));
return; Im.Image.Draw(icon.Id(), new Vector2(height));
} if (Im.Item.Hovered())
{
ImGui.Image(icon.Handle, new Vector2(height)); using var tt = Im.Tooltip.Begin();
if (ImGui.IsItemHovered()) Im.Image.Draw(icon.Id(), new Vector2(_smallestIconWidth));
{ Im.Line.Same();
using var tt = ImRaii.Tooltip(); ImEx.TextFramed(iconFlagType.ToDescription(), new Vector2(0, _smallestIconWidth), 0);
ImGui.Image(icon.Handle, new Vector2(_smallestIconWidth)); }
Im.Line.Same(); }
ImGuiUtil.DrawTextButton(iconFlagType.ToDescription(), new Vector2(0, _smallestIconWidth), 0);
} public void ChangedItemHandling(IIdentifiedObjectData data, bool leftClicked)
} {
var ret = leftClicked ? MouseButton.Left : MouseButton.None;
public void ChangedItemHandling(IIdentifiedObjectData data, bool leftClicked) ret = Im.Item.Clicked(ImSharp.MouseButton.Right) ? MouseButton.Right : ret;
{ ret = Im.Item.Clicked(ImSharp.MouseButton.Middle) ? MouseButton.Middle : ret;
var ret = leftClicked ? MouseButton.Left : MouseButton.None; if (ret != MouseButton.None)
ret = ImGui.IsItemClicked(ImGuiMouseButton.Right) ? MouseButton.Right : ret; _communicator.ChangedItemClick.Invoke(new ChangedItemClick.Arguments(ret, data));
ret = ImGui.IsItemClicked(ImGuiMouseButton.Middle) ? MouseButton.Middle : ret; if (!Im.Item.Hovered())
if (ret != MouseButton.None) return;
_communicator.ChangedItemClick.Invoke(new ChangedItemClick.Arguments(ret, data));
if (!ImGui.IsItemHovered()) using var tt = Im.Tooltip.Begin();
return; if (data.Count == 1)
Im.Text("This item is changed through a single effective change.\n"u8);
using var tt = ImUtf8.Tooltip(); else
if (data.Count == 1) Im.Text($"This item is changed through {data.Count} distinct effective changes.\n");
ImUtf8.Text("This item is changed through a single effective change.\n"); Im.Cursor.Y += 3 * Im.Style.GlobalScale;
else Im.Separator();
ImUtf8.Text($"This item is changed through {data.Count} distinct effective changes.\n"); Im.Cursor.Y += 3 * Im.Style.GlobalScale;
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 3 * ImUtf8.GlobalScale); _communicator.ChangedItemHover.Invoke(new ChangedItemHover.Arguments(data));
ImGui.Separator(); }
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 3 * ImUtf8.GlobalScale);
_communicator.ChangedItemHover.Invoke(new ChangedItemHover.Arguments(data)); /// <summary> Draw the model information, right-justified. </summary>
} public static void DrawModelData(IIdentifiedObjectData data, float height)
{
/// <summary> Draw the model information, right-justified. </summary> var additionalData = data.AdditionalData;
public static void DrawModelData(IIdentifiedObjectData data, float height) if (additionalData.Length == 0)
{ return;
var additionalData = data.AdditionalData;
if (additionalData.Length == 0) Im.Line.Same();
return; using var color = ImGuiColor.Text.Push(ColorId.ItemId.Value());
Im.Cursor.Y += height - Im.Style.TextHeight / 2;
Im.Line.Same(); ImEx.TextRightAligned(additionalData, Im.Style.ItemInnerSpacing.X);
using var color = ImRaii.PushColor(ImGuiCol.Text, ColorId.ItemId.Value()); }
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + (height - ImGui.GetTextLineHeight()) / 2);
ImUtf8.TextRightAligned(additionalData, ImGui.GetStyle().ItemInnerSpacing.X); /// <summary> Draw the model information, right-justified. </summary>
} public static void DrawModelData(ReadOnlySpan<byte> text, float height)
{
/// <summary> Draw the model information, right-justified. </summary> if (text.Length == 0)
public static void DrawModelData(ReadOnlySpan<byte> text, float height) return;
{
if (text.Length == 0) Im.Line.Same();
return; using var color = ImGuiColor.Text.Push(ColorId.ItemId.Value());
Im.Cursor.Y += height - Im.Style.TextHeight / 2;
Im.Line.Same(); ImEx.TextRightAligned(text, Im.Style.ItemInnerSpacing.X);
using var color = ImRaii.PushColor(ImGuiCol.Text, ColorId.ItemId.Value()); }
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + (height - ImGui.GetTextLineHeight()) / 2);
ImUtf8.TextRightAligned(text, ImGui.GetStyle().ItemInnerSpacing.X); /// <summary> Draw a header line with the different icon types to filter them. </summary>
} public void DrawTypeFilter()
{
/// <summary> Draw a header line with the different icon types to filter them. </summary> if (_config.HideChangedItemFilters)
public void DrawTypeFilter() return;
{
if (_config.HideChangedItemFilters) var typeFilter = _config.Ephemeral.ChangedItemFilter;
return; if (DrawTypeFilter(ref typeFilter))
{
var typeFilter = _config.Ephemeral.ChangedItemFilter; _config.Ephemeral.ChangedItemFilter = typeFilter;
if (DrawTypeFilter(ref typeFilter)) _config.Ephemeral.Save();
{ }
_config.Ephemeral.ChangedItemFilter = typeFilter; }
_config.Ephemeral.Save();
} /// <summary> Draw a header line with the different icon types to filter them. </summary>
} public bool DrawTypeFilter(ref ChangedItemIconFlag typeFilter)
{
/// <summary> Draw a header line with the different icon types to filter them. </summary> var ret = false;
public bool DrawTypeFilter(ref ChangedItemIconFlag typeFilter) using var _ = Im.Id.Push("ChangedItemIconFilter"u8);
{ var size = TypeFilterIconSize;
var ret = false; using var style = ImStyleDouble.ItemSpacing.Push(Vector2.Zero);
using var _ = ImRaii.PushId("ChangedItemIconFilter");
var size = TypeFilterIconSize;
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero); foreach (var iconType in ChangedItemFlagExtensions.Order)
{
ret |= DrawIcon(iconType, ref typeFilter);
foreach (var iconType in ChangedItemFlagExtensions.Order) Im.Line.Same();
{ }
ret |= DrawIcon(iconType, ref typeFilter);
Im.Line.Same(); Im.Cursor.X = Im.ContentRegion.Maximum.X - size.X;
} Im.Image.Draw(_icons[ChangedItemFlagExtensions.AllFlags].Id(), size, Vector2.Zero, Vector2.One,
typeFilter switch
ImGui.SetCursorPosX(ImGui.GetContentRegionMax().X - size.X); {
ImGui.Image(_icons[ChangedItemFlagExtensions.AllFlags].Handle, size, Vector2.Zero, Vector2.One, 0 => new Vector4(0.6f, 0.3f, 0.3f, 1f),
typeFilter switch ChangedItemFlagExtensions.AllFlags => new Vector4(0.75f, 0.75f, 0.75f, 1f),
{ _ => new Vector4(0.5f, 0.5f, 1f, 1f),
0 => new Vector4(0.6f, 0.3f, 0.3f, 1f), });
ChangedItemFlagExtensions.AllFlags => new Vector4(0.75f, 0.75f, 0.75f, 1f), if (Im.Item.Clicked())
_ => new Vector4(0.5f, 0.5f, 1f, 1f), {
}); typeFilter = typeFilter == ChangedItemFlagExtensions.AllFlags ? 0 : ChangedItemFlagExtensions.AllFlags;
if (ImGui.IsItemClicked()) ret = true;
{ }
typeFilter = typeFilter == ChangedItemFlagExtensions.AllFlags ? 0 : ChangedItemFlagExtensions.AllFlags;
ret = true; return ret;
}
bool DrawIcon(ChangedItemIconFlag type, ref ChangedItemIconFlag typeFilter)
return ret; {
var localRet = false;
bool DrawIcon(ChangedItemIconFlag type, ref ChangedItemIconFlag typeFilter) var icon = _icons[type];
{ var flag = typeFilter.HasFlag(type);
var localRet = false; Im.Image.Draw(icon.Id(), size, Vector2.Zero, Vector2.One, flag ? Vector4.One : new Vector4(0.6f, 0.3f, 0.3f, 1f));
var icon = _icons[type]; if (Im.Item.Clicked())
var flag = typeFilter.HasFlag(type); {
ImGui.Image(icon.Handle, size, Vector2.Zero, Vector2.One, flag ? Vector4.One : new Vector4(0.6f, 0.3f, 0.3f, 1f)); typeFilter = flag ? typeFilter & ~type : typeFilter | type;
if (ImGui.IsItemClicked(ImGuiMouseButton.Left)) localRet = true;
{ }
typeFilter = flag ? typeFilter & ~type : typeFilter | type;
localRet = true; using var popup = Im.Popup.BeginContextItem($"{type}");
} if (popup)
if (Im.Menu.Item("Enable Only This"u8))
using var popup = ImRaii.ContextPopupItem(type.ToString()); {
if (popup) typeFilter = type;
if (ImGui.MenuItem("Enable Only This")) localRet = true;
{ Im.Popup.CloseCurrent();
typeFilter = type; }
localRet = true;
ImGui.CloseCurrentPopup(); if (Im.Item.Hovered())
} {
using var tt = Im.Tooltip.Begin();
if (ImGui.IsItemHovered()) Im.Image.Draw(icon.Id(), new Vector2(_smallestIconWidth));
{ Im.Line.Same();
using var tt = ImRaii.Tooltip(); ImEx.TextFramed(type.ToDescription(), new Vector2(0, _smallestIconWidth), 0);
ImGui.Image(icon.Handle, new Vector2(_smallestIconWidth)); }
Im.Line.Same();
ImGuiUtil.DrawTextButton(type.ToDescription(), new Vector2(0, _smallestIconWidth), 0); return localRet;
} }
}
return localRet;
} /// <summary> Initialize the icons. </summary>
} private bool CreateEquipSlotIcons(IUiBuilder uiBuilder, IDataManager gameData, ITextureProvider textureProvider)
{
/// <summary> Initialize the icons. </summary> using var equipTypeIcons = uiBuilder.LoadUld("ui/uld/ArmouryBoard.uld");
private bool CreateEquipSlotIcons(IUiBuilder uiBuilder, IDataManager gameData, ITextureProvider textureProvider)
{ if (!equipTypeIcons.Valid)
using var equipTypeIcons = uiBuilder.LoadUld("ui/uld/ArmouryBoard.uld"); return false;
if (!equipTypeIcons.Valid) void Add(ChangedItemIconFlag icon, IDalamudTextureWrap? tex)
return false; {
if (tex != null)
void Add(ChangedItemIconFlag icon, IDalamudTextureWrap? tex) _icons.Add(icon, tex);
{ }
if (tex != null)
_icons.Add(icon, tex); Add(ChangedItemIconFlag.Mainhand, equipTypeIcons.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", 0));
} Add(ChangedItemIconFlag.Head, equipTypeIcons.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", 1));
Add(ChangedItemIconFlag.Body, equipTypeIcons.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", 2));
Add(ChangedItemIconFlag.Mainhand, equipTypeIcons.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", 0)); Add(ChangedItemIconFlag.Hands, equipTypeIcons.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", 3));
Add(ChangedItemIconFlag.Head, equipTypeIcons.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", 1)); Add(ChangedItemIconFlag.Legs, equipTypeIcons.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", 5));
Add(ChangedItemIconFlag.Body, equipTypeIcons.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", 2)); Add(ChangedItemIconFlag.Feet, equipTypeIcons.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", 6));
Add(ChangedItemIconFlag.Hands, equipTypeIcons.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", 3)); Add(ChangedItemIconFlag.Offhand, equipTypeIcons.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", 7));
Add(ChangedItemIconFlag.Legs, equipTypeIcons.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", 5)); Add(ChangedItemIconFlag.Ears, equipTypeIcons.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", 8));
Add(ChangedItemIconFlag.Feet, equipTypeIcons.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", 6)); Add(ChangedItemIconFlag.Neck, equipTypeIcons.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", 9));
Add(ChangedItemIconFlag.Offhand, equipTypeIcons.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", 7)); Add(ChangedItemIconFlag.Wrists, equipTypeIcons.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", 10));
Add(ChangedItemIconFlag.Ears, equipTypeIcons.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", 8)); Add(ChangedItemIconFlag.Finger, equipTypeIcons.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", 11));
Add(ChangedItemIconFlag.Neck, equipTypeIcons.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", 9)); Add(ChangedItemIconFlag.Monster, textureProvider.CreateFromTexFile(gameData.GetFile<TexFile>("ui/icon/062000/062044_hr1.tex")!));
Add(ChangedItemIconFlag.Wrists, equipTypeIcons.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", 10)); Add(ChangedItemIconFlag.Demihuman, textureProvider.CreateFromTexFile(gameData.GetFile<TexFile>("ui/icon/062000/062043_hr1.tex")!));
Add(ChangedItemIconFlag.Finger, equipTypeIcons.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", 11)); Add(ChangedItemIconFlag.Customization, textureProvider.CreateFromTexFile(gameData.GetFile<TexFile>("ui/icon/062000/062045_hr1.tex")!));
Add(ChangedItemIconFlag.Monster, textureProvider.CreateFromTexFile(gameData.GetFile<TexFile>("ui/icon/062000/062044_hr1.tex")!)); Add(ChangedItemIconFlag.Action, textureProvider.CreateFromTexFile(gameData.GetFile<TexFile>("ui/icon/062000/062001_hr1.tex")!));
Add(ChangedItemIconFlag.Demihuman, textureProvider.CreateFromTexFile(gameData.GetFile<TexFile>("ui/icon/062000/062043_hr1.tex")!)); Add(ChangedItemIconFlag.Emote, LoadEmoteTexture(gameData, textureProvider));
Add(ChangedItemIconFlag.Customization, textureProvider.CreateFromTexFile(gameData.GetFile<TexFile>("ui/icon/062000/062045_hr1.tex")!)); Add(ChangedItemIconFlag.Unknown, LoadUnknownTexture(gameData, textureProvider));
Add(ChangedItemIconFlag.Action, textureProvider.CreateFromTexFile(gameData.GetFile<TexFile>("ui/icon/062000/062001_hr1.tex")!)); Add(ChangedItemFlagExtensions.AllFlags, textureProvider.CreateFromTexFile(gameData.GetFile<TexFile>("ui/icon/114000/114052_hr1.tex")!));
Add(ChangedItemIconFlag.Emote, LoadEmoteTexture(gameData, textureProvider));
Add(ChangedItemIconFlag.Unknown, LoadUnknownTexture(gameData, textureProvider)); _smallestIconWidth = _icons.Values.Min(i => i.Width);
Add(ChangedItemFlagExtensions.AllFlags, textureProvider.CreateFromTexFile(gameData.GetFile<TexFile>("ui/icon/114000/114052_hr1.tex")!));
return true;
_smallestIconWidth = _icons.Values.Min(i => i.Width); }
return true; private static IDalamudTextureWrap? LoadUnknownTexture(IDataManager gameData, ITextureProvider textureProvider)
} {
var unk = gameData.GetFile<TexFile>("ui/uld/levelup2_hr1.tex");
private static IDalamudTextureWrap? LoadUnknownTexture(IDataManager gameData, ITextureProvider textureProvider) if (unk == null)
{ return null;
var unk = gameData.GetFile<TexFile>("ui/uld/levelup2_hr1.tex");
if (unk == null) var image = unk.GetRgbaImageData();
return null; var bytes = new byte[unk.Header.Height * unk.Header.Height * 4];
var diff = 2 * (unk.Header.Height - unk.Header.Width);
var image = unk.GetRgbaImageData(); for (var y = 0; y < unk.Header.Height; ++y)
var bytes = new byte[unk.Header.Height * unk.Header.Height * 4]; image.AsSpan(4 * y * unk.Header.Width, 4 * unk.Header.Width).CopyTo(bytes.AsSpan(4 * y * unk.Header.Height + diff));
var diff = 2 * (unk.Header.Height - unk.Header.Width);
for (var y = 0; y < unk.Header.Height; ++y) return textureProvider.CreateFromRaw(RawImageSpecification.Rgba32(unk.Header.Height, unk.Header.Height), bytes, "Penumbra.UnkItemIcon");
image.AsSpan(4 * y * unk.Header.Width, 4 * unk.Header.Width).CopyTo(bytes.AsSpan(4 * y * unk.Header.Height + diff)); }
return textureProvider.CreateFromRaw(RawImageSpecification.Rgba32(unk.Header.Height, unk.Header.Height), bytes, "Penumbra.UnkItemIcon"); private static unsafe IDalamudTextureWrap? LoadEmoteTexture(IDataManager gameData, ITextureProvider textureProvider)
} {
var emote = gameData.GetFile<TexFile>("ui/icon/000000/000019_hr1.tex");
private static unsafe IDalamudTextureWrap? LoadEmoteTexture(IDataManager gameData, ITextureProvider textureProvider) if (emote == null)
{ return null;
var emote = gameData.GetFile<TexFile>("ui/icon/000000/000019_hr1.tex");
if (emote == null) var image2 = emote.GetRgbaImageData();
return null; fixed (byte* ptr = image2)
{
var image2 = emote.GetRgbaImageData(); var color = (uint*)ptr;
fixed (byte* ptr = image2) for (var i = 0; i < image2.Length / 4; ++i)
{ {
var color = (uint*)ptr; if (color[i] == 0xFF000000)
for (var i = 0; i < image2.Length / 4; ++i) image2[i * 4 + 3] = 0;
{ }
if (color[i] == 0xFF000000) }
image2[i * 4 + 3] = 0;
} return textureProvider.CreateFromRaw(RawImageSpecification.Rgba32(emote.Header.Width, emote.Header.Height), image2,
} "Penumbra.EmoteItemIcon");
}
return textureProvider.CreateFromRaw(RawImageSpecification.Rgba32(emote.Header.Width, emote.Header.Height), image2, }
"Penumbra.EmoteItemIcon");
}
}

View file

@ -52,26 +52,26 @@ public static class ChangedItemFlagExtensions
public static readonly int NumCategories = Order.Count; public static readonly int NumCategories = Order.Count;
public const ChangedItemIconFlag DefaultFlags = AllFlags & ~ChangedItemIconFlag.Offhand; public const ChangedItemIconFlag DefaultFlags = AllFlags & ~ChangedItemIconFlag.Offhand;
public static string ToDescription(this ChangedItemIconFlag iconFlag) public static ReadOnlySpan<byte> ToDescription(this ChangedItemIconFlag iconFlag)
=> iconFlag switch => iconFlag switch
{ {
ChangedItemIconFlag.Head => EquipSlot.Head.ToName(), ChangedItemIconFlag.Head => EquipSlot.Head.ToNameU8(),
ChangedItemIconFlag.Body => EquipSlot.Body.ToName(), ChangedItemIconFlag.Body => EquipSlot.Body.ToNameU8(),
ChangedItemIconFlag.Hands => EquipSlot.Hands.ToName(), ChangedItemIconFlag.Hands => EquipSlot.Hands.ToNameU8(),
ChangedItemIconFlag.Legs => EquipSlot.Legs.ToName(), ChangedItemIconFlag.Legs => EquipSlot.Legs.ToNameU8(),
ChangedItemIconFlag.Feet => EquipSlot.Feet.ToName(), ChangedItemIconFlag.Feet => EquipSlot.Feet.ToNameU8(),
ChangedItemIconFlag.Ears => EquipSlot.Ears.ToName(), ChangedItemIconFlag.Ears => EquipSlot.Ears.ToNameU8(),
ChangedItemIconFlag.Neck => EquipSlot.Neck.ToName(), ChangedItemIconFlag.Neck => EquipSlot.Neck.ToNameU8(),
ChangedItemIconFlag.Wrists => EquipSlot.Wrists.ToName(), ChangedItemIconFlag.Wrists => EquipSlot.Wrists.ToNameU8(),
ChangedItemIconFlag.Finger => "Ring", ChangedItemIconFlag.Finger => "Ring"u8,
ChangedItemIconFlag.Monster => "Monster", ChangedItemIconFlag.Monster => "Monster"u8,
ChangedItemIconFlag.Demihuman => "Demi-Human", ChangedItemIconFlag.Demihuman => "Demi-Human"u8,
ChangedItemIconFlag.Customization => "Customization", ChangedItemIconFlag.Customization => "Customization"u8,
ChangedItemIconFlag.Action => "Action", ChangedItemIconFlag.Action => "Action"u8,
ChangedItemIconFlag.Emote => "Emote", ChangedItemIconFlag.Emote => "Emote"u8,
ChangedItemIconFlag.Mainhand => "Weapon (Mainhand)", ChangedItemIconFlag.Mainhand => "Weapon (Mainhand)"u8,
ChangedItemIconFlag.Offhand => "Weapon (Offhand)", ChangedItemIconFlag.Offhand => "Weapon (Offhand)"u8,
_ => "Other", _ => "Other"u8,
}; };
public static ChangedItemIcon ToApiIcon(this ChangedItemIconFlag iconFlag) public static ChangedItemIcon ToApiIcon(this ChangedItemIconFlag iconFlag)

View file

@ -1,160 +1,164 @@
using Dalamud.Interface; using Dalamud.Interface;
using Dalamud.Bindings.ImGui; using ImSharp;
using ImSharp; using Luna;
using OtterGui; using Penumbra.Collections;
using OtterGui.Raii; using Penumbra.Collections.Manager;
using OtterGui.Text; using Penumbra.Interop.PathResolving;
using Penumbra.Collections; using Penumbra.Mods;
using Penumbra.Collections.Manager; using Penumbra.UI.CollectionTab;
using Penumbra.Interop.PathResolving; using CollectionTuple =
using Penumbra.Mods; ImSharp.RefTuple<Penumbra.Collections.ModCollection?, ImSharp.Utf8StringHandler<ImSharp.LabelStringHandlerBuffer>,
using Penumbra.UI.CollectionTab; ImSharp.Utf8StringHandler<ImSharp.TextStringHandlerBuffer>, bool>;
namespace Penumbra.UI.Classes; namespace Penumbra.UI.Classes;
public class CollectionSelectHeader( public class CollectionSelectHeader(
CollectionManager collectionManager, CollectionManager collectionManager,
TutorialService tutorial, TutorialService tutorial,
ModSelection selection, ModSelection selection,
CollectionResolver resolver, CollectionResolver resolver,
Configuration config, Configuration config,
CollectionCombo combo) CollectionCombo combo)
: Luna.IUiService : IUiService
{ {
private readonly ActiveCollections _activeCollections = collectionManager.Active; private readonly ActiveCollections _activeCollections = collectionManager.Active;
private static readonly AwesomeIcon Icon = FontAwesomeIcon.Stopwatch;
/// <summary> Draw the header line that can quick switch between collections. </summary>
public void Draw(bool spacing) /// <summary> Draw the header line that can quick switch between collections. </summary>
{ public void Draw(bool spacing)
using var style = ImRaii.PushStyle(ImGuiStyleVar.FrameRounding, 0) {
.Push(ImGuiStyleVar.ItemSpacing, new Vector2(0, spacing ? ImGui.GetStyle().ItemSpacing.Y : 0)); using var style = ImStyleSingle.FrameRounding.Push(0)
DrawTemporaryCheckbox(); .Push(ImStyleDouble.ItemSpacing, new Vector2(0, spacing ? Im.Style.ItemSpacing.Y : 0));
Im.Line.Same(); DrawTemporaryCheckbox();
var comboWidth = ImGui.GetContentRegionAvail().X / 4f; Im.Line.Same();
var buttonSize = new Vector2(comboWidth * 3f / 4f, 0f); var comboWidth = Im.ContentRegion.Available.X / 4f;
using (var _ = ImRaii.Group()) var buttonSize = new Vector2(comboWidth * 3f / 4f, 0f);
{ using (var _ = Im.Group())
DrawCollectionButton(buttonSize, GetDefaultCollectionInfo(), 1); {
DrawCollectionButton(buttonSize, GetInterfaceCollectionInfo(), 2); DrawCollectionButton(buttonSize, GetDefaultCollectionInfo(), 1);
DrawCollectionButton(buttonSize, GetPlayerCollectionInfo(), 3); DrawCollectionButton(buttonSize, GetInterfaceCollectionInfo(), 2);
DrawCollectionButton(buttonSize, GetInheritedCollectionInfo(), 4); DrawCollectionButton(buttonSize, GetPlayerCollectionInfo(), 3);
DrawCollectionButton(buttonSize, GetInheritedCollectionInfo(), 4);
combo.Draw("##collectionSelector"u8, comboWidth, ColorId.SelectedCollection.Value());
} combo.Draw("##collectionSelector"u8, comboWidth, ColorId.SelectedCollection.Value());
}
tutorial.OpenTutorial(BasicTutorialSteps.CollectionSelectors);
tutorial.OpenTutorial(BasicTutorialSteps.CollectionSelectors);
if (!_activeCollections.CurrentCollectionInUse)
ImGuiUtil.DrawTextButton("The currently selected collection is not used in any way.", -Vector2.UnitX, Colors.PressEnterWarningBg); if (!_activeCollections.CurrentCollectionInUse)
} ImEx.TextFramed("The currently selected collection is not used in any way."u8, -Vector2.UnitX, Colors.PressEnterWarningBg);
}
private void DrawTemporaryCheckbox()
{ private void DrawTemporaryCheckbox()
var hold = config.IncognitoModifier.IsActive(); {
using (ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, ImUtf8.GlobalScale)) var hold = config.IncognitoModifier.IsActive();
{ var tint = config.DefaultTemporaryMode
var tint = config.DefaultTemporaryMode ? Rgba32.TintColor(Im.Style[ImGuiColor.Text], ColorId.TemporaryModSettingsTint.Value().ToVector())
? ImGuiCol.Text.Tinted(ColorId.TemporaryModSettingsTint) : Im.Style[ImGuiColor.TextDisabled];
: ImGui.GetColorU32(ImGuiCol.TextDisabled); var frameBg = Im.Style[ImGuiColor.FrameBackground];
using var color = ImRaii.PushColor(ImGuiCol.ButtonHovered, ImGui.GetColorU32(ImGuiCol.FrameBg), !hold)
.Push(ImGuiCol.ButtonActive, ImGui.GetColorU32(ImGuiCol.FrameBg), !hold) using (ImStyleBorder.Frame.Push(tint)
.Push(ImGuiCol.Border, tint, config.DefaultTemporaryMode); .Push(ImGuiColor.ButtonHovered, frameBg, !hold)
if (ImUtf8.IconButton(FontAwesomeIcon.Stopwatch, ""u8, default, false, tint, ImGui.GetColorU32(ImGuiCol.FrameBg)) && hold) .Push(ImGuiColor.ButtonActive, frameBg, !hold))
{ {
config.DefaultTemporaryMode = !config.DefaultTemporaryMode; if (ImEx.Icon.Button(Icon, buttonColor: frameBg, textColor: tint) && hold)
config.Save(); {
} config.DefaultTemporaryMode = !config.DefaultTemporaryMode;
} config.Save();
}
ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, }
"Toggle the temporary settings mode, where all changes you do create temporary settings first and need to be made permanent if desired."u8);
if (!hold) Im.Tooltip.OnHover(HoveredFlags.AllowWhenDisabled,
ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"\nHold {config.IncognitoModifier} while clicking to toggle."); "Toggle the temporary settings mode, where all changes you do create temporary settings first and need to be made permanent if desired."u8);
} if (!hold)
Im.Tooltip.OnHover(HoveredFlags.AllowWhenDisabled, $"\nHold {config.IncognitoModifier} while clicking to toggle.");
private enum CollectionState }
{
Empty, private enum CollectionState
Selected, {
Unavailable, Empty,
Available, Selected,
} Unavailable,
Available,
private CollectionState CheckCollection(ModCollection? collection, bool inheritance = false) }
{
if (collection == null) private CollectionState CheckCollection(ModCollection? collection, bool inheritance = false)
return CollectionState.Unavailable; {
if (collection == ModCollection.Empty) if (collection is null)
return CollectionState.Empty; return CollectionState.Unavailable;
if (collection == _activeCollections.Current) if (collection == ModCollection.Empty)
return inheritance ? CollectionState.Unavailable : CollectionState.Selected; return CollectionState.Empty;
if (collection == _activeCollections.Current)
return CollectionState.Available; return inheritance ? CollectionState.Unavailable : CollectionState.Selected;
}
return CollectionState.Available;
private (ModCollection?, string, string, bool) GetDefaultCollectionInfo() }
{
var collection = _activeCollections.Default; private CollectionTuple GetDefaultCollectionInfo()
return CheckCollection(collection) switch {
{ var collection = _activeCollections.Default;
CollectionState.Empty => (collection, "None", "The base collection is configured to use no mods.", true), return CheckCollection(collection) switch
CollectionState.Selected => (collection, collection.Identity.Name, {
"The configured base collection is already selected as the current collection.", true), CollectionState.Empty => new CollectionTuple(collection, "None"u8, "The base collection is configured to use no mods."u8, true),
CollectionState.Available => (collection, collection.Identity.Name, CollectionState.Selected => new CollectionTuple(collection, collection.Identity.Name,
$"Select the configured base collection {collection.Identity.Name} as the current collection.", false), "The configured base collection is already selected as the current collection."u8, true),
_ => throw new Exception("Can not happen."), CollectionState.Available => new CollectionTuple(collection, collection.Identity.Name,
}; $"Select the configured base collection {collection.Identity.Name} as the current collection.", false),
} _ => throw new Exception("Can not happen."),
};
private (ModCollection?, string, string, bool) GetPlayerCollectionInfo() }
{
var collection = resolver.PlayerCollection(); private CollectionTuple GetPlayerCollectionInfo()
return CheckCollection(collection) switch {
{ var collection = resolver.PlayerCollection();
CollectionState.Empty => (collection, "None", "The loaded player character is configured to use no mods.", true), return CheckCollection(collection) switch
CollectionState.Selected => (collection, collection.Identity.Name, {
"The collection configured to apply to the loaded player character is already selected as the current collection.", true), CollectionState.Empty => new CollectionTuple(collection, "None"u8, "The loaded player character is configured to use no mods."u8,
CollectionState.Available => (collection, collection.Identity.Name, true),
$"Select the collection {collection.Identity.Name} that applies to the loaded player character as the current collection.", CollectionState.Selected => new CollectionTuple(collection, collection.Identity.Name,
false), "The collection configured to apply to the loaded player character is already selected as the current collection."u8, true),
_ => throw new Exception("Can not happen."), CollectionState.Available => new CollectionTuple(collection, collection.Identity.Name,
}; $"Select the collection {collection.Identity.Name} that applies to the loaded player character as the current collection.",
} false),
_ => throw new Exception("Can not happen."),
private (ModCollection?, string, string, bool) GetInterfaceCollectionInfo() };
{ }
var collection = _activeCollections.Interface;
return CheckCollection(collection) switch private CollectionTuple GetInterfaceCollectionInfo()
{ {
CollectionState.Empty => (collection, "None", "The interface collection is configured to use no mods.", true), var collection = _activeCollections.Interface;
CollectionState.Selected => (collection, collection.Identity.Name, return CheckCollection(collection) switch
"The configured interface collection is already selected as the current collection.", true), {
CollectionState.Available => (collection, collection.Identity.Name, CollectionState.Empty => new CollectionTuple(collection, "None"u8, "The interface collection is configured to use no mods."u8,
$"Select the configured interface collection {collection.Identity.Name} as the current collection.", false), true),
_ => throw new Exception("Can not happen."), CollectionState.Selected => new CollectionTuple(collection, collection.Identity.Name,
}; "The configured interface collection is already selected as the current collection."u8, true),
} CollectionState.Available => new CollectionTuple(collection, collection.Identity.Name,
$"Select the configured interface collection {collection.Identity.Name} as the current collection.", false),
private (ModCollection?, string, string, bool) GetInheritedCollectionInfo() _ => throw new Exception("Can not happen."),
{ };
var collection = selection.Mod == null ? null : selection.Collection; }
return CheckCollection(collection, true) switch
{ private CollectionTuple GetInheritedCollectionInfo()
CollectionState.Unavailable => (null, "Not Inherited", {
"The settings of the selected mod are not inherited from another collection.", true), var collection = selection.Mod is null ? null : selection.Collection;
CollectionState.Available => (collection, collection!.Identity.Name, return CheckCollection(collection, true) switch
$"Select the collection {collection!.Identity.Name} from which the selected mod inherits its settings as the current collection.", {
false), CollectionState.Unavailable => new CollectionTuple(null, "Not Inherited"u8,
_ => throw new Exception("Can not happen."), "The settings of the selected mod are not inherited from another collection."u8, true),
}; CollectionState.Available => new CollectionTuple(collection, collection!.Identity.Name,
} $"Select the collection {collection.Identity.Name} from which the selected mod inherits its settings as the current collection.",
false),
private void DrawCollectionButton(Vector2 buttonWidth, (ModCollection?, string, string, bool) tuple, int id) _ => throw new Exception("Can not happen."),
{ };
var (collection, name, tooltip, disabled) = tuple; }
using var _ = ImRaii.PushId(id);
if (ImGuiUtil.DrawDisabledButton(name, buttonWidth, tooltip, disabled)) private void DrawCollectionButton(Vector2 buttonWidth, in CollectionTuple tuple, int id)
_activeCollections.SetCollection(collection!, CollectionType.Current); {
Im.Line.Same(); var (collection, name, tooltip, disabled) = tuple;
} using var _ = Im.Id.Push(id);
} if (ImEx.Button(name, buttonWidth, tooltip, disabled))
_activeCollections.SetCollection(collection!, CollectionType.Current);
Im.Line.Same();
}
}

View file

@ -1,4 +1,4 @@
using Dalamud.Bindings.ImGui; using ImSharp;
using OtterGui.Custom; using OtterGui.Custom;
namespace Penumbra.UI.Classes; namespace Penumbra.UI.Classes;
@ -53,30 +53,6 @@ public static class Colors
public const uint ReniColorHovered = CustomGui.ReniColorHovered; public const uint ReniColorHovered = CustomGui.ReniColorHovered;
public const uint ReniColorActive = CustomGui.ReniColorActive; public const uint ReniColorActive = CustomGui.ReniColorActive;
public static uint Tinted(this ColorId color, ColorId tint)
{
var tintValue = ImGui.ColorConvertU32ToFloat4(tint.Value());
var value = ImGui.ColorConvertU32ToFloat4(color.Value());
return ImGui.ColorConvertFloat4ToU32(TintColor(value, tintValue));
}
public static unsafe uint Tinted(this ImGuiCol color, ColorId tint)
{
var tintValue = ImGui.ColorConvertU32ToFloat4(tint.Value());
ref var value = ref *ImGui.GetStyleColorVec4(color);
return ImGui.ColorConvertFloat4ToU32(TintColor(value, tintValue));
}
private static unsafe Vector4 TintColor(in Vector4 color, in Vector4 tint)
{
var negAlpha = 1 - tint.W;
var newAlpha = negAlpha * color.W + tint.W;
var newR = (negAlpha * color.W * color.X + tint.W * tint.X) / newAlpha;
var newG = (negAlpha * color.W * color.Y + tint.W * tint.Y) / newAlpha;
var newB = (negAlpha * color.W * color.Z + tint.W * tint.Z) / newAlpha;
return new Vector4(newR, newG, newB, newAlpha);
}
public static (uint DefaultColor, string Name, string Description) Data(this ColorId color) public static (uint DefaultColor, string Name, string Description) Data(this ColorId color)
=> color switch => color switch
{ {
@ -116,10 +92,10 @@ public static class Colors
// @formatter:on // @formatter:on
}; };
private static IReadOnlyDictionary<ColorId, uint> _colors = new Dictionary<ColorId, uint>(); private static Dictionary<ColorId, uint> _colors = [];
/// <summary> Obtain the configured value for a color. </summary> /// <summary> Obtain the configured value for a color. </summary>
public static uint Value(this ColorId color) public static Rgba32 Value(this ColorId color)
=> _colors.TryGetValue(color, out var value) ? value : color.Data().DefaultColor; => _colors.TryGetValue(color, out var value) ? value : color.Data().DefaultColor;
/// <summary> Set the configurable colors dictionary to a value. </summary> /// <summary> Set the configurable colors dictionary to a value. </summary>

File diff suppressed because it is too large Load diff

View file

@ -1,157 +1,158 @@
using Dalamud.Interface; using Dalamud.Interface;
using Dalamud.Interface.ImGuiFileDialog; using Dalamud.Interface.ImGuiFileDialog;
using Dalamud.Utility; using Dalamud.Utility;
using Dalamud.Bindings.ImGui; using Dalamud.Bindings.ImGui;
using Luna; using Luna;
using OtterGui; using Penumbra.Communication;
using Penumbra.Communication; using Penumbra.Services;
using Penumbra.Services;
namespace Penumbra.UI;
namespace Penumbra.UI;
public class FileDialogService : IDisposable, IUiService
public class FileDialogService : IDisposable, IUiService {
{ private readonly CommunicatorService _communicator;
private readonly CommunicatorService _communicator; private readonly FileDialogManager _manager;
private readonly FileDialogManager _manager; private readonly ConcurrentDictionary<string, string> _startPaths = new();
private readonly ConcurrentDictionary<string, string> _startPaths = new(); private bool _isOpen;
private bool _isOpen;
private readonly Func<object?, object?>? _dialogGetter;
public FileDialogService(CommunicatorService communicator, Configuration config)
{ public FileDialogService(CommunicatorService communicator, Configuration config)
_communicator = communicator; {
_manager = SetupFileManager(config.ModDirectory); _communicator = communicator;
_communicator.ModDirectoryChanged.Subscribe(OnModDirectoryChange, ModDirectoryChanged.Priority.FileDialogService); _manager = SetupFileManager(config.ModDirectory);
} _communicator.ModDirectoryChanged.Subscribe(OnModDirectoryChange, ModDirectoryChanged.Priority.FileDialogService);
public void OpenFilePicker(string title, string filters, Action<bool, List<string>> callback, int selectionCountMax, string? startPath, var fieldType = _manager.GetType().GetField("dialog", BindingFlags.Instance | BindingFlags.NonPublic);
bool forceStartPath) _dialogGetter = fieldType is null ? null : fieldType.GetValue;
{ }
_isOpen = true;
_manager.OpenFileDialog(title, filters, CreateCallback(title, callback), selectionCountMax, public void OpenFilePicker(string title, string filters, Action<bool, List<string>> callback, int selectionCountMax, string? startPath,
GetStartPath(title, startPath, forceStartPath)); bool forceStartPath)
} {
_isOpen = true;
public void OpenFolderPicker(string title, Action<bool, string> callback, string? startPath, bool forceStartPath) _manager.OpenFileDialog(title, filters, CreateCallback(title, callback), selectionCountMax,
{ GetStartPath(title, startPath, forceStartPath));
_isOpen = true; }
_manager.OpenFolderDialog(title, CreateCallback(title, callback), GetStartPath(title, startPath, forceStartPath));
} public void OpenFolderPicker(string title, Action<bool, string> callback, string? startPath, bool forceStartPath)
{
public void OpenSavePicker(string title, string filters, string defaultFileName, string defaultExtension, Action<bool, string> callback, _isOpen = true;
string? startPath, _manager.OpenFolderDialog(title, CreateCallback(title, callback), GetStartPath(title, startPath, forceStartPath));
bool forceStartPath) }
{
_isOpen = true; public void OpenSavePicker(string title, string filters, string defaultFileName, string defaultExtension, Action<bool, string> callback,
_manager.SaveFileDialog(title, filters, defaultFileName, defaultExtension, CreateCallback(title, callback), string? startPath,
GetStartPath(title, startPath, forceStartPath)); bool forceStartPath)
} {
_isOpen = true;
public void Close() _manager.SaveFileDialog(title, filters, defaultFileName, defaultExtension, CreateCallback(title, callback),
{ GetStartPath(title, startPath, forceStartPath));
_isOpen = false; }
}
public void Close()
public void Reset() {
{ _isOpen = false;
_isOpen = false; }
_manager.Reset();
} public void Reset()
{
public void Draw() _isOpen = false;
{ _manager.Reset();
if (_isOpen) }
_manager.Draw();
} public void Draw()
{
public void Dispose() if (_isOpen)
{ _manager.Draw();
_startPaths.Clear(); }
_manager.Reset();
_communicator.ModDirectoryChanged.Unsubscribe(OnModDirectoryChange); public void Dispose()
} {
_startPaths.Clear();
private string? GetStartPath(string title, string? startPath, bool forceStartPath) _manager.Reset();
{ _communicator.ModDirectoryChanged.Unsubscribe(OnModDirectoryChange);
var path = !forceStartPath && _startPaths.TryGetValue(title, out var p) ? p : startPath; }
if (!path.IsNullOrEmpty() && !Directory.Exists(path))
path = null; private string? GetStartPath(string title, string? startPath, bool forceStartPath)
return path; {
} var path = !forceStartPath && _startPaths.TryGetValue(title, out var p) ? p : startPath;
if (!path.IsNullOrEmpty() && !Directory.Exists(path))
private Action<bool, List<string>> CreateCallback(string title, Action<bool, List<string>> callback) path = null;
{ return path;
return (valid, list) => }
{
_isOpen = false; private Action<bool, List<string>> CreateCallback(string title, Action<bool, List<string>> callback)
var loc = HandleRoot(GetCurrentLocation()); {
_startPaths[title] = loc; return (valid, list) =>
callback(valid, list.Select(HandleRoot).ToList()); {
}; _isOpen = false;
} var loc = HandleRoot(GetCurrentLocation());
_startPaths[title] = loc;
private Action<bool, string> CreateCallback(string title, Action<bool, string> callback) callback(valid, list.Select(HandleRoot).ToList());
{ };
return (valid, list) => }
{
_isOpen = false; private Action<bool, string> CreateCallback(string title, Action<bool, string> callback)
var loc = HandleRoot(GetCurrentLocation()); {
_startPaths[title] = loc; return (valid, list) =>
callback(valid, HandleRoot(list)); {
}; _isOpen = false;
} var loc = HandleRoot(GetCurrentLocation());
_startPaths[title] = loc;
private static string HandleRoot(string path) callback(valid, HandleRoot(list));
{ };
if (path is [_, ':']) }
return path + '\\';
private static string HandleRoot(string path)
return path; {
} if (path is [_, ':'])
return path + '\\';
// TODO: maybe change this from reflection when its public.
private string GetCurrentLocation() return path;
=> (_manager.GetType().GetField("dialog", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(_manager) as FileDialog) }
?.GetCurrentPath()
?? "."; private string GetCurrentLocation()
=> (_dialogGetter?.Invoke(_manager) as FileDialog)?.GetCurrentPath() ?? ".";
/// <summary> Set up the file selector with the right flags and custom side bar items. </summary>
private static FileDialogManager SetupFileManager(string modDirectory) /// <summary> Set up the file selector with the right flags and custom side bar items. </summary>
{ private static FileDialogManager SetupFileManager(string modDirectory)
var fileManager = new FileDialogManager {
{ var fileManager = new FileDialogManager
AddedWindowFlags = ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoDocking, {
}; AddedWindowFlags = ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoDocking,
};
if (Functions.GetDownloadsFolder(out var downloadsFolder))
fileManager.CustomSideBarItems.Add(("Downloads", downloadsFolder, FontAwesomeIcon.Download, -1)); if (WindowsFunctions.GetDownloadsFolder(out var downloadsFolder))
fileManager.CustomSideBarItems.Add(("Downloads", downloadsFolder, FontAwesomeIcon.Download, -1));
if (Functions.GetQuickAccessFolders(out var folders))
foreach (var (idx, (name, path)) in folders.Index()) if (WindowsFunctions.GetQuickAccessFolders(out var folders))
fileManager.CustomSideBarItems.Add(($"{name}##{idx}", path, FontAwesomeIcon.Folder, -1)); foreach (var (idx, (name, path)) in folders.Index())
fileManager.CustomSideBarItems.Add(($"{name}##{idx}", path, FontAwesomeIcon.Folder, -1));
// Add Penumbra Root. This is not updated if the root changes right now.
fileManager.CustomSideBarItems.Add(("Root Directory", modDirectory, FontAwesomeIcon.Gamepad, 0)); // Add Penumbra Root. This is not updated if the root changes right now.
fileManager.CustomSideBarItems.Add(("Root Directory", modDirectory, FontAwesomeIcon.Gamepad, 0));
// Remove Videos and Music.
fileManager.CustomSideBarItems.Add(("Videos", string.Empty, 0, -1)); // Remove Videos and Music.
fileManager.CustomSideBarItems.Add(("Music", string.Empty, 0, -1)); fileManager.CustomSideBarItems.Add(("Videos", string.Empty, 0, -1));
fileManager.CustomSideBarItems.Add(("Music", string.Empty, 0, -1));
return fileManager;
} return fileManager;
}
/// <summary> Update the Root Directory link on changes. </summary>
private void OnModDirectoryChange(in ModDirectoryChanged.Arguments arguments) /// <summary> Update the Root Directory link on changes. </summary>
{ private void OnModDirectoryChange(in ModDirectoryChanged.Arguments arguments)
var idx = _manager.CustomSideBarItems.IndexOf(t => t.Name == "Root Directory"); {
if (idx >= 0) var idx = _manager.CustomSideBarItems.IndexOf(t => t.Name == "Root Directory");
_manager.CustomSideBarItems.RemoveAt(idx); if (idx >= 0)
_manager.CustomSideBarItems.RemoveAt(idx);
if (!arguments.Valid)
return; if (!arguments.Valid)
return;
if (idx >= 0)
_manager.CustomSideBarItems.Insert(idx, ("Root Directory", arguments.Directory, FontAwesomeIcon.Gamepad, 0)); if (idx >= 0)
else _manager.CustomSideBarItems.Insert(idx, ("Root Directory", arguments.Directory, FontAwesomeIcon.Gamepad, 0));
_manager.CustomSideBarItems.Add(("Root Directory", arguments.Directory, FontAwesomeIcon.Gamepad, 0)); else
} _manager.CustomSideBarItems.Add(("Root Directory", arguments.Directory, FontAwesomeIcon.Gamepad, 0));
} }
}

View file

@ -1,33 +1,33 @@
using Dalamud.Interface.Windowing; using ImSharp;
using Dalamud.Bindings.ImGui; using Luna;
using OtterGui.Raii;
using Penumbra.Import.Structs; using Penumbra.Import.Structs;
using Penumbra.Mods.Manager; using Penumbra.Mods.Manager;
namespace Penumbra.UI; namespace Penumbra.UI;
/// <summary> Draw the progress information for import. </summary> /// <summary> Draw the progress information for import. </summary>
public sealed class ImportPopup : Window, Luna.IUiService public sealed class ImportPopup : Window, IUiService
{ {
public const string WindowLabel = "Penumbra Import Status"; public const string WindowLabel = "Penumbra Import Status";
private readonly ModImportManager _modImportManager; private readonly ModImportManager _modImportManager;
private static readonly Vector2 OneHalf = Vector2.One / 2;
public bool WasDrawn { get; private set; } public bool WasDrawn { get; private set; }
public bool PopupWasDrawn { get; private set; } public bool PopupWasDrawn { get; private set; }
public ImportPopup(ModImportManager modImportManager) public ImportPopup(ModImportManager modImportManager)
: base(WindowLabel, : base(WindowLabel,
ImGuiWindowFlags.NoCollapse WindowFlags.NoCollapse
| ImGuiWindowFlags.NoDecoration | WindowFlags.NoDecoration
| ImGuiWindowFlags.NoBackground | WindowFlags.NoBackground
| ImGuiWindowFlags.NoMove | WindowFlags.NoMove
| ImGuiWindowFlags.NoInputs | WindowFlags.NoInputs
| ImGuiWindowFlags.NoNavFocus | WindowFlags.NoNavFocus
| ImGuiWindowFlags.NoFocusOnAppearing | WindowFlags.NoFocusOnAppearing
| ImGuiWindowFlags.NoBringToFrontOnFocus | WindowFlags.NoBringToFrontOnFocus
| ImGuiWindowFlags.NoDocking | WindowFlags.NoDocking
| ImGuiWindowFlags.NoTitleBar, true) | WindowFlags.NoTitleBar, true)
{ {
_modImportManager = modImportManager; _modImportManager = modImportManager;
DisableWindowSounds = true; DisableWindowSounds = true;
@ -55,29 +55,28 @@ public sealed class ImportPopup : Window, Luna.IUiService
if (!_modImportManager.IsImporting(out var import)) if (!_modImportManager.IsImporting(out var import))
return; return;
const string importPopup = "##PenumbraImportPopup"; if (!Im.Popup.IsOpen("##PenumbraImportPopup"u8))
if (!ImGui.IsPopupOpen(importPopup)) Im.Popup.Open("##PenumbraImportPopup"u8);
ImGui.OpenPopup(importPopup);
var display = ImGui.GetIO().DisplaySize; var display = Im.Io.DisplaySize;
var height = Math.Max(display.Y / 4, 15 * ImGui.GetFrameHeightWithSpacing()); var height = Math.Max(display.Y / 4, 15 * Im.Style.FrameHeightWithSpacing);
var width = display.X / 8; var width = display.X / 8;
var size = new Vector2(width * 2, height); var size = new Vector2(width * 2, height);
ImGui.SetNextWindowPos(ImGui.GetMainViewport().GetCenter(), ImGuiCond.Always, Vector2.One / 2); Im.Window.SetNextPosition(Im.Viewport.Main.Center, Condition.Always, OneHalf);
ImGui.SetNextWindowSize(size); Im.Window.SetNextSize(size);
using var popup = ImRaii.Popup(importPopup, ImGuiWindowFlags.Modal); using var popup = Im.Popup.Begin("##PenumbraImportPopup"u8, WindowFlags.Modal);
PopupWasDrawn = true; PopupWasDrawn = true;
var terminate = false; var terminate = false;
using (var child = ImRaii.Child("##import", new Vector2(-1, size.Y - ImGui.GetFrameHeight() * 2))) using (var child = Im.Child.Begin("##import"u8, new Vector2(-1, size.Y - Im.Style.FrameHeight * 2)))
{ {
if (child.Success && import.DrawProgressInfo(new Vector2(-1, ImGui.GetFrameHeight()))) if (child.Success && import.DrawProgressInfo(new Vector2(-1, Im.Style.FrameHeight)))
if (!ImGui.IsMouseHoveringRect(ImGui.GetWindowPos(), ImGui.GetWindowPos() + ImGui.GetWindowSize()) if (!Im.Mouse.IsHoveringRectangle(Rectangle.FromSize(Im.Window.Position, Im.Window.Size))
&& ImGui.IsMouseClicked(ImGuiMouseButton.Left)) && Im.Mouse.IsClicked(MouseButton.Left))
terminate = true; terminate = true;
} }
terminate |= import.State == ImporterState.Done terminate |= import.State == ImporterState.Done
? ImGui.Button("Close", -Vector2.UnitX) ? Im.Button("Close"u8, -Vector2.UnitX)
: import.DrawCancelButton(-Vector2.UnitX); : import.DrawCancelButton(-Vector2.UnitX);
if (terminate) if (terminate)
_modImportManager.ClearImport(); _modImportManager.ClearImport();

View file

@ -13,7 +13,7 @@ public class IncognitoService(TutorialService tutorial, Configuration config) :
{ {
var hold = config.IncognitoModifier.IsActive(); var hold = config.IncognitoModifier.IsActive();
var color = ColorId.FolderExpanded.Value(); var color = ColorId.FolderExpanded.Value();
using (new Im.ColorStyleDisposable().PushBorder(ImStyleBorder.Frame, 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;

View file

@ -1,77 +1,74 @@
using Dalamud.Interface.Utility.Raii; using ImSharp;
using Dalamud.Interface.Windowing; using Luna;
using Dalamud.Bindings.ImGui; using Penumbra.String;
using OtterGui.Text;
using Penumbra.String; namespace Penumbra.UI.Knowledge;
namespace Penumbra.UI.Knowledge; /// <summary> Draw the progress information for import. </summary>
public sealed class KnowledgeWindow : Window, IUiService
/// <summary> Draw the progress information for import. </summary> {
public sealed class KnowledgeWindow : Window, Luna.IUiService private readonly IReadOnlyList<IKnowledgeTab> _tabs =
{ [
private readonly IReadOnlyList<IKnowledgeTab> _tabs = new RaceCodeTab(),
[ ];
new RaceCodeTab(),
]; private IKnowledgeTab? _selected;
private readonly byte[] _filterStore = new byte[256];
private IKnowledgeTab? _selected;
private readonly byte[] _filterStore = new byte[256]; private ByteString _lower = ByteString.Empty;
private ByteString _lower = ByteString.Empty; /// <summary> Draw the progress information for import. </summary>
public KnowledgeWindow()
/// <summary> Draw the progress information for import. </summary> : base("Penumbra Knowledge Window")
public KnowledgeWindow() => SizeConstraints = new WindowSizeConstraints
: base("Penumbra Knowledge Window") {
=> SizeConstraints = new WindowSizeConstraints MaximumSize = new Vector2(10000, 10000),
{ MinimumSize = new Vector2(400, 200),
MaximumSize = new Vector2(10000, 10000), };
MinimumSize = new Vector2(400, 200),
}; public override void Draw()
{
public override void Draw() DrawSelector();
{ Im.Line.SameInner();
DrawSelector(); DrawMain();
ImUtf8.SameLineInner(); }
DrawMain();
} private void DrawSelector()
{
private void DrawSelector() using var group = Im.Group();
{ using (ImStyleSingle.FrameRounding.Push(0).Push(ImStyleDouble.ItemSpacing, Vector2.Zero))
using var group = ImUtf8.Group(); {
using (ImRaii.PushStyle(ImGuiStyleVar.FrameRounding, 0).Push(ImGuiStyleVar.ItemSpacing, Vector2.Zero)) Im.Item.SetNextWidthScaled(200);
{ if (Im.Input.Text("##Filter"u8, _filterStore, out ulong length, "Filter..."u8))
ImGui.SetNextItemWidth(200 * ImUtf8.GlobalScale); _lower = ByteString.FromSpanUnsafe(_filterStore.AsSpan(0, (int)length), true, null, null).AsciiToLowerClone();
if (ImUtf8.InputText("##Filter"u8, _filterStore, out TerminatedByteString filter, "Filter..."u8)) }
_lower = ByteString.FromSpanUnsafe(filter, true, null, null).AsciiToLowerClone();
} using var child = Im.Child.Begin("KnowledgeSelector"u8, Im.ContentRegion.Available with { X = 200 * Im.Style.GlobalScale }, true);
if (!child)
using var child = ImUtf8.Child("KnowledgeSelector"u8, new Vector2(200 * ImUtf8.GlobalScale, ImGui.GetContentRegionAvail().Y), true); return;
if (!child)
return; foreach (var tab in _tabs)
{
foreach (var tab in _tabs) if (!_lower.IsEmpty && tab.SearchTags.IndexOf(_lower.Span) < 0)
{ continue;
if (!_lower.IsEmpty && tab.SearchTags.IndexOf(_lower.Span) < 0)
continue; if (Im.Selectable(tab.Name, _selected == tab))
_selected = tab;
if (ImUtf8.Selectable(tab.Name, _selected == tab)) }
_selected = tab; }
}
} private void DrawMain()
{
private void DrawMain() using var group = Im.Group();
{ using (ImStyleSingle.FrameRounding.Push(0).Push(ImStyleDouble.ItemSpacing, Vector2.Zero))
using var group = ImUtf8.Group(); {
using (ImRaii.PushStyle(ImGuiStyleVar.FrameRounding, 0).Push(ImGuiStyleVar.ItemSpacing, Vector2.Zero)) ImEx.TextFramed(_selected == null ? "No Selection"u8 : _selected.Name, Im.ContentRegion.Available with { Y = 0 });
{ }
ImUtf8.TextFramed(_selected == null ? "No Selection"u8 : _selected.Name, ImGui.GetColorU32(ImGuiCol.FrameBg),
new Vector2(ImGui.GetContentRegionAvail().X, 0)); using var child = Im.Child.Begin("KnowledgeMain"u8, Im.ContentRegion.Available, true);
} if (!child || _selected == null)
return;
using var child = ImUtf8.Child("KnowledgeMain"u8, ImGui.GetContentRegionAvail(), true);
if (!child || _selected == null) _selected.Draw();
return; }
}
_selected.Draw();
}
}

View file

@ -1,83 +1,77 @@
using Dalamud.Bindings.ImGui; using ImSharp;
using ImSharp; using Penumbra.GameData.Enums;
using OtterGui.Text;
using Penumbra.GameData.Enums; namespace Penumbra.UI.Knowledge;
namespace Penumbra.UI.Knowledge; public sealed class RaceCodeTab : IKnowledgeTab
{
public sealed class RaceCodeTab() : IKnowledgeTab public ReadOnlySpan<byte> Name
{ => "Race Codes"u8;
public ReadOnlySpan<byte> Name
=> "Race Codes"u8; public ReadOnlySpan<byte> SearchTags
=> "deformersracecodesmodel"u8;
public ReadOnlySpan<byte> SearchTags
=> "deformersracecodesmodel"u8; public void Draw()
{
public void Draw() var size = new Vector2((Im.ContentRegion.Available.X - Im.Style.ItemSpacing.X) / 2, 0);
{ using (var table = Im.Table.Begin("adults"u8, 4, TableFlags.BordersOuter, size))
var size = new Vector2((ImGui.GetContentRegionAvail().X - ImUtf8.ItemSpacing.X) / 2, 0); {
using (var table = ImUtf8.Table("adults"u8, 4, ImGuiTableFlags.BordersOuter, size)) if (!table)
{ return;
if (!table)
return; DrawHeaders(table);
foreach (var gr in Enum.GetValues<GenderRace>())
DrawHeaders(); {
foreach (var gr in Enum.GetValues<GenderRace>()) var (gender, race) = gr.Split();
{ if (gender is not Gender.Male and not Gender.Female || race is ModelRace.Unknown)
var (gender, race) = gr.Split(); continue;
if (gender is not Gender.Male and not Gender.Female || race is ModelRace.Unknown)
continue; DrawRow(table, gender, race, false);
}
DrawRow(gender, race, false); }
}
} Im.Line.Same();
Im.Line.Same(); using (var table = Im.Table.Begin("children"u8, 4, TableFlags.BordersOuter, size))
{
using (var table = ImUtf8.Table("children"u8, 4, ImGuiTableFlags.BordersOuter, size)) if (!table)
{ return;
if (!table)
return; DrawHeaders(table);
foreach (var race in (ReadOnlySpan<ModelRace>)
DrawHeaders(); [ModelRace.Midlander, ModelRace.Elezen, ModelRace.Miqote, ModelRace.AuRa, ModelRace.Unknown])
foreach (var race in (ReadOnlySpan<ModelRace>) {
[ModelRace.Midlander, ModelRace.Elezen, ModelRace.Miqote, ModelRace.AuRa, ModelRace.Unknown]) foreach (var gender in (ReadOnlySpan<Gender>)[Gender.Male, Gender.Female])
{ DrawRow(table, gender, race, true);
foreach (var gender in (ReadOnlySpan<Gender>) [Gender.Male, Gender.Female]) }
DrawRow(gender, race, true); }
}
} return;
return; static void DrawHeaders(in Im.TableDisposable table)
{
static void DrawHeaders() table.NextColumn();
{ table.Header("Race"u8);
ImGui.TableNextColumn(); table.NextColumn();
ImUtf8.TableHeader("Race"u8); table.Header("Gender"u8);
ImGui.TableNextColumn(); table.NextColumn();
ImUtf8.TableHeader("Gender"u8); table.Header("Age"u8);
ImGui.TableNextColumn(); table.NextColumn();
ImUtf8.TableHeader("Age"u8); table.Header("Race Code"u8);
ImGui.TableNextColumn(); }
ImUtf8.TableHeader("Race Code"u8);
} static void DrawRow(in Im.TableDisposable table, Gender gender, ModelRace race, bool child)
{
static void DrawRow(Gender gender, ModelRace race, bool child) var gr = child
{ ? Names.CombinedRace(gender is Gender.Male ? Gender.MaleNpc : Gender.FemaleNpc, race)
var gr = child : Names.CombinedRace(gender, race);
? Names.CombinedRace(gender is Gender.Male ? Gender.MaleNpc : Gender.FemaleNpc, race)
: Names.CombinedRace(gender, race); table.DrawColumn(race.ToNameU8());
ImGui.TableNextColumn(); table.DrawColumn(gender.ToNameU8());
ImUtf8.Text(race.ToName()); table.DrawColumn(child ? "Child"u8 : "Adult"u8);
ImGui.TableNextColumn(); table.NextColumn();
ImUtf8.Text(gender.ToName()); ImEx.CopyOnClickSelectable(gr.ToRaceCode());
}
ImGui.TableNextColumn(); }
ImUtf8.Text(child ? "Child"u8 : "Adult"u8); }
ImGui.TableNextColumn();
ImUtf8.CopyOnClickSelectable(gr.ToRaceCode());
}
}
}

View file

@ -3,6 +3,7 @@ using Dalamud.Interface.DragDrop;
using Dalamud.Interface.ImGuiNotification; using Dalamud.Interface.ImGuiNotification;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using Dalamud.Bindings.ImGui; using Dalamud.Bindings.ImGui;
using ImSharp;
using Luna; using Luna;
using OtterGui; using OtterGui;
using OtterGui.Filesystem; using OtterGui.Filesystem;
@ -186,13 +187,13 @@ public sealed class ModFileSystemSelector : FileSystemSelector<Mod, ModFileSyste
=> _config.SortMode; => _config.SortMode;
protected override uint ExpandedFolderColor protected override uint ExpandedFolderColor
=> ColorId.FolderExpanded.Value(); => ColorId.FolderExpanded.Value().Color;
protected override uint CollapsedFolderColor protected override uint CollapsedFolderColor
=> ColorId.FolderCollapsed.Value(); => ColorId.FolderCollapsed.Value().Color;
protected override uint FolderLineColor protected override uint FolderLineColor
=> ColorId.FolderLine.Value(); => ColorId.FolderLine.Value().Color;
protected override bool FoldersDefaultOpen protected override bool FoldersDefaultOpen
=> _config.OpenFoldersByDefault; => _config.OpenFoldersByDefault;
@ -218,8 +219,8 @@ public sealed class ModFileSystemSelector : FileSystemSelector<Mod, ModFileSyste
protected override void DrawLeafName(FileSystem<Mod>.Leaf leaf, in ModState state, bool selected) protected override void DrawLeafName(FileSystem<Mod>.Leaf leaf, in ModState state, bool selected)
{ {
var flags = selected ? ImGuiTreeNodeFlags.Selected | LeafFlags : LeafFlags; var flags = selected ? ImGuiTreeNodeFlags.Selected | LeafFlags : LeafFlags;
using var c = ImRaii.PushColor(ImGuiCol.Text, state.Color.Tinted(state.Tint)) using var c = ImGuiColor.Text.Push(state.Color.Value().Tinted(state.Tint.Value()))
.Push(ImGuiCol.HeaderHovered, 0x4000FFFF, leaf.Value.Favorite); .Push(ImGuiColor.HeaderHovered, 0x4000FFFF, leaf.Value.Favorite);
using var id = ImUtf8.PushId(leaf.Value.Index); using var id = ImUtf8.PushId(leaf.Value.Index);
ImUtf8.TreeNode(leaf.Value.Name, flags).Dispose(); ImUtf8.TreeNode(leaf.Value.Name, flags).Dispose();
if (ImGui.IsItemClicked(ImGuiMouseButton.Middle)) if (ImGui.IsItemClicked(ImGuiMouseButton.Middle))
@ -265,7 +266,7 @@ public sealed class ModFileSystemSelector : FileSystemSelector<Mod, ModFileSyste
offset -= ImGui.GetStyle().ItemInnerSpacing.X; offset -= ImGui.GetStyle().ItemInnerSpacing.X;
if (offset > ImGui.GetStyle().ItemSpacing.X) if (offset > ImGui.GetStyle().ItemSpacing.X)
ImGui.GetWindowDrawList().AddText(new Vector2(itemPos + offset, line), ColorId.SelectorPriority.Value(), priorityString); ImGui.GetWindowDrawList().AddText(new Vector2(itemPos + offset, line), ColorId.SelectorPriority.Value().Color, priorityString);
} }
} }
@ -456,17 +457,17 @@ public sealed class ModFileSystemSelector : FileSystemSelector<Mod, ModFileSyste
ImUtf8.BulletText("Select a mod to obtain more information or change settings."u8); ImUtf8.BulletText("Select a mod to obtain more information or change settings."u8);
ImUtf8.BulletText("Names are colored according to your config and their current state in the collection:"u8); ImUtf8.BulletText("Names are colored according to your config and their current state in the collection:"u8);
indent.Push(); indent.Push();
ImUtf8.BulletTextColored(ColorId.EnabledMod.Value(), "enabled in the current collection."u8); ImUtf8.BulletTextColored(ColorId.EnabledMod.Value().Color, "enabled in the current collection."u8);
ImUtf8.BulletTextColored(ColorId.DisabledMod.Value(), "disabled in the current collection."u8); ImUtf8.BulletTextColored(ColorId.DisabledMod.Value().Color, "disabled in the current collection."u8);
ImUtf8.BulletTextColored(ColorId.InheritedMod.Value(), "enabled due to inheritance from another collection."u8); ImUtf8.BulletTextColored(ColorId.InheritedMod.Value().Color, "enabled due to inheritance from another collection."u8);
ImUtf8.BulletTextColored(ColorId.InheritedDisabledMod.Value(), "disabled due to inheritance from another collection."u8); ImUtf8.BulletTextColored(ColorId.InheritedDisabledMod.Value().Color, "disabled due to inheritance from another collection."u8);
ImUtf8.BulletTextColored(ColorId.UndefinedMod.Value(), "unconfigured in all inherited collections."u8); ImUtf8.BulletTextColored(ColorId.UndefinedMod.Value().Color, "unconfigured in all inherited collections."u8);
ImUtf8.BulletTextColored(ColorId.HandledConflictMod.Value(), ImUtf8.BulletTextColored(ColorId.HandledConflictMod.Value().Color,
"enabled and conflicting with another enabled Mod, but on different priorities (i.e. the conflict is solved)."u8); "enabled and conflicting with another enabled Mod, but on different priorities (i.e. the conflict is solved)."u8);
ImUtf8.BulletTextColored(ColorId.ConflictingMod.Value(), ImUtf8.BulletTextColored(ColorId.ConflictingMod.Value().Color,
"enabled and conflicting with another enabled Mod on the same priority."u8); "enabled and conflicting with another enabled Mod on the same priority."u8);
ImUtf8.BulletTextColored(ColorId.FolderExpanded.Value(), "expanded mod folder."u8); ImUtf8.BulletTextColored(ColorId.FolderExpanded.Value().Color, "expanded mod folder."u8);
ImUtf8.BulletTextColored(ColorId.FolderCollapsed.Value(), "collapsed mod folder"u8); ImUtf8.BulletTextColored(ColorId.FolderCollapsed.Value().Color, "collapsed mod folder"u8);
indent.Pop(1); indent.Pop(1);
ImUtf8.BulletText("Middle-click a mod to disable it if it is enabled or enable it if it is disabled."u8); ImUtf8.BulletText("Middle-click a mod to disable it if it is enabled or enable it if it is disabled."u8);
indent.Push(); indent.Push();

View file

@ -124,7 +124,7 @@ public class ModPanelCollectionsTab(CollectionManager manager, ModFileSystemSele
: settings.Enabled : settings.Enabled
? (parent == collection ? enabled : inherited, ModState.Enabled) ? (parent == collection ? enabled : inherited, ModState.Enabled)
: (parent == collection ? disabled : disInherited, ModState.Disabled); : (parent == collection ? disabled : disInherited, ModState.Disabled);
_cache.Add((collection, parent, color, text)); _cache.Add((collection, parent, color.Color, text));
if (color == enabled) if (color == enabled)
++directCount; ++directCount;

View file

@ -75,7 +75,7 @@ public class ModPanelSettingsTab(
if (!_temporary) if (!_temporary)
return; return;
using var color = ImRaii.PushColor(ImGuiCol.Button, ImGuiCol.Button.Tinted(ColorId.TemporaryModSettingsTint)); using var color = ImGuiColor.Button.Push(Rgba32.TintColor(Im.Style[ImGuiColor.Button], ColorId.TemporaryModSettingsTint.Value().ToVector()));
var width = new Vector2(ImGui.GetContentRegionAvail().X, 0); var width = new Vector2(ImGui.GetContentRegionAvail().X, 0);
if (ImUtf8.ButtonEx($"These settings are temporarily set by {selection.TemporarySettings!.Source}{(_locked ? " and locked." : ".")}", if (ImUtf8.ButtonEx($"These settings are temporarily set by {selection.TemporarySettings!.Source}{(_locked ? " and locked." : ".")}",
width, width,

View file

@ -1,13 +1,8 @@
using Dalamud.Bindings.ImGui; using Dalamud.Interface.ImGuiNotification;
using Dalamud.Interface;
using Dalamud.Interface.ImGuiNotification;
using ImSharp; using ImSharp;
using Luna; using Luna;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using OtterGui;
using OtterGui.Raii;
using OtterGui.Text;
using Penumbra.Mods; using Penumbra.Mods;
using Penumbra.Mods.Manager; using Penumbra.Mods.Manager;
using Penumbra.Services; using Penumbra.Services;
@ -116,17 +111,15 @@ public sealed class PredefinedTagManager : ISavable, IReadOnlyList<string>, ISer
public void DrawToggleButton() public void DrawToggleButton()
{ {
using var color = ImRaii.PushColor(ImGuiCol.Button, ImGui.GetColorU32(ImGuiCol.ButtonActive), _isListOpen); using var color = ImGuiColor.Button.Push(Im.Style[ImGuiColor.ButtonActive], _isListOpen);
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Tags.ToIconString(), new Vector2(ImGui.GetFrameHeight()), if (ImEx.Icon.Button(LunaStyle.TagsMarker, "Add Predefined Tags..."u8))
"Add Predefined Tags...", false, true))
_isListOpen = !_isListOpen; _isListOpen = !_isListOpen;
} }
private void DrawToggleButtonTopRight() private void DrawToggleButtonTopRight()
{ {
ImGui.SameLine(ImGui.GetContentRegionMax().X var scrollBar = Im.Scroll.MaximumY > 0 ? Im.Style.ItemInnerSpacing.X : 0;
- ImGui.GetFrameHeight() Im.Line.Same(Im.ContentRegion.Maximum.X - Im.Style.FrameHeight - scrollBar);
- (ImGui.GetScrollMaxY() > 0 ? ImGui.GetStyle().ItemInnerSpacing.X : 0));
DrawToggleButton(); DrawToggleButton();
} }
@ -139,8 +132,8 @@ public sealed class PredefinedTagManager : ISavable, IReadOnlyList<string>, ISer
if (!_isListOpen) if (!_isListOpen)
return false; return false;
ImUtf8.Text("Predefined Tags"u8); Im.Text("Predefined Tags"u8);
ImGui.Separator(); Im.Separator();
var ret = false; var ret = false;
_enabledColor = ColorId.PredefinedTagAdd.Value(); _enabledColor = ColorId.PredefinedTagAdd.Value();
@ -159,8 +152,8 @@ public sealed class PredefinedTagManager : ISavable, IReadOnlyList<string>, ISer
Im.Line.Same(); Im.Line.Same();
} }
ImGui.NewLine(); Im.Line.New();
ImGui.Separator(); Im.Separator();
return ret; return ret;
} }
@ -181,12 +174,12 @@ public sealed class PredefinedTagManager : ISavable, IReadOnlyList<string>, ISer
if (!_isListOpen) if (!_isListOpen)
return; return;
ImUtf8.Text("Predefined Tags"u8); Im.Text("Predefined Tags"u8);
PrepareLists(selection); PrepareLists(selection);
_enabledColor = ColorId.PredefinedTagAdd.Value(); _enabledColor = ColorId.PredefinedTagAdd.Value();
_disabledColor = ColorId.PredefinedTagRemove.Value(); _disabledColor = ColorId.PredefinedTagRemove.Value();
using var color = new ImRaii.Color(); using var color = new Im.ColorDisposable();
foreach (var (idx, tag) in _predefinedTags.Keys.Index()) foreach (var (idx, tag) in _predefinedTags.Keys.Index())
{ {
var alreadyContained = 0; var alreadyContained = 0;
@ -217,22 +210,22 @@ public sealed class PredefinedTagManager : ISavable, IReadOnlyList<string>, ISer
} }
} }
using var id = ImRaii.PushId(idx); using var id = Im.Id.Push(idx);
var buttonWidth = CalcTextButtonWidth(tag); var buttonWidth = new Vector2(Im.Font.CalculateButtonSize(tag).X, 0);
// Prevent adding a new tag past the right edge of the popup // Prevent adding a new tag past the right edge of the popup
if (buttonWidth + ImGui.GetStyle().ItemSpacing.X >= ImGui.GetContentRegionAvail().X) if (buttonWidth.X + Im.Style.ItemSpacing.X >= Im.ContentRegion.Available.X)
ImGui.NewLine(); Im.Line.New();
var (usedColor, disabled, tt) = (missing, alreadyContained) switch var (usedColor, disabled, tt) = (missing, alreadyContained) switch
{ {
(> 0, _) => (_enabledColor, false, (> 0, _) => (_enabledColor, false,
$"Add this tag to {missing} mods.{(inModData > 0 ? $" {inModData} mods contain it in their mod tags and are untouched." : string.Empty)}"), new StringU8($"Add this tag to {missing} mods.{(inModData > 0 ? $" {inModData} mods contain it in their mod tags and are untouched." : string.Empty)}")),
(_, > 0) => (_disabledColor, false, (_, > 0) => (_disabledColor, false,
$"Remove this tag from {alreadyContained} mods.{(inModData > 0 ? $" {inModData} mods contain it in their mod tags and are untouched." : string.Empty)}"), new StringU8($"Remove this tag from {alreadyContained} mods.{(inModData > 0 ? $" {inModData} mods contain it in their mod tags and are untouched." : string.Empty)}")),
_ => (_disabledColor, true, "This tag is already present in the mod tags of all selected mods."), _ => (_disabledColor, true, new StringU8("This tag is already present in the mod tags of all selected mods.")),
}; };
color.Push(ImGuiCol.Button, usedColor); color.Push(ImGuiColor.Button, usedColor);
if (ImUtf8.ButtonEx(tag, tt, new Vector2(buttonWidth, 0), disabled)) if (ImEx.Button(tag, buttonWidth, tt, disabled))
{ {
if (missing > 0) if (missing > 0)
foreach (var (mod, (localIdx, _)) in _selectedMods.Zip(_countedMods)) foreach (var (mod, (localIdx, _)) in _selectedMods.Zip(_countedMods))
@ -256,34 +249,30 @@ public sealed class PredefinedTagManager : ISavable, IReadOnlyList<string>, ISer
color.Pop(); color.Pop();
} }
ImGui.NewLine(); Im.Line.New();
} }
private bool DrawColoredButton(string buttonLabel, int index, int tagIdx, bool inOther) private bool DrawColoredButton(string buttonLabel, int index, int tagIdx, bool inOther)
{ {
using var id = ImRaii.PushId(index); using var id = Im.Id.Push(index);
var buttonWidth = CalcTextButtonWidth(buttonLabel); var buttonWidth = Im.Font.CalculateButtonSize(buttonLabel).X;
// Prevent adding a new tag past the right edge of the popup // Prevent adding a new tag past the right edge of the popup
if (buttonWidth + ImGui.GetStyle().ItemSpacing.X >= ImGui.GetContentRegionAvail().X) if (buttonWidth + Im.Style.ItemSpacing.X >= Im.ContentRegion.Available.X)
ImGui.NewLine(); Im.Line.New();
bool ret; bool ret;
using (ImRaii.Disabled(inOther)) using (Im.Disabled(inOther))
{ {
using var color = ImRaii.PushColor(ImGuiCol.Button, tagIdx >= 0 || inOther ? _disabledColor : _enabledColor); using var color = ImGuiColor.Button.Push(tagIdx >= 0 || inOther ? _disabledColor : _enabledColor);
ret = ImGui.Button(buttonLabel); ret = Im.Button(buttonLabel);
} }
if (inOther && ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)) if (inOther)
ImGui.SetTooltip("This tag is already present in the other set of tags."); Im.Tooltip.OnHover(HoveredFlags.AllowWhenDisabled, "This tag is already present in the other set of tags."u8);
return ret; return ret;
} }
private static float CalcTextButtonWidth(string text)
=> ImGui.CalcTextSize(text).X + 2 * ImGui.GetStyle().FramePadding.X;
public IEnumerator<string> GetEnumerator() public IEnumerator<string> GetEnumerator()
=> _predefinedTags.Keys.GetEnumerator(); => _predefinedTags.Keys.GetEnumerator();

View file

@ -2,7 +2,6 @@ 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.Raii;
using OtterGui.Widgets; using OtterGui.Widgets;
using Penumbra.Api.Enums; using Penumbra.Api.Enums;
using Penumbra.Collections; using Penumbra.Collections;
@ -144,8 +143,7 @@ public sealed class ResourceWatcher : IDisposable, ITab, Luna.IUiService
Im.Item.SetNextWidth(Im.ContentRegion.Available.X); Im.Item.SetNextWidth(Im.ContentRegion.Available.X);
var tmp = _logFilter; var tmp = _logFilter;
var invalidRegex = _logRegex is null && _logFilter.Length > 0; var invalidRegex = _logRegex is null && _logFilter.Length > 0;
using var color = using var color = ImStyleBorder.Frame.Push(Colors.RegexWarningBorder, Im.Style.GlobalScale, invalidRegex);
new Im.ColorStyleDisposable().PushBorder(ImStyleBorder.Frame, Colors.RegexWarningBorder, Im.Style.GlobalScale, invalidRegex);
if (Im.Input.Text("##logFilter"u8, ref tmp, "If path matches this Regex..."u8)) if (Im.Input.Text("##logFilter"u8, ref tmp, "If path matches this Regex..."u8))
UpdateFilter(tmp, true); UpdateFilter(tmp, true);
} }