diff --git a/Glamourer/Events/ObjectUnlocked.cs b/Glamourer/Events/ObjectUnlocked.cs
index ccb4b18..7b8c120 100644
--- a/Glamourer/Events/ObjectUnlocked.cs
+++ b/Glamourer/Events/ObjectUnlocked.cs
@@ -22,6 +22,7 @@ public sealed class ObjectUnlocked : EventWrapper
+ /// Currently used as a hack to make the unlock table dirty in it. If anything else starts using this, rework.
UnlockTable = 0,
}
diff --git a/Glamourer/Gui/Colors.cs b/Glamourer/Gui/Colors.cs
index 2ece621..a665eee 100644
--- a/Glamourer/Gui/Colors.cs
+++ b/Glamourer/Gui/Colors.cs
@@ -18,6 +18,9 @@ public enum ColorId
AutomationActorAvailable,
AutomationActorUnavailable,
HeaderButtons,
+ FavoriteStarOn,
+ FavoriteStarHovered,
+ FavoriteStarOff,
}
public static class Colors
@@ -40,6 +43,9 @@ public static class Colors
ColorId.AutomationActorAvailable => (0xFFFFFFFF, "Automation Actor Available", "A character associated with the given automated design set is currently visible." ),
ColorId.AutomationActorUnavailable => (0xFF808080, "Automation Actor Unavailable", "No character associated with the given automated design set is currently visible." ),
ColorId.HeaderButtons => (0xFFFFF0C0, "Header Buttons", "The text and border color of buttons in the header, like the Incognito toggle." ),
+ ColorId.FavoriteStarOn => (0xFF40D0D0, "Favored Item", "The color of the star for favored items and of the border in the unlock overview tab." ),
+ ColorId.FavoriteStarHovered => (0xFFD040D0, "Favorite Star Hovered", "The color of the star for favored items when it is hovered." ),
+ ColorId.FavoriteStarOff => (0x20808080, "Favorite Star Outline", "The color of the star for items that are not favored when it is not hovered." ),
_ => (0x00000000, string.Empty, string.Empty ),
// @formatter:on
};
diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.cs
index de91fd0..75ca0ab 100644
--- a/Glamourer/Gui/Equipment/EquipmentDrawer.cs
+++ b/Glamourer/Gui/Equipment/EquipmentDrawer.cs
@@ -10,6 +10,7 @@ using Glamourer.Designs;
using Glamourer.Events;
using Glamourer.Services;
using Glamourer.Structs;
+using Glamourer.Unlocks;
using ImGuiNET;
using OtterGui;
using OtterGui.Raii;
@@ -37,7 +38,8 @@ public class EquipmentDrawer
private float _requiredComboWidthUnscaled;
private float _requiredComboWidth;
- public EquipmentDrawer(IDataManager gameData, ItemManager items, CodeService codes, TextureService textures, Configuration config,
+ public EquipmentDrawer(FavoriteManager favorites, IDataManager gameData, ItemManager items, CodeService codes, TextureService textures,
+ Configuration config,
GPoseService gPose)
{
_items = items;
@@ -48,7 +50,7 @@ public class EquipmentDrawer
_stainData = items.Stains;
_stainCombo = new FilterComboColors(DefaultWidth - 20,
_stainData.Data.Prepend(new KeyValuePair(0, ("None", 0, false))), Glamourer.Log);
- _itemCombo = EquipSlotExtensions.EqdpSlots.Select(e => new ItemCombo(gameData, items, e, Glamourer.Log)).ToArray();
+ _itemCombo = EquipSlotExtensions.EqdpSlots.Select(e => new ItemCombo(gameData, items, e, Glamourer.Log, favorites)).ToArray();
_weaponCombo = new Dictionary(FullEquipTypeExtensions.WeaponTypes.Count * 2);
foreach (var type in Enum.GetValues())
{
diff --git a/Glamourer/Gui/Equipment/ItemCombo.cs b/Glamourer/Gui/Equipment/ItemCombo.cs
index 0430e94..5062949 100644
--- a/Glamourer/Gui/Equipment/ItemCombo.cs
+++ b/Glamourer/Gui/Equipment/ItemCombo.cs
@@ -2,6 +2,7 @@
using System.Linq;
using Dalamud.Plugin.Services;
using Glamourer.Services;
+using Glamourer.Unlocks;
using ImGuiNET;
using Lumina.Excel.GeneratedSheets;
using OtterGui;
@@ -16,13 +17,15 @@ namespace Glamourer.Gui.Equipment;
public sealed class ItemCombo : FilterComboCache
{
- public readonly string Label;
- private ItemId _currentItem;
- private float _innerWidth;
+ private readonly FavoriteManager _favorites;
+ public readonly string Label;
+ private ItemId _currentItem;
+ private float _innerWidth;
- public ItemCombo(IDataManager gameData, ItemManager items, EquipSlot slot, Logger log)
- : base(() => GetItems(items, slot), log)
+ public ItemCombo(IDataManager gameData, ItemManager items, EquipSlot slot, Logger log, FavoriteManager favorites)
+ : base(() => GetItems(favorites, items, slot), log)
{
+ _favorites = favorites;
Label = GetLabel(gameData, slot);
_currentItem = ItemManager.NothingId(slot);
SearchByParts = true;
@@ -59,7 +62,15 @@ public sealed class ItemCombo : FilterComboCache
{
var obj = Items[globalIdx];
var name = ToString(obj);
- var ret = ImGui.Selectable(name, selected);
+ if (UiHelpers.DrawFavoriteStar(_favorites, obj) && CurrentSelectionIdx == globalIdx)
+ {
+ CurrentSelectionIdx = -1;
+ _currentItem = obj.ItemId;
+ CurrentSelection = default;
+ }
+
+ ImGui.SameLine();
+ var ret = ImGui.Selectable(name, selected);
ImGui.SameLine();
using var color = ImRaii.PushColor(ImGuiCol.Text, 0xFF808080);
ImGuiUtil.RightAlign($"({obj.ModelString})");
@@ -92,7 +103,7 @@ public sealed class ItemCombo : FilterComboCache
};
}
- private static IReadOnlyList GetItems(ItemManager items, EquipSlot slot)
+ private static IReadOnlyList GetItems(FavoriteManager favorites, ItemManager items, EquipSlot slot)
{
var nothing = ItemManager.NothingItem(slot);
if (!items.ItemService.AwaitedService.TryGetValue(slot.ToEquipType(), out var list))
@@ -104,6 +115,6 @@ public sealed class ItemCombo : FilterComboCache
var enumerable = list.AsEnumerable();
if (slot.IsEquipment())
enumerable = enumerable.Append(ItemManager.SmallClothesItem(slot));
- return enumerable.OrderBy(i => i.Name).Prepend(nothing).ToList();
+ return enumerable.OrderByDescending(favorites.Contains).ThenBy(i => i.Name).Prepend(nothing).ToList();
}
}
diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs
index 8c596ae..2bd79d8 100644
--- a/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs
+++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs
@@ -1,6 +1,7 @@
using System;
using System.Linq;
using System.Numerics;
+using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Interface.Utility;
using Glamourer.Customization;
using Glamourer.Interop;
@@ -24,6 +25,7 @@ public class UnlockOverview
private readonly TextureService _textures;
private readonly CodeService _codes;
private readonly JobService _jobs;
+ private readonly FavoriteManager _favorites;
private static readonly Vector4 UnavailableTint = new(0.3f, 0.3f, 0.3f, 1.0f);
@@ -70,7 +72,7 @@ public class UnlockOverview
public UnlockOverview(ItemManager items, CustomizationService customizations, ItemUnlockManager itemUnlocks,
CustomizeUnlockManager customizeUnlocks, PenumbraChangedItemTooltip tooltip, TextureService textures, CodeService codes,
- JobService jobs)
+ JobService jobs, FavoriteManager favorites)
{
_items = items;
_customizations = customizations;
@@ -80,6 +82,7 @@ public class UnlockOverview
_textures = textures;
_codes = codes;
_jobs = jobs;
+ _favorites = favorites;
}
public void Draw()
@@ -166,10 +169,12 @@ public class UnlockOverview
var (icon, size) = (iconHandle.ImGuiHandle, new Vector2(iconHandle.Width, iconHandle.Height));
ImGui.Image(icon, iconSize, Vector2.Zero, Vector2.One, unlocked || _codes.EnabledShirts ? Vector4.One : UnavailableTint);
+ if (_favorites.Contains(item))
+ ImGui.GetWindowDrawList().AddRect(ImGui.GetItemRectMin(), ImGui.GetItemRectMax(), ColorId.FavoriteStarOn.Value(),
+ 2 * ImGuiHelpers.GlobalScale, ImDrawFlags.RoundCornersAll, 4 * ImGuiHelpers.GlobalScale);
+
if (ImGui.IsItemClicked())
- {
- // TODO link
- }
+ Glamourer.Messager.Chat.Print(new SeStringBuilder().AddItemLink(item.ItemId.Id, false).BuiltString);
if (ImGui.IsItemClicked(ImGuiMouseButton.Right) && _tooltip.Player(out var state))
_tooltip.ApplyItem(state, item);
diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs
index 7ac9989..5ccd037 100644
--- a/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs
+++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs
@@ -3,6 +3,7 @@ using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
+using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Interface;
using Dalamud.Interface.Utility;
using Glamourer.Events;
@@ -24,19 +25,20 @@ public class UnlockTable : Table, IDisposable
private readonly ObjectUnlocked _event;
public UnlockTable(ItemManager items, TextureService textures, ItemUnlockManager itemUnlocks,
- PenumbraChangedItemTooltip tooltip, ObjectUnlocked @event, JobService jobs)
+ PenumbraChangedItemTooltip tooltip, ObjectUnlocked @event, JobService jobs, FavoriteManager favorites)
: base("ItemUnlockTable", new ItemList(items),
- new NameColumn(textures, tooltip) { Label = "Item Name..." },
- new SlotColumn() { Label = "Equip Slot" },
- new TypeColumn() { Label = "Item Type..." },
- new UnlockDateColumn(itemUnlocks) { Label = "Unlocked" },
- new ItemIdColumn() { Label = "Item Id..." },
- new ModelDataColumn(items) { Label = "Model Data..." },
- new JobColumn(jobs) { Label = "Jobs" },
- new LevelColumn() { Label = "Level..." },
- new DyableColumn() { Label = "Dye" },
- new CrestColumn() { Label = "Crest" },
- new TradableColumn() { Label = "Trade" }
+ new FavoriteColumn(favorites, @event) { Label = "F" },
+ new NameColumn(textures, tooltip) { Label = "Item Name..." },
+ new SlotColumn() { Label = "Equip Slot" },
+ new TypeColumn() { Label = "Item Type..." },
+ new UnlockDateColumn(itemUnlocks) { Label = "Unlocked" },
+ new ItemIdColumn() { Label = "Item Id..." },
+ new ModelDataColumn(items) { Label = "Model Data..." },
+ new JobColumn(jobs) { Label = "Jobs" },
+ new LevelColumn() { Label = "Level..." },
+ new DyableColumn() { Label = "Dye" },
+ new CrestColumn() { Label = "Crest" },
+ new TradableColumn() { Label = "Trade" }
)
{
_event = @event;
@@ -48,6 +50,37 @@ public class UnlockTable : Table, IDisposable
public void Dispose()
=> _event.Unsubscribe(OnObjectUnlock);
+ private sealed class FavoriteColumn : YesNoColumn
+ {
+ public override float Width
+ => ImGui.GetFrameHeightWithSpacing();
+
+ private readonly FavoriteManager _favorites;
+ private readonly ObjectUnlocked _hackEvent; // used to trigger the table dirty.
+
+ public FavoriteColumn(FavoriteManager favorites, ObjectUnlocked hackEvent)
+ {
+ _favorites = favorites;
+ _hackEvent = hackEvent;
+ }
+
+ protected override bool GetValue(EquipItem item)
+ => _favorites.Contains(item);
+
+ public override void DrawColumn(EquipItem item, int idx)
+ {
+ ImGui.AlignTextToFramePadding();
+ if (UiHelpers.DrawFavoriteStar(_favorites, item))
+ _hackEvent.Invoke(ObjectUnlocked.Type.Customization, 0, DateTimeOffset.Now);
+ }
+
+ public override bool FilterFunc(EquipItem item)
+ => FilterValue.HasFlag(_favorites.Contains(item) ? YesNoFlag.Yes : YesNoFlag.No);
+
+ public override int Compare(EquipItem lhs, EquipItem rhs)
+ => _favorites.Contains(rhs).CompareTo(_favorites.Contains(lhs));
+ }
+
private sealed class NameColumn : ColumnString
{
private readonly TextureService _textures;
@@ -75,9 +108,7 @@ public class UnlockTable : Table, IDisposable
ImGui.SameLine();
ImGui.AlignTextToFramePadding();
if (ImGui.Selectable(item.Name))
- {
- // TODO link
- }
+ Glamourer.Messager.Chat.Print(new SeStringBuilder().AddItemLink(item.ItemId.Id, false).BuiltString);
if (ImGui.IsItemClicked(ImGuiMouseButton.Right) && _tooltip.Player(out var state))
_tooltip.ApplyItem(state, item);
diff --git a/Glamourer/Gui/UiHelpers.cs b/Glamourer/Gui/UiHelpers.cs
index 93311b6..3bb4c3a 100644
--- a/Glamourer/Gui/UiHelpers.cs
+++ b/Glamourer/Gui/UiHelpers.cs
@@ -1,9 +1,11 @@
using System;
using System.Numerics;
+using Dalamud.Interface;
using Dalamud.Interface.Utility;
using Glamourer.Customization;
using Glamourer.Services;
using Glamourer.Structs;
+using Glamourer.Unlocks;
using ImGuiNET;
using Lumina.Misc;
using OtterGui;
@@ -119,4 +121,26 @@ public static class UiHelpers
(true, false) => (true, false),
(false, true) => (false, true),
};
-}
\ No newline at end of file
+
+ public static bool DrawFavoriteStar(FavoriteManager favorites, EquipItem item)
+ {
+ var favorite = favorites.Contains(item);
+ var hovering = ImGui.IsMouseHoveringRect(ImGui.GetCursorScreenPos(),
+ ImGui.GetCursorScreenPos() + new Vector2(ImGui.GetTextLineHeight()));
+
+ using var font = ImRaii.PushFont(UiBuilder.IconFont);
+ using var c = ImRaii.PushColor(ImGuiCol.Text,
+ hovering ? ColorId.FavoriteStarHovered.Value() : favorite ? ColorId.FavoriteStarOn.Value() : ColorId.FavoriteStarOff.Value());
+ ImGui.TextUnformatted(FontAwesomeIcon.Star.ToIconString());
+ if (ImGui.IsItemClicked())
+ {
+ if (favorite)
+ favorites.Remove(item);
+ else
+ favorites.TryAdd(item);
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/Glamourer/Services/BackupService.cs b/Glamourer/Services/BackupService.cs
index 25a6e8c..dfccb2a 100644
--- a/Glamourer/Services/BackupService.cs
+++ b/Glamourer/Services/BackupService.cs
@@ -34,6 +34,7 @@ public class BackupService
new(fileNames.AutomationFile),
new(fileNames.UnlockFileCustomize),
new(fileNames.UnlockFileItems),
+ new(fileNames.FavoriteFile),
};
list.AddRange(fileNames.Designs());
diff --git a/Glamourer/Services/FilenameService.cs b/Glamourer/Services/FilenameService.cs
index 5a5f554..7299d32 100644
--- a/Glamourer/Services/FilenameService.cs
+++ b/Glamourer/Services/FilenameService.cs
@@ -15,6 +15,7 @@ public class FilenameService
public readonly string AutomationFile;
public readonly string UnlockFileCustomize;
public readonly string UnlockFileItems;
+ public readonly string FavoriteFile;
public FilenameService(DalamudPluginInterface pi)
{
@@ -26,6 +27,7 @@ public class FilenameService
UnlockFileCustomize = Path.Combine(ConfigDirectory, "unlocks_customize.json");
UnlockFileItems = Path.Combine(ConfigDirectory, "unlocks_items.json");
DesignDirectory = Path.Combine(ConfigDirectory, "designs");
+ FavoriteFile = Path.Combine(ConfigDirectory, "favorites.json");
}
diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs
index 1171388..916c679 100644
--- a/Glamourer/Services/ServiceManager.cs
+++ b/Glamourer/Services/ServiceManager.cs
@@ -56,7 +56,8 @@ public static class ServiceManager
.AddSingleton()
.AddSingleton()
.AddSingleton()
- .AddSingleton();
+ .AddSingleton()
+ .AddSingleton();
private static IServiceCollection AddEvents(this IServiceCollection services)
=> services.AddSingleton()
diff --git a/Glamourer/Unlocks/FavoriteManager.cs b/Glamourer/Unlocks/FavoriteManager.cs
new file mode 100644
index 0000000..4cd98c8
--- /dev/null
+++ b/Glamourer/Unlocks/FavoriteManager.cs
@@ -0,0 +1,96 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using Dalamud.Interface.Internal.Notifications;
+using Glamourer.Services;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using OtterGui.Classes;
+using Penumbra.GameData.Structs;
+
+namespace Glamourer.Unlocks;
+
+public class FavoriteManager : ISavable
+{
+ private readonly SaveService _saveService;
+ private readonly HashSet _favorites = new();
+
+ public FavoriteManager(SaveService saveService)
+ {
+ _saveService = saveService;
+ Load();
+ }
+
+ private void Load()
+ {
+ var file = _saveService.FileNames.FavoriteFile;
+ if (!File.Exists(file))
+ return;
+
+ try
+ {
+ var text = File.ReadAllText(file);
+ var array = JsonConvert.DeserializeObject(text) ?? Array.Empty();
+ _favorites.UnionWith(array.Select(i => (ItemId)i));
+ }
+ catch (Exception ex)
+ {
+ Glamourer.Messager.NotificationMessage(ex, "Could not read Favorite file.", NotificationType.Error);
+ }
+ }
+
+ public string ToFilename(FilenameService fileNames)
+ => fileNames.FavoriteFile;
+
+ private void Save()
+ => _saveService.DelaySave(this);
+
+ public void Save(StreamWriter writer)
+ {
+ using var j = new JsonTextWriter(writer)
+ {
+ Formatting = Formatting.Indented,
+ };
+ j.WriteStartArray();
+ foreach (var item in _favorites)
+ j.WriteValue(item.Id);
+ j.WriteEndArray();
+ }
+
+ public bool TryAdd(EquipItem item)
+ => TryAdd(item.ItemId);
+
+ public bool TryAdd(ItemId item)
+ {
+ if (item.Id == 0 || !_favorites.Add(item))
+ return false;
+
+ Save();
+ return true;
+ }
+
+ public bool Remove(EquipItem item)
+ => Remove(item.ItemId);
+
+ public bool Remove(ItemId item)
+ {
+ if (!_favorites.Remove(item))
+ return false;
+
+ Save();
+ return true;
+ }
+
+ public IEnumerator GetEnumerator()
+ => _favorites.GetEnumerator();
+
+ public int Count
+ => _favorites.Count;
+
+ public bool Contains(EquipItem item)
+ => _favorites.Contains(item.ItemId);
+
+ public bool Contains(ItemId item)
+ => _favorites.Contains(item);
+}