Glamaholic/Glamaholic/Ui/MainInterface.cs

641 lines
24 KiB
C#
Executable File

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Numerics;
using Dalamud.Interface;
using Dalamud.Interface.Colors;
using FFXIVClientStructs.FFXIV.Client.System.Framework;
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using ImGuiNET;
using Lumina.Excel.GeneratedSheets;
using Newtonsoft.Json;
namespace Glamaholic.Ui {
internal class MainInterface {
private static readonly PlateSlot[] LeftSide = {
PlateSlot.MainHand,
PlateSlot.Head,
PlateSlot.Body,
PlateSlot.Hands,
PlateSlot.Legs,
PlateSlot.Feet,
};
private static readonly PlateSlot[] RightSide = {
PlateSlot.OffHand,
PlateSlot.Ears,
PlateSlot.Neck,
PlateSlot.Wrists,
PlateSlot.RightRing,
PlateSlot.LeftRing,
};
private PluginUi Ui { get; }
private List<Item> Items { get; }
private List<Item> FilteredItems { get; set; }
private bool _visible;
private string _plateName = string.Empty;
private int _dragging = -1;
private int _selectedPlate = -1;
private bool _scrollToSelected;
private string _plateFilter = string.Empty;
private bool _showRename;
private string _renameInput = string.Empty;
private string _importInput = string.Empty;
private Exception? _importError;
private bool _deleteConfirm;
private readonly Stopwatch _shareTimer = new();
private bool _editing;
private SavedPlate? _editingPlate;
private string _itemFilter = string.Empty;
private string _dyeFilter = string.Empty;
internal MainInterface(PluginUi ui) {
this.Ui = ui;
// get all equippable items that aren't soul crystals
this.Items = this.Ui.Plugin.DataManager.GetExcelSheet<Item>()!
.Where(row => row.EquipSlotCategory.Row is not 0 && row.EquipSlotCategory.Value!.SoulCrystal == 0)
.ToList();
this.FilteredItems = this.Items;
}
internal void Open() {
this._visible = true;
}
internal void Toggle() {
this._visible ^= true;
}
internal void Draw() {
this.HandleTimers();
if (!this._visible) {
return;
}
ImGui.SetNextWindowSize(new Vector2(415, 650), ImGuiCond.FirstUseEver);
if (!ImGui.Begin(this.Ui.Plugin.Name, ref this._visible, ImGuiWindowFlags.MenuBar)) {
ImGui.End();
return;
}
this.DrawInner();
ImGui.End();
}
private void DrawMenuBar() {
if (!ImGui.BeginMenuBar()) {
return;
}
if (ImGui.BeginMenu("Plates")) {
if (ImGui.MenuItem("New")) {
this.Ui.Plugin.Config.Plates.Add(new SavedPlate("Untitled Plate"));
this.Ui.Plugin.SaveConfig();
this.SwitchPlate(this.Ui.Plugin.Config.Plates.Count - 1, true);
}
if (ImGui.BeginMenu("Add from current plate")) {
if (Util.DrawTextInput("current-name", ref this._plateName, message: "Input name and press Enter to save.")) {
var current = GameFunctions.CurrentPlate;
if (current != null) {
var plate = new SavedPlate(this._plateName) {
Items = current,
};
this.Ui.Plugin.Config.Plates.Add(plate);
this._plateName = string.Empty;
this.Ui.Plugin.SaveConfig();
}
}
if (ImGui.IsWindowAppearing()) {
ImGui.SetKeyboardFocusHere();
}
ImGui.EndMenu();
}
if (ImGui.BeginMenu("Import")) {
if (Util.DrawTextInput("import-input", ref this._importInput, 2048, "Press Enter to import.")) {
try {
var plate = JsonConvert.DeserializeObject<SavedPlate>(this._importInput);
this._importError = null;
if (plate != null) {
this.Ui.Plugin.Config.Plates.Add(plate);
this.Ui.Plugin.SaveConfig();
}
} catch (Exception ex) {
this._importError = ex;
}
}
if (ImGui.IsWindowAppearing()) {
this._importError = null;
ImGui.SetKeyboardFocusHere();
}
if (this._importError != null) {
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed);
Util.TextUnformattedWrapped(this._importError.Message);
ImGui.PopStyleColor();
}
ImGui.EndMenu();
}
ImGui.EndMenu();
}
var anyChanged = false;
if (ImGui.BeginMenu("Settings")) {
anyChanged |= ImGui.MenuItem("Show plate editor menu", null, ref this.Ui.Plugin.Config.ShowEditorMenu);
anyChanged |= ImGui.MenuItem("Show examine window menu", null, ref this.Ui.Plugin.Config.ShowExamineMenu);
ImGui.EndMenu();
}
if (anyChanged) {
this.Ui.Plugin.SaveConfig();
}
ImGui.EndMenuBar();
}
private void DrawPlateList() {
if (!ImGui.BeginChild("plate list", new Vector2(205 * ImGuiHelpers.GlobalScale, 0), true)) {
return;
}
ImGui.SetNextItemWidth(-1);
ImGui.InputText("##plate-filter", ref this._plateFilter, 512, ImGuiInputTextFlags.AutoSelectAll);
var filter = this._plateFilter.ToLowerInvariant();
(int src, int dst)? drag = null;
if (ImGui.BeginChild("plate list actual", Vector2.Zero, false, ImGuiWindowFlags.HorizontalScrollbar)) {
for (var i = 0; i < this.Ui.Plugin.Config.Plates.Count; i++) {
var plate = this.Ui.Plugin.Config.Plates[i];
if (filter.Length != 0 && !plate.Name.ToLowerInvariant().Contains(filter)) {
continue;
}
int? switchTo = null;
if (ImGui.Selectable($"{plate.Name}##{i}", this._selectedPlate == i)) {
switchTo = i;
}
if (this._scrollToSelected && this._selectedPlate == i) {
this._scrollToSelected = false;
ImGui.SetScrollHereY(1f);
}
if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) {
switchTo = -1;
}
if (ImGui.IsItemHovered()) {
ImGui.PushFont(UiBuilder.IconFont);
var deleteWidth = ImGui.CalcTextSize(FontAwesomeIcon.Times.ToIconString()).X;
ImGui.SameLine(ImGui.GetContentRegionAvail().X - ImGui.GetStyle().ItemInnerSpacing.X * 2 - deleteWidth);
ImGui.TextUnformatted(FontAwesomeIcon.Times.ToIconString());
ImGui.PopFont();
var mouseDown = ImGui.IsMouseDown(ImGuiMouseButton.Left);
var mouseClicked = ImGui.IsMouseReleased(ImGuiMouseButton.Left);
if (ImGui.IsItemHovered() || mouseDown) {
if (mouseClicked) {
switchTo = null;
if (this._deleteConfirm) {
this._deleteConfirm = false;
if (this._selectedPlate == i) {
switchTo = -1;
}
this.Ui.Plugin.Config.Plates.RemoveAt(i);
this.Ui.Plugin.SaveConfig();
} else {
this._deleteConfirm = true;
}
}
} else {
this._deleteConfirm = false;
}
if (this._deleteConfirm) {
ImGui.BeginTooltip();
ImGui.TextUnformatted("Click delete again to confirm.");
ImGui.EndTooltip();
}
}
if (switchTo != null) {
this.SwitchPlate(switchTo.Value);
}
// handle dragging
if (this._plateFilter.Length == 0 && ImGui.IsItemActive() || this._dragging == i) {
this._dragging = i;
var step = 0;
if (ImGui.GetIO().MouseDelta.Y < 0 && ImGui.GetMousePos().Y < ImGui.GetItemRectMin().Y) {
step = -1;
}
if (ImGui.GetIO().MouseDelta.Y > 0 && ImGui.GetMousePos().Y > ImGui.GetItemRectMax().Y) {
step = 1;
}
if (step != 0) {
drag = (i, i + step);
}
}
}
if (!ImGui.IsMouseDown(ImGuiMouseButton.Left) && this._dragging != -1) {
this._dragging = -1;
this.Ui.Plugin.SaveConfig();
}
if (drag != null && drag.Value.dst < this.Ui.Plugin.Config.Plates.Count && drag.Value.dst >= 0) {
this._dragging = drag.Value.dst;
// ReSharper disable once SwapViaDeconstruction
var temp = this.Ui.Plugin.Config.Plates[drag.Value.src];
this.Ui.Plugin.Config.Plates[drag.Value.src] = this.Ui.Plugin.Config.Plates[drag.Value.dst];
this.Ui.Plugin.Config.Plates[drag.Value.dst] = temp;
// do not SwitchPlate, because this is technically not a switch
if (this._selectedPlate == drag.Value.dst) {
var step = drag.Value.dst - drag.Value.src;
this._selectedPlate = drag.Value.dst - step;
} else if (this._selectedPlate == drag.Value.src) {
this._selectedPlate = drag.Value.dst;
}
}
ImGui.EndChild();
}
ImGui.EndChild();
}
private void DrawDyePopup(string dyePopup, SavedGlamourItem mirage) {
if (!ImGui.BeginPopup(dyePopup)) {
return;
}
ImGui.PushItemWidth(-1);
ImGui.InputText("##dye-filter", ref this._dyeFilter, 512);
if (ImGui.IsWindowAppearing()) {
ImGui.SetKeyboardFocusHere();
}
if (ImGui.BeginChild("dye picker", new Vector2(250, 350), false, ImGuiWindowFlags.HorizontalScrollbar)) {
if (ImGui.Selectable("None", mirage.StainId == 0)) {
mirage.StainId = 0;
ImGui.CloseCurrentPopup();
}
var filter = this._dyeFilter.ToLowerInvariant();
foreach (var stain in this.Ui.Plugin.DataManager.GetExcelSheet<Stain>()!) {
if (stain.RowId == 0 || stain.Shade == 0) {
continue;
}
if (filter.Length > 0 && !stain.Name.RawString.ToLowerInvariant().Contains(filter)) {
continue;
}
if (ImGui.Selectable($"{stain.Name}##{stain.RowId}", mirage.StainId == stain.RowId)) {
mirage.StainId = (byte) stain.RowId;
ImGui.CloseCurrentPopup();
}
}
ImGui.EndChild();
}
ImGui.EndPopup();
}
private unsafe void DrawItemPopup(string itemPopup, SavedPlate plate, PlateSlot slot) {
if (!ImGui.BeginPopup(itemPopup)) {
return;
}
ImGui.SetNextItemWidth(-1);
if (ImGui.InputText("##item-filter", ref this._itemFilter, 512, ImGuiInputTextFlags.AutoSelectAll)) {
this.FilterItems(slot);
}
if (ImGui.IsWindowAppearing()) {
ImGui.SetKeyboardFocusHere();
}
if (ImGui.BeginChild("item search", new Vector2(250, 450), false, ImGuiWindowFlags.HorizontalScrollbar)) {
var id = 0u;
if (plate.Items.TryGetValue(slot, out var slotMirage)) {
id = slotMirage.ItemId;
}
if (ImGui.Selectable("None", id == 0)) {
plate.Items.Remove(slot);
ImGui.CloseCurrentPopup();
}
var clipper = new ImGuiListClipperPtr(ImGuiNative.ImGuiListClipper_ImGuiListClipper());
clipper.Begin(this.FilteredItems.Count);
while (clipper.Step()) {
for (var i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) {
var item = this.FilteredItems[i];
if (ImGui.Selectable($"{item.Name}##{item.RowId}", item.RowId == id)) {
if (!plate.Items.ContainsKey(slot)) {
plate.Items[slot] = new SavedGlamourItem();
}
plate.Items[slot].ItemId = item.RowId;
if (!item.IsDyeable) {
plate.Items[slot].StainId = 0;
}
ImGui.CloseCurrentPopup();
}
}
}
ImGui.EndChild();
}
ImGui.EndPopup();
}
private unsafe void DrawIcon(PlateSlot slot, SavedPlate plate, bool editingPlate, int iconSize, int paddingSize) {
var drawCursor = ImGui.GetCursorScreenPos();
var tooltip = slot.Name();
ImGui.BeginGroup();
plate.Items.TryGetValue(slot, out var mirage);
var borderColour = *ImGui.GetStyleColorVec4(ImGuiCol.Border);
// check for item
if (mirage != null && editingPlate) {
var has = GameFunctions.DresserContents.Any(saved => saved.ItemId % 1_000_000 == mirage.ItemId) || this.Ui.Plugin.Functions.IsInArmoire(mirage.ItemId);
if (!has) {
borderColour = ImGuiColors.DalamudYellow;
}
}
ImGui.GetWindowDrawList().AddRect(drawCursor, drawCursor + new Vector2(iconSize + paddingSize), ImGui.ColorConvertFloat4ToU32(borderColour));
var cursorBefore = ImGui.GetCursorPos();
ImGui.InvisibleButton($"preview {slot}", new Vector2(iconSize + paddingSize));
var cursorAfter = ImGui.GetCursorPos();
if (mirage != null) {
var item = this.Ui.Plugin.DataManager.GetExcelSheet<Item>()!.GetRow(mirage.ItemId);
if (item != null) {
var icon = this.Ui.GetIcon(item.Icon);
if (icon != null) {
ImGui.SetCursorPos(cursorBefore + new Vector2(paddingSize / 2f));
ImGui.Image(icon.ImGuiHandle, new Vector2(iconSize));
ImGui.SetCursorPos(cursorAfter);
var stain = this.Ui.Plugin.DataManager.GetExcelSheet<Stain>()!.GetRow(mirage.StainId);
var circleCentre = drawCursor + new Vector2(iconSize, 4 + paddingSize / 2f);
if (mirage.StainId != 0 && stain != null) {
var colour = stain.Color;
var abgr = 0xFF000000;
abgr |= (colour & 0xFF) << 16;
abgr |= ((colour >> 8) & 0xFF) << 8;
abgr |= (colour >> 16) & 0xFF;
ImGui.GetWindowDrawList().AddCircleFilled(circleCentre, 4, abgr);
}
if (item.IsDyeable) {
ImGui.GetWindowDrawList().AddCircle(circleCentre, 5, 0xFF000000);
}
var stainName = mirage.StainId == 0 || stain == null
? ""
: $" ({stain.Name})";
tooltip += $"\n{item.Name}{stainName}";
}
}
}
ImGui.EndGroup();
// fix spacing
ImGui.SetCursorPos(cursorAfter);
if (ImGui.IsItemHovered()) {
ImGui.BeginTooltip();
ImGui.TextUnformatted(tooltip);
ImGui.EndTooltip();
}
var itemPopup = $"plate item edit {slot}";
var dyePopup = $"plate item dye {slot}";
if (this._editing && ImGui.IsItemClicked(ImGuiMouseButton.Left)) {
ImGui.OpenPopup(itemPopup);
this.FilterItems(slot);
}
if (this._editing && ImGui.IsItemClicked(ImGuiMouseButton.Right) && mirage != null) {
var dyeable = this.Ui.Plugin.DataManager.GetExcelSheet<Item>()!.GetRow(mirage.ItemId)?.IsDyeable ?? false;
if (dyeable) {
ImGui.OpenPopup(dyePopup);
}
}
this.DrawItemPopup(itemPopup, plate, slot);
if (mirage != null) {
this.DrawDyePopup(dyePopup, mirage);
}
}
private void DrawPlatePreview(bool editingPlate, SavedPlate plate) {
const int iconSize = 48;
const int paddingSize = 12;
if (!ImGui.BeginTable("plate item preview", 2, ImGuiTableFlags.SizingFixedFit)) {
return;
}
foreach (var (left, right) in LeftSide.Zip(RightSide)) {
ImGui.TableNextColumn();
this.DrawIcon(left, plate, editingPlate, iconSize, paddingSize);
ImGui.TableNextColumn();
this.DrawIcon(right, plate, editingPlate, iconSize, paddingSize);
}
ImGui.EndTable();
}
private unsafe void DrawPlateButtons(SavedPlate plate) {
if (this._editing || !ImGui.BeginTable("plate buttons", 5, ImGuiTableFlags.SizingFixedFit)) {
return;
}
ImGui.TableNextColumn();
if (Util.IconButton(FontAwesomeIcon.Check, tooltip: "Apply")) {
this.Ui.Plugin.Functions.LoadPlate(plate);
}
ImGui.TableNextColumn();
if (Util.IconButton(FontAwesomeIcon.Search, tooltip: "Try on")) {
void SetTryOnSave(bool save) {
var tryOnAgent = (IntPtr) Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.Tryon);
if (tryOnAgent != IntPtr.Zero) {
*(byte*) (tryOnAgent + 0x2E2) = (byte) (save ? 1 : 0);
}
}
SetTryOnSave(false);
foreach (var mirage in plate.Items.Values) {
this.Ui.Plugin.Functions.TryOn(mirage.ItemId, mirage.StainId);
SetTryOnSave(true);
}
}
ImGui.TableNextColumn();
if (Util.IconButton(FontAwesomeIcon.Font, tooltip: "Rename")) {
this._showRename ^= true;
this._renameInput = plate.Name;
}
ImGui.TableNextColumn();
if (Util.IconButton(FontAwesomeIcon.PencilAlt, tooltip: "Edit")) {
this._editing = true;
this._editingPlate = plate.Clone();
}
ImGui.TableNextColumn();
if (Util.IconButton(FontAwesomeIcon.ShareAltSquare, tooltip: "Share")) {
ImGui.SetClipboardText(JsonConvert.SerializeObject(plate));
this._shareTimer.Start();
}
ImGui.EndTable();
}
private void DrawPlateDetail(bool editingPlate) {
if (!ImGui.BeginChild("plate detail")) {
return;
}
if (this._selectedPlate > -1 && this._selectedPlate < this.Ui.Plugin.Config.Plates.Count) {
var plate = this._editingPlate ?? this.Ui.Plugin.Config.Plates[this._selectedPlate];
this.DrawPlatePreview(editingPlate, plate);
var renameWasVisible = this._showRename;
this.DrawPlateButtons(plate);
if (this._shareTimer.IsRunning) {
Util.TextUnformattedWrapped("Copied to clipboard.");
}
if (this._showRename && Util.DrawTextInput("plate-rename", ref this._renameInput, flags: ImGuiInputTextFlags.AutoSelectAll)) {
plate.Name = this._renameInput;
this.Ui.Plugin.SaveConfig();
this._showRename = false;
}
if (this._showRename && !renameWasVisible) {
ImGui.SetKeyboardFocusHere();
}
if (!this.Ui.Plugin.Functions.ArmoireLoaded) {
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudYellow);
Util.TextUnformattedWrapped("The Armoire is not loaded. Open it once to enable glamours from the Armoire.");
ImGui.PopStyleColor();
}
if (this._editing) {
Util.TextUnformattedWrapped("Click an item to edit it. Right-click to dye.");
if (ImGui.Button("Save") && this._editingPlate != null) {
this.Ui.Plugin.Config.Plates[this._selectedPlate] = this._editingPlate;
this.Ui.Plugin.SaveConfig();
this.ResetEditing();
}
ImGui.SameLine();
if (ImGui.Button("Cancel")) {
this.ResetEditing();
}
}
}
ImGui.EndChild();
}
private void DrawInner() {
var editingPlate = Util.IsEditingPlate(this.Ui.Plugin.GameGui);
this.DrawMenuBar();
if (!editingPlate) {
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudYellow);
Util.TextUnformattedWrapped("Glamour Plate editor is not open. Certain functions will not work.");
ImGui.PopStyleColor();
}
this.DrawPlateList();
ImGui.SameLine();
this.DrawPlateDetail(editingPlate);
ImGui.End();
}
private void HandleTimers() {
if (this._shareTimer.Elapsed > TimeSpan.FromSeconds(5)) {
this._shareTimer.Reset();
}
}
internal void SwitchPlate(int idx, bool scrollTo = false) {
this._selectedPlate = idx;
this._scrollToSelected = scrollTo;
this._renameInput = string.Empty;
this._showRename = false;
this._deleteConfirm = false;
this._shareTimer.Reset();
this.ResetEditing();
}
private void ResetEditing() {
this._editing = false;
this._editingPlate = null;
this._itemFilter = string.Empty;
this._dyeFilter = string.Empty;
}
private void FilterItems(PlateSlot slot) {
var filter = this._itemFilter.ToLowerInvariant();
this.FilteredItems = this.Items
.Where(item => Util.MatchesSlot(item.EquipSlotCategory.Value!, slot))
.Where(item => this._itemFilter.Length == 0 || item.Name.RawString.ToLowerInvariant().Contains(filter))
.ToList();
}
}
}