Initial Commit

This commit is contained in:
Ottermandias 2021-07-30 17:23:15 +02:00
commit 164f304cf6
38 changed files with 2796 additions and 0 deletions

View file

@ -0,0 +1,178 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using ImGuiNET;
namespace Glamourer.Gui
{
public class ComboWithFilter<T>
{
private readonly string _label;
private readonly string _filterLabel;
private readonly string _listLabel;
private string _currentFilter = string.Empty;
private string _currentFilterLower = string.Empty;
private bool _focus;
private readonly float _size;
private readonly IReadOnlyList<T> _items;
private readonly IReadOnlyList<string> _itemNamesLower;
private readonly Func<T, string> _itemToName;
public Action? PrePreview = null;
public Action? PostPreview = null;
public Func<T, bool>? CreateSelectable = null;
public Action? PreList = null;
public Action? PostList = null;
public float? HeightPerItem = null;
private float _heightPerItem;
public ImGuiComboFlags Flags { get; set; } = ImGuiComboFlags.None;
public int ItemsAtOnce { get; set; } = 12;
public ComboWithFilter(string label, float size, IReadOnlyList<T> items, Func<T, string> itemToName)
{
_label = label;
_filterLabel = $"##_{label}_filter";
_listLabel = $"##_{label}_list";
_itemToName = itemToName;
_items = items;
_size = size;
_itemNamesLower = _items.Select(i => _itemToName(i).ToLowerInvariant()).ToList();
}
public ComboWithFilter(string label, ComboWithFilter<T> other)
{
_label = label;
_filterLabel = $"##_{label}_filter";
_listLabel = $"##_{label}_list";
_itemToName = other._itemToName;
_items = other._items;
_itemNamesLower = other._itemNamesLower;
_size = other._size;
PrePreview = other.PrePreview;
PostPreview = other.PostPreview;
CreateSelectable = other.CreateSelectable;
PreList = other.PreList;
PostList = other.PostList;
HeightPerItem = other.HeightPerItem;
Flags = other.Flags;
}
private bool DrawList(string currentName, out int numItems, out int nodeIdx, ref T? value)
{
numItems = ItemsAtOnce;
nodeIdx = -1;
if (!ImGui.BeginChild(_listLabel, new Vector2(_size, ItemsAtOnce * _heightPerItem)))
return false;
var ret = false;
try
{
if (!_focus)
{
ImGui.SetScrollY(0);
_focus = true;
}
var scrollY = Math.Max((int) (ImGui.GetScrollY() / _heightPerItem) - 1, 0);
var restHeight = scrollY * _heightPerItem;
numItems = 0;
nodeIdx = 0;
if (restHeight > 0)
ImGui.Dummy(Vector2.UnitY * restHeight);
for (var i = scrollY; i < _items.Count; ++i)
{
if (!_itemNamesLower[i].Contains(_currentFilterLower))
continue;
++numItems;
if (numItems <= ItemsAtOnce + 2)
{
nodeIdx = i;
var item = _items[i]!;
var success = false;
if (CreateSelectable != null)
{
success = CreateSelectable(item);
}
else
{
var name = _itemToName(item);
success = ImGui.Selectable(name, name == currentName);
}
if (success)
{
value = item;
ImGui.CloseCurrentPopup();
ret = true;
}
}
}
if (numItems > ItemsAtOnce + 2)
ImGui.Dummy(Vector2.UnitY * (numItems - ItemsAtOnce - 2) * _heightPerItem);
}
finally
{
ImGui.EndChild();
}
return ret;
}
public bool Draw(string currentName, out T? value)
{
value = default;
ImGui.SetNextItemWidth(_size);
PrePreview?.Invoke();
if (!ImGui.BeginCombo(_label, currentName, Flags))
{
_focus = false;
_currentFilter = string.Empty;
_currentFilterLower = string.Empty;
PostPreview?.Invoke();
return false;
}
PostPreview?.Invoke();
_heightPerItem = HeightPerItem ?? ImGui.GetTextLineHeightWithSpacing();
var ret = false;
try
{
ImGui.SetNextItemWidth(-1);
if (ImGui.InputTextWithHint(_filterLabel, "Filter...", ref _currentFilter, 255))
_currentFilterLower = _currentFilter.ToLowerInvariant();
var isFocused = ImGui.IsItemActive();
if (!_focus)
ImGui.SetKeyboardFocusHere();
PreList?.Invoke();
ret = DrawList(currentName, out var numItems, out var nodeIdx, ref value);
PostList?.Invoke();
if (!isFocused && numItems <= 1 && nodeIdx >= 0)
{
value = _items[nodeIdx];
ret = true;
ImGui.CloseCurrentPopup();
}
}
finally
{
ImGui.EndCombo();
}
return ret;
}
}
}

154
Glamourer/Gui/ImGuiRaii.cs Normal file
View file

@ -0,0 +1,154 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using ImGuiNET;
namespace Glamourer.Gui
{
public sealed class ImGuiRaii : IDisposable
{
private int _colorStack = 0;
private int _fontStack = 0;
private int _styleStack = 0;
private float _indentation = 0f;
private Stack<Action>? _onDispose = null;
public ImGuiRaii()
{ }
public static ImGuiRaii NewGroup()
=> new ImGuiRaii().Group();
public ImGuiRaii Group()
=> Begin(ImGui.BeginGroup, ImGui.EndGroup);
public static ImGuiRaii NewTooltip()
=> new ImGuiRaii().Tooltip();
public ImGuiRaii Tooltip()
=> Begin(ImGui.BeginTooltip, ImGui.EndTooltip);
public ImGuiRaii PushColor(ImGuiCol which, uint color)
{
ImGui.PushStyleColor(which, color);
++_colorStack;
return this;
}
public ImGuiRaii PushColor(ImGuiCol which, Vector4 color)
{
ImGui.PushStyleColor(which, color);
++_colorStack;
return this;
}
public ImGuiRaii PopColors(int n = 1)
{
var actualN = Math.Min(n, _colorStack);
if (actualN > 0)
{
ImGui.PopStyleColor(actualN);
_colorStack -= actualN;
}
return this;
}
public ImGuiRaii PushStyle(ImGuiStyleVar style, Vector2 value)
{
ImGui.PushStyleVar(style, value);
++_styleStack;
return this;
}
public ImGuiRaii PushStyle(ImGuiStyleVar style, float value)
{
ImGui.PushStyleVar(style, value);
++_styleStack;
return this;
}
public ImGuiRaii PopStyles(int n = 1)
{
var actualN = Math.Min(n, _styleStack);
if (actualN > 0)
{
ImGui.PopStyleVar(actualN);
_styleStack -= actualN;
}
return this;
}
public ImGuiRaii PushFont(ImFontPtr font)
{
ImGui.PushFont(font);
++_fontStack;
return this;
}
public ImGuiRaii PopFonts(int n = 1)
{
var actualN = Math.Min(n, _fontStack);
while (actualN-- > 0)
{
ImGui.PopFont();
--_fontStack;
}
return this;
}
public ImGuiRaii Indent(float width)
{
if (width != 0)
{
ImGui.Indent(width);
_indentation += width;
}
return this;
}
public ImGuiRaii Unindent(float width)
=> Indent(-width);
public bool Begin(Func<bool> begin, Action end)
{
if (begin())
{
_onDispose ??= new Stack<Action>();
_onDispose.Push(end);
return true;
}
return false;
}
public ImGuiRaii Begin(Action begin, Action end)
{
begin();
_onDispose ??= new Stack<Action>();
_onDispose.Push(end);
return this;
}
public void End(int n = 1)
{
var actualN = Math.Min(n, _onDispose?.Count ?? 0);
while(actualN-- > 0)
_onDispose!.Pop()();
}
public void Dispose()
{
Unindent(_indentation);
PopColors(_colorStack);
PopStyles(_styleStack);
PopFonts(_fontStack);
if (_onDispose != null)
{
End(_onDispose.Count);
_onDispose = null;
}
}
}
}

326
Glamourer/Gui/Interface.cs Normal file
View file

@ -0,0 +1,326 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Dalamud.Data.LuminaExtensions;
using Dalamud.Game.ClientState.Actors;
using Dalamud.Game.ClientState.Actors.Types;
using Glamourer.Customization;
using ImGuiNET;
using Penumbra.Api;
using Penumbra.GameData;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using Penumbra.PlayerWatch;
using SDL2;
namespace Glamourer.Gui
{
internal class Interface : IDisposable
{
public const int GPoseActorId = 201;
private const string PluginName = "Glamourer";
private readonly string _glamourerHeader;
private const int ColorButtonWidth = 140;
private readonly IReadOnlyDictionary<byte, Stain> _stains;
private readonly IReadOnlyDictionary<EquipSlot, List<Item>> _equip;
private readonly ActorTable _actors;
private readonly IObjectIdentifier _identifier;
private readonly Dictionary<EquipSlot, (ComboWithFilter<Item>, ComboWithFilter<Stain>)> _combos;
private readonly IPlayerWatcher _playerWatcher;
private bool _visible = false;
private Actor? _player;
private static readonly Vector2 FeatureIconSize = new(80, 80);
public Interface()
{
_glamourerHeader = GlamourerPlugin.Version.Length > 0
? $"{PluginName} v{GlamourerPlugin.Version}###{PluginName}Main"
: $"{PluginName}###{PluginName}Main";
GlamourerPlugin.PluginInterface.UiBuilder.OnBuildUi += Draw;
GlamourerPlugin.PluginInterface.UiBuilder.OnOpenConfigUi += ToggleVisibility;
_stains = GameData.Stains(GlamourerPlugin.PluginInterface);
_equip = GameData.ItemsBySlot(GlamourerPlugin.PluginInterface);
_identifier = Penumbra.GameData.GameData.GetIdentifier(GlamourerPlugin.PluginInterface);
_actors = GlamourerPlugin.PluginInterface.ClientState.Actors;
_playerWatcher = PlayerWatchFactory.Create(GlamourerPlugin.PluginInterface);
var stainCombo = new ComboWithFilter<Stain>("##StainCombo", ColorButtonWidth, _stains.Values.ToArray(),
s => s.Name.ToString())
{
Flags = ImGuiComboFlags.NoArrowButton | ImGuiComboFlags.HeightLarge,
PreList = () =>
{
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero);
ImGui.PushStyleVar(ImGuiStyleVar.FrameRounding, 0);
},
PostList = () => { ImGui.PopStyleVar(3); },
CreateSelectable = s =>
{
var push = PushColor(s);
var ret = ImGui.Button($"{s.Name}##Stain{(byte) s.RowIndex}",
Vector2.UnitX * (ColorButtonWidth - ImGui.GetStyle().ScrollbarSize));
ImGui.PopStyleColor(push);
return ret;
},
ItemsAtOnce = 12,
};
_combos = _equip.ToDictionary(kvp => kvp.Key,
kvp => (new ComboWithFilter<Item>($"{kvp.Key}##Equip", 300, kvp.Value, i => i.Name) { Flags = ImGuiComboFlags.HeightLarge }
, new ComboWithFilter<Stain>($"##{kvp.Key}Stain", stainCombo))
);
}
public void ToggleVisibility(object _, object _2)
=> _visible = !_visible;
public void Dispose()
{
_playerWatcher?.Dispose();
GlamourerPlugin.PluginInterface.UiBuilder.OnBuildUi -= Draw;
GlamourerPlugin.PluginInterface.UiBuilder.OnOpenConfigUi -= ToggleVisibility;
}
private string _currentActorName = "";
private static int PushColor(Stain stain, ImGuiCol type = ImGuiCol.Button)
{
ImGui.PushStyleColor(type, stain.RgbaColor);
if (stain.Intensity > 127)
{
ImGui.PushStyleColor(ImGuiCol.Text, 0xFF101010);
return 2;
}
return 1;
}
private bool DrawColorSelector(ComboWithFilter<Stain> stainCombo, EquipSlot slot, StainId stainIdx)
{
var name = string.Empty;
stainCombo.PostPreview = null;
if (_stains.TryGetValue((byte) stainIdx, out var stain))
{
name = stain.Name;
var previewPush = PushColor(stain, ImGuiCol.FrameBg);
stainCombo.PostPreview = () => ImGui.PopStyleColor(previewPush);
}
if (stainCombo.Draw(name, out var newStain) && _player != null)
{
newStain.Write(_player.Address, slot);
return true;
}
return false;
}
private bool DrawItemSelector(ComboWithFilter<Item> equipCombo, Lumina.Excel.GeneratedSheets.Item? item)
{
var currentName = item?.Name.ToString() ?? "Nothing";
if (equipCombo.Draw(currentName, out var newItem) && _player != null)
{
newItem.Write(_player.Address);
return true;
}
return false;
}
private bool DrawEquip(EquipSlot slot, ActorArmor equip)
{
var (equipCombo, stainCombo) = _combos[slot];
var ret = false;
ret = DrawColorSelector(stainCombo, slot, equip.Stain);
ImGui.SameLine();
var item = _identifier.Identify(equip.Set, new WeaponType(), equip.Variant, slot);
ret |= DrawItemSelector(equipCombo, item);
return ret;
}
private bool DrawWeapon(EquipSlot slot, ActorWeapon weapon)
{
var (equipCombo, stainCombo) = _combos[slot];
var ret = DrawColorSelector(stainCombo, slot, weapon.Stain);
ImGui.SameLine();
var item = _identifier.Identify(weapon.Set, weapon.Type, weapon.Variant, slot);
ret |= DrawItemSelector(equipCombo, item);
return ret;
}
public void UpdateActors(Actor actor)
{
var newEquip = _playerWatcher.UpdateActorWithoutEvent(actor);
GlamourerPlugin.Penumbra?.RedrawActor(actor, RedrawType.WithSettings);
var gPose = _actors[GPoseActorId];
var player = _actors[0];
if (gPose != null && actor.Address == gPose.Address && player != null)
newEquip.Write(player.Address);
}
private SubRace _currentSubRace = SubRace.Midlander;
private Gender _currentGender = Gender.Male;
private CustomizationId _currentCustomization = CustomizationId.Hairstyle;
private static readonly string[]
SubRaceNames = ((SubRace[]) Enum.GetValues(typeof(SubRace))).Skip(1).Select(s => s.ToName()).ToArray();
private void DrawStuff()
{
if (ImGui.BeginCombo("SubRace", _currentSubRace.ToString()))
{
for (var i = 0; i < SubRaceNames.Length; ++i)
{
if (ImGui.Selectable(SubRaceNames[i], (int) _currentSubRace == i + 1))
_currentSubRace = (SubRace) (i + 1);
}
ImGui.EndCombo();
}
if (ImGui.BeginCombo("Gender", _currentGender.ToName()))
{
if (ImGui.Selectable(Gender.Male.ToName(), _currentGender == Gender.Male))
_currentGender = Gender.Male;
if (ImGui.Selectable(Gender.Female.ToName(), _currentGender == Gender.Female))
_currentGender = Gender.Female;
ImGui.EndCombo();
}
var set = GlamourerPlugin.Customization.GetList(_currentSubRace, _currentGender);
if (ImGui.BeginCombo("Customization", _currentCustomization.ToString()))
{
foreach (CustomizationId customizationId in Enum.GetValues(typeof(CustomizationId)))
{
if (!set.IsAvailable(customizationId))
continue;
if (ImGui.Selectable(customizationId.ToString(), customizationId == _currentCustomization))
_currentCustomization = customizationId;
}
ImGui.EndCombo();
}
var count = set.Count(_currentCustomization);
var tmp = 0;
switch (_currentCustomization.ToType(_currentSubRace.ToRace() == Race.Hrothgar))
{
case CharaMakeParams.MenuType.ColorPicker:
{
using var raii = new ImGuiRaii().PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
.PushStyle(ImGuiStyleVar.FrameRounding, 0f);
for (var i = 0; i < count; ++i)
{
var data = set.Data(_currentCustomization, i);
ImGui.ColorButton($"{data.Value}", ImGui.ColorConvertU32ToFloat4(data.Color));
if (i % 8 != 7)
ImGui.SameLine();
}
}
break;
case CharaMakeParams.MenuType.Percentage:
ImGui.SliderInt("Percentage", ref tmp, 0, 100);
break;
case CharaMakeParams.MenuType.ListSelector:
ImGui.Combo("List", ref tmp, Enumerable.Range(0, count).Select(i => $"{_currentCustomization} #{i}").ToArray(), count);
break;
case CharaMakeParams.MenuType.IconSelector:
case CharaMakeParams.MenuType.MultiIconSelector:
{
using var raii = new ImGuiRaii().PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
.PushStyle(ImGuiStyleVar.FrameRounding, 0f);
for (var i = 0; i < count; ++i)
{
var data = set.Data(_currentCustomization, i);
var texture = GlamourerPlugin.Customization.GetIcon(data.IconId);
ImGui.ImageButton(texture.ImGuiHandle, FeatureIconSize * ImGui.GetIO().FontGlobalScale);
if (ImGui.IsItemHovered())
{
using var tooltip = ImGuiRaii.NewTooltip();
ImGui.Image(texture.ImGuiHandle, new Vector2(texture.Width, texture.Height));
}
if (i % 4 != 3)
ImGui.SameLine();
}
}
break;
}
}
private void Draw()
{
ImGui.SetNextWindowSizeConstraints(Vector2.One * 600, Vector2.One * 5000);
if (!_visible || !ImGui.Begin(_glamourerHeader))
return;
try
{
if (ImGui.BeginCombo("Actor", _currentActorName))
{
var idx = 0;
foreach (var actor in GlamourerPlugin.PluginInterface.ClientState.Actors.Where(a => a.ObjectKind == ObjectKind.Player))
{
if (ImGui.Selectable($"{actor.Name}##{idx++}"))
_currentActorName = actor.Name;
}
ImGui.EndCombo();
}
_player = _actors[GPoseActorId] ?? _actors[0];
if (_player == null || !GlamourerPlugin.PluginInterface.ClientState.Condition.Any())
{
ImGui.TextColored(new Vector4(0.4f, 0.1f, 0.1f, 1f),
"No player character available.");
}
else
{
var equip = new ActorEquipment(_player);
var changes = false;
changes |= DrawWeapon(EquipSlot.MainHand, equip.MainHand);
changes |= DrawWeapon(EquipSlot.OffHand, equip.OffHand);
changes |= DrawEquip(EquipSlot.Head, equip.Head);
changes |= DrawEquip(EquipSlot.Body, equip.Body);
changes |= DrawEquip(EquipSlot.Hands, equip.Hands);
changes |= DrawEquip(EquipSlot.Legs, equip.Legs);
changes |= DrawEquip(EquipSlot.Feet, equip.Feet);
changes |= DrawEquip(EquipSlot.Ears, equip.Ears);
changes |= DrawEquip(EquipSlot.Neck, equip.Neck);
changes |= DrawEquip(EquipSlot.Wrists, equip.Wrists);
changes |= DrawEquip(EquipSlot.RFinger, equip.RFinger);
changes |= DrawEquip(EquipSlot.LFinger, equip.LFinger);
if (changes)
UpdateActors(_player);
}
DrawStuff();
}
finally
{
ImGui.End();
}
}
}
}