|
|
|
|
@@ -29,12 +29,15 @@ namespace Glamaholic.Ui {
|
|
|
|
|
private Dictionary<ItemCategory, Item?> SelectedItems { get; set; } = new();
|
|
|
|
|
private Dictionary<ItemCategory, List<Item>> FilteredItemsCache { get; set; } = new();
|
|
|
|
|
private Dictionary<ItemCategory, float> ScrollPositions { get; set; } = new();
|
|
|
|
|
private Dictionary<ItemCategory, byte> SelectedStain1 { get; set; } = new();
|
|
|
|
|
private Dictionary<ItemCategory, byte> SelectedStain2 { get; set; } = new();
|
|
|
|
|
|
|
|
|
|
private bool _visible;
|
|
|
|
|
private string _itemFilter = string.Empty;
|
|
|
|
|
private ItemCategory _currentCategory = ItemCategory.Head;
|
|
|
|
|
private ItemCategory _previousCategory = ItemCategory.Head;
|
|
|
|
|
private string _lastFilterUsed = string.Empty;
|
|
|
|
|
private string _dyeFilter = string.Empty;
|
|
|
|
|
|
|
|
|
|
internal MainInterface(PluginUi ui) {
|
|
|
|
|
this.Ui = ui;
|
|
|
|
|
@@ -48,6 +51,19 @@ namespace Glamaholic.Ui {
|
|
|
|
|
SelectedItems[ItemCategory.Gloves] = null;
|
|
|
|
|
SelectedItems[ItemCategory.Legs] = null;
|
|
|
|
|
SelectedItems[ItemCategory.Feet] = null;
|
|
|
|
|
|
|
|
|
|
// Initialize dye selections (0 = no dye)
|
|
|
|
|
SelectedStain1[ItemCategory.Head] = 0;
|
|
|
|
|
SelectedStain1[ItemCategory.Body] = 0;
|
|
|
|
|
SelectedStain1[ItemCategory.Gloves] = 0;
|
|
|
|
|
SelectedStain1[ItemCategory.Legs] = 0;
|
|
|
|
|
SelectedStain1[ItemCategory.Feet] = 0;
|
|
|
|
|
|
|
|
|
|
SelectedStain2[ItemCategory.Head] = 0;
|
|
|
|
|
SelectedStain2[ItemCategory.Body] = 0;
|
|
|
|
|
SelectedStain2[ItemCategory.Gloves] = 0;
|
|
|
|
|
SelectedStain2[ItemCategory.Legs] = 0;
|
|
|
|
|
SelectedStain2[ItemCategory.Feet] = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void LoadItemsByCategory() {
|
|
|
|
|
@@ -213,11 +229,11 @@ namespace Glamaholic.Ui {
|
|
|
|
|
var selectedItem = SelectedItems[category];
|
|
|
|
|
|
|
|
|
|
ImGui.TextUnformatted(label);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var drawCursor = ImGui.GetCursorScreenPos();
|
|
|
|
|
var slotSize = SelectedGearIconSize + SelectedGearPaddingSize;
|
|
|
|
|
var borderColour = *ImGui.GetStyleColorVec4(ImGuiCol.Border);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ImGui.GetWindowDrawList().AddRect(
|
|
|
|
|
drawCursor,
|
|
|
|
|
drawCursor + new Vector2(slotSize),
|
|
|
|
|
@@ -241,6 +257,23 @@ namespace Glamaholic.Ui {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ImGui.TextUnformatted(selectedItem.Value.Name.ExtractText());
|
|
|
|
|
|
|
|
|
|
// Draw dye controls - always show both slots
|
|
|
|
|
ImGui.PushItemWidth(-1);
|
|
|
|
|
|
|
|
|
|
// Primary dye
|
|
|
|
|
if (DrawDyeCombo($"##dye1-{label}", SelectedStain1, category, true)) {
|
|
|
|
|
// Apply the dye change immediately
|
|
|
|
|
this.ApplyItemToCharacter(selectedItem.Value, category);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Secondary dye
|
|
|
|
|
if (DrawDyeCombo($"##dye2-{label}", SelectedStain2, category, false)) {
|
|
|
|
|
// Apply the dye change immediately
|
|
|
|
|
this.ApplyItemToCharacter(selectedItem.Value, category);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ImGui.PopItemWidth();
|
|
|
|
|
} else {
|
|
|
|
|
ImGui.SetCursorPos(cursorAfter);
|
|
|
|
|
ImGui.TextUnformatted("(empty)");
|
|
|
|
|
@@ -261,6 +294,137 @@ namespace Glamaholic.Ui {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private unsafe bool DrawDyeCombo(string id, Dictionary<ItemCategory, byte> stainDict, ItemCategory category, bool isPrimary) {
|
|
|
|
|
var allStains = DataCache.AllStains.Value;
|
|
|
|
|
var currentStain = stainDict[category];
|
|
|
|
|
|
|
|
|
|
// Find the current stain info
|
|
|
|
|
string previewName = "None";
|
|
|
|
|
uint previewColor = 0;
|
|
|
|
|
bool previewGloss = false;
|
|
|
|
|
if (currentStain != 0) {
|
|
|
|
|
var stain = allStains.FirstOrDefault(s => s.Id == currentStain);
|
|
|
|
|
if (stain != default) {
|
|
|
|
|
previewName = stain.Name;
|
|
|
|
|
previewColor = stain.Color;
|
|
|
|
|
previewGloss = stain.Gloss;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Calculate contrast color for text
|
|
|
|
|
var textColor = GetContrastColor(previewColor);
|
|
|
|
|
|
|
|
|
|
bool changed = false;
|
|
|
|
|
|
|
|
|
|
// Draw preview button with color
|
|
|
|
|
if (previewColor != 0) {
|
|
|
|
|
ImGui.PushStyleColor(ImGuiCol.FrameBg, previewColor);
|
|
|
|
|
ImGui.PushStyleColor(ImGuiCol.Text, textColor);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ImGui.BeginCombo(id, previewName, ImGuiComboFlags.HeightLarge)) {
|
|
|
|
|
if (previewColor != 0) {
|
|
|
|
|
ImGui.PopStyleColor(2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Search filter
|
|
|
|
|
ImGui.SetNextItemWidth(-1);
|
|
|
|
|
ImGui.InputTextWithHint("##dyefilter", "Search dyes...", ref _dyeFilter, 256);
|
|
|
|
|
|
|
|
|
|
ImGui.Separator();
|
|
|
|
|
|
|
|
|
|
// Filter dyes based on search
|
|
|
|
|
IEnumerable<(byte Id, string Name, uint Color, bool Gloss)> filteredStains = string.IsNullOrEmpty(_dyeFilter)
|
|
|
|
|
? allStains
|
|
|
|
|
: allStains.Where(s => s.Name.Contains(_dyeFilter, StringComparison.OrdinalIgnoreCase));
|
|
|
|
|
|
|
|
|
|
// None option
|
|
|
|
|
var noneColor = ImGui.ColorConvertFloat4ToU32(new Vector4(0.2f, 0.2f, 0.2f, 1.0f));
|
|
|
|
|
ImGui.PushStyleColor(ImGuiCol.Button, noneColor);
|
|
|
|
|
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, noneColor + 0x202020);
|
|
|
|
|
ImGui.PushStyleColor(ImGuiCol.ButtonActive, noneColor + 0x404040);
|
|
|
|
|
if (ImGui.Button("None", new Vector2(-1, 0))) {
|
|
|
|
|
stainDict[category] = 0;
|
|
|
|
|
changed = true;
|
|
|
|
|
ImGui.CloseCurrentPopup();
|
|
|
|
|
}
|
|
|
|
|
ImGui.PopStyleColor(3);
|
|
|
|
|
|
|
|
|
|
// All filtered dyes
|
|
|
|
|
foreach (var stain in filteredStains) {
|
|
|
|
|
var stainTextColor = GetContrastColor(stain.Color);
|
|
|
|
|
ImGui.PushStyleColor(ImGuiCol.Button, stain.Color);
|
|
|
|
|
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, AdjustBrightness(stain.Color, 1.1f));
|
|
|
|
|
ImGui.PushStyleColor(ImGuiCol.ButtonActive, AdjustBrightness(stain.Color, 0.9f));
|
|
|
|
|
ImGui.PushStyleColor(ImGuiCol.Text, stainTextColor);
|
|
|
|
|
|
|
|
|
|
if (ImGui.Button($"{stain.Name}##{stain.Id}", new Vector2(-1, 0))) {
|
|
|
|
|
stainDict[category] = stain.Id;
|
|
|
|
|
changed = true;
|
|
|
|
|
ImGui.CloseCurrentPopup();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Draw gloss overlay
|
|
|
|
|
if (stain.Gloss) {
|
|
|
|
|
var drawList = ImGui.GetWindowDrawList();
|
|
|
|
|
var min = ImGui.GetItemRectMin();
|
|
|
|
|
var max = ImGui.GetItemRectMax();
|
|
|
|
|
drawList.AddRectFilledMultiColor(min, max,
|
|
|
|
|
0x50FFFFFF, 0x50000000, 0x50FFFFFF, 0x50000000);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Selection indicator
|
|
|
|
|
if (currentStain == stain.Id) {
|
|
|
|
|
var drawList = ImGui.GetWindowDrawList();
|
|
|
|
|
var min = ImGui.GetItemRectMin();
|
|
|
|
|
var max = ImGui.GetItemRectMax();
|
|
|
|
|
drawList.AddRect(min, max, 0xFF2020D0, 0, ImDrawFlags.None, 2.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ImGui.PopStyleColor(4);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ImGui.EndCombo();
|
|
|
|
|
} else {
|
|
|
|
|
if (previewColor != 0) {
|
|
|
|
|
ImGui.PopStyleColor(2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Draw gloss overlay on preview
|
|
|
|
|
if (previewGloss) {
|
|
|
|
|
var drawList = ImGui.GetWindowDrawList();
|
|
|
|
|
var min = ImGui.GetItemRectMin();
|
|
|
|
|
var max = ImGui.GetItemRectMax();
|
|
|
|
|
drawList.AddRectFilledMultiColor(min, max,
|
|
|
|
|
0x50FFFFFF, 0x50000000, 0x50FFFFFF, 0x50000000);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Right-click to clear
|
|
|
|
|
if (ImGui.IsItemClicked(ImGuiMouseButton.Right) && currentStain != 0) {
|
|
|
|
|
stainDict[category] = 0;
|
|
|
|
|
changed = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return changed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static uint GetContrastColor(uint rgba) {
|
|
|
|
|
float r = ((rgba >> 0) & 0xFF) / 255.0f;
|
|
|
|
|
float g = ((rgba >> 8) & 0xFF) / 255.0f;
|
|
|
|
|
float b = ((rgba >> 16) & 0xFF) / 255.0f;
|
|
|
|
|
float luminance = 0.299f * r + 0.587f * g + 0.114f * b;
|
|
|
|
|
return luminance > 0.5f ? 0xFF000000 : 0xFFFFFFFF;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static uint AdjustBrightness(uint rgba, float factor) {
|
|
|
|
|
byte r = (byte)Math.Min(255, ((rgba >> 0) & 0xFF) * factor);
|
|
|
|
|
byte g = (byte)Math.Min(255, ((rgba >> 8) & 0xFF) * factor);
|
|
|
|
|
byte b = (byte)Math.Min(255, ((rgba >> 16) & 0xFF) * factor);
|
|
|
|
|
byte a = (byte)((rgba >> 24) & 0xFF);
|
|
|
|
|
return (uint)(r | (g << 8) | (b << 16) | (a << 24));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void ApplySelectedGearToCharacter() {
|
|
|
|
|
if (Service.ObjectTable.LocalPlayer == null) {
|
|
|
|
|
Service.ChatGui.PrintError("[Glamour Browser] No character found.");
|
|
|
|
|
@@ -285,9 +449,11 @@ namespace Glamaholic.Ui {
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
foreach (var (category, glamourerSlot) in slotMappings) {
|
|
|
|
|
if (SelectedItems[category].HasValue) {
|
|
|
|
|
var item = SelectedItems[category].Value;
|
|
|
|
|
Interop.Glamourer.SetItem(playerIndex, glamourerSlot, item.RowId, []);
|
|
|
|
|
if (SelectedItems[category] is { } item) {
|
|
|
|
|
var stain1 = SelectedStain1[category];
|
|
|
|
|
var stain2 = SelectedStain2[category];
|
|
|
|
|
var stains = new byte[] { stain1, stain2 };
|
|
|
|
|
Interop.Glamourer.SetItem(playerIndex, glamourerSlot, item.RowId, stains);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -441,7 +607,10 @@ namespace Glamaholic.Ui {
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (slotMapping.TryGetValue(category, out var glamourerSlot)) {
|
|
|
|
|
Interop.Glamourer.SetItem(playerIndex, glamourerSlot, item.RowId, []);
|
|
|
|
|
var stain1 = SelectedStain1[category];
|
|
|
|
|
var stain2 = SelectedStain2[category];
|
|
|
|
|
var stains = new byte[] { stain1, stain2 };
|
|
|
|
|
Interop.Glamourer.SetItem(playerIndex, glamourerSlot, item.RowId, stains);
|
|
|
|
|
}
|
|
|
|
|
} catch (Exception ex) {
|
|
|
|
|
Service.Log.Error(ex, "Failed to apply item to character");
|
|
|
|
|
|