Add preview stuff.

This commit is contained in:
Ottermandias 2024-02-16 15:11:59 +01:00
parent 0f7d7caba8
commit 5f74f4b4d9
7 changed files with 370 additions and 50 deletions

View file

@ -34,15 +34,16 @@ public class EquipmentDrawer
public EquipmentDrawer(FavoriteManager favorites, IDataManager gameData, ItemManager items, CodeService codes, TextureService textures, public EquipmentDrawer(FavoriteManager favorites, IDataManager gameData, ItemManager items, CodeService codes, TextureService textures,
Configuration config, GPoseService gPose) Configuration config, GPoseService gPose)
{ {
_items = items; _items = items;
_codes = codes; _codes = codes;
_textures = textures; _textures = textures;
_config = config; _config = config;
_gPose = gPose; _gPose = gPose;
_stainData = items.Stains; _advancedDyes = advancedDyes;
_stainCombo = new GlamourerColorCombo(DefaultWidth - 20, _stainData, favorites); _stainData = items.Stains;
_itemCombo = EquipSlotExtensions.EqdpSlots.Select(e => new ItemCombo(gameData, items, e, Glamourer.Log, favorites)).ToArray(); _stainCombo = new GlamourerColorCombo(DefaultWidth - 20, _stainData, favorites);
_weaponCombo = new Dictionary<FullEquipType, WeaponCombo>(FullEquipTypeExtensions.WeaponTypes.Count * 2); _itemCombo = EquipSlotExtensions.EqdpSlots.Select(e => new ItemCombo(gameData, items, e, Glamourer.Log, favorites)).ToArray();
_weaponCombo = new Dictionary<FullEquipType, WeaponCombo>(FullEquipTypeExtensions.WeaponTypes.Count * 2);
foreach (var type in Enum.GetValues<FullEquipType>()) foreach (var type in Enum.GetValues<FullEquipType>())
{ {
if (type.ToSlot() is EquipSlot.MainHand) if (type.ToSlot() is EquipSlot.MainHand)
@ -465,8 +466,10 @@ public class EquipmentDrawer
(var tt, item, var valid) = (allowRevert && !revertItem.Equals(currentItem), allowClear && !clearItem.Equals(currentItem), (var tt, item, var valid) = (allowRevert && !revertItem.Equals(currentItem), allowClear && !clearItem.Equals(currentItem),
ImGui.GetIO().KeyCtrl) switch ImGui.GetIO().KeyCtrl) switch
{ {
(true, true, true) => ("Right-click to clear. Control and Right-Click to revert to game.\nControl and mouse wheel to scroll.", revertItem, true), (true, true, true) => ("Right-click to clear. Control and Right-Click to revert to game.\nControl and mouse wheel to scroll.",
(true, true, false) => ("Right-click to clear. Control and Right-Click to revert to game.\nControl and mouse wheel to scroll.", clearItem, true), revertItem, true),
(true, true, false) => ("Right-click to clear. Control and Right-Click to revert to game.\nControl and mouse wheel to scroll.",
clearItem, true),
(true, false, true) => ("Control and Right-Click to revert to game.\nControl and mouse wheel to scroll.", revertItem, true), (true, false, true) => ("Control and Right-Click to revert to game.\nControl and mouse wheel to scroll.", revertItem, true),
(true, false, false) => ("Control and Right-Click to revert to game.\nControl and mouse wheel to scroll.", default, false), (true, false, false) => ("Control and Right-Click to revert to game.\nControl and mouse wheel to scroll.", default, false),
(false, true, _) => ("Right-click to clear.\nControl and mouse wheel to scroll.", clearItem, true), (false, true, _) => ("Right-click to clear.\nControl and mouse wheel to scroll.", clearItem, true),

View file

@ -0,0 +1,224 @@
using Dalamud.Interface;
using Dalamud.Interface.Utility;
using Glamourer.Designs;
using Glamourer.Gui.Tabs.ActorTab;
using Glamourer.Interop.Material;
using Glamourer.Interop.Structs;
using Glamourer.State;
using ImGuiNET;
using OtterGui;
using OtterGui.Raii;
using OtterGui.Services;
using Penumbra.GameData.Actors;
using Penumbra.GameData.Files;
using Penumbra.GameData.Structs;
namespace Glamourer.Gui.Materials;
public sealed unsafe class AdvancedDyePopup(
MainWindowPosition mainPosition,
Configuration config,
StateManager stateManager,
ActorSelector actorSelector,
MaterialDrawer materials,
LiveColorTablePreviewer preview) : IService
{
private MaterialValueIndex? _drawIndex;
private ActorIdentifier _identifier;
private ActorState? _state;
private Actor _actor;
private byte _selectedMaterial = byte.MaxValue;
private bool ShouldBeDrawn()
{
if (!mainPosition.IsOpen)
return false;
if (!config.UseAdvancedDyes)
return false;
if (config.Ephemeral.SelectedTab is not MainWindow.TabType.Actors)
return false;
if (!_drawIndex.HasValue)
return false;
if (actorSelector.Selection.Identifier != _identifier || !_identifier.IsValid)
return false;
if (_state == null)
return false;
_actor = actorSelector.Selection.Data.Valid ? actorSelector.Selection.Data.Objects[0] : Actor.Null;
if (!_actor.Valid || !_actor.Model.IsCharacterBase)
return false;
return true;
}
public void DrawButton(ActorIdentifier identifier, ActorState state, MaterialValueIndex index)
{
using var id = ImRaii.PushId(index.SlotIndex | ((int)index.DrawObject << 8));
var isOpen = identifier == _identifier && state == _state && index == _drawIndex;
using (ImRaii.PushColor(ImGuiCol.Button, ImGui.GetColorU32(ImGuiCol.ButtonActive), isOpen))
{
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Palette.ToIconString(), new Vector2(ImGui.GetFrameHeight()),
"Open advanced dyes for this slot.", false, true))
{
if (isOpen)
{
_drawIndex = null;
_identifier = ActorIdentifier.Invalid;
_state = null;
_selectedMaterial = byte.MaxValue;
}
else
{
_drawIndex = index;
_identifier = identifier;
_state = state;
_selectedMaterial = byte.MaxValue;
}
}
}
}
public unsafe void Draw()
{
if (!ShouldBeDrawn())
return;
var position = mainPosition.Position;
position.X += mainPosition.Size.X;
position.Y += ImGui.GetFrameHeightWithSpacing() * 3;
var size = new Vector2(3 * ImGui.GetFrameHeight() + 300 * ImGuiHelpers.GlobalScale, 18.5f * ImGui.GetFrameHeightWithSpacing());
ImGui.SetNextWindowPos(position);
ImGui.SetNextWindowSize(size);
var window = ImGui.Begin("###Glamourer Advanced Dyes",
ImGuiWindowFlags.NoFocusOnAppearing
| ImGuiWindowFlags.NoCollapse
| ImGuiWindowFlags.NoDecoration
| ImGuiWindowFlags.NoMove
| ImGuiWindowFlags.NoResize);
try
{
if (!window)
return;
using var bar = ImRaii.TabBar("tabs");
if (!bar)
return;
var model = _actor.Model.AsCharacterBase;
var firstAvailable = true;
Span<byte> label = stackalloc byte[12];
label[0] = (byte)'M';
label[1] = (byte)'a';
label[2] = (byte)'t';
label[3] = (byte)'e';
label[4] = (byte)'r';
label[5] = (byte)'i';
label[6] = (byte)'a';
label[7] = (byte)'l';
label[8] = (byte)' ';
label[9] = (byte)'#';
label[11] = 0;
for (byte i = 0; i < MaterialService.MaterialsPerModel; ++i)
{
var texture = model->ColorTableTextures + _drawIndex!.Value.SlotIndex * MaterialService.MaterialsPerModel + i;
var index = _drawIndex!.Value with { MaterialIndex = i };
var available = *texture != null && DirectXTextureHelper.TryGetColorTable(*texture, out var table);
if (index == preview.LastValueIndex with {RowIndex = 0})
table = preview.LastOriginalColorTable;
using var disable = ImRaii.Disabled(!available);
label[10] = (byte)('1' + i);
var select = available && (_selectedMaterial == i || firstAvailable && _selectedMaterial == byte.MaxValue)
? ImGuiTabItemFlags.SetSelected
: ImGuiTabItemFlags.None;
if (available)
firstAvailable = false;
if (select is ImGuiTabItemFlags.SetSelected)
_selectedMaterial = i;
fixed (byte* labelPtr = label)
{
using var tab = ImRaii.TabItem(labelPtr, select);
if (tab.Success && available)
DrawTable(index, table);
}
}
}
finally
{
ImGui.End();
}
}
private void DrawTable(MaterialValueIndex materialIndex, in MtrlFile.ColorTable table)
{
for (byte i = 0; i < MtrlFile.ColorTable.NumRows; ++i)
{
var index = materialIndex with { RowIndex = i };
ref var row = ref table[i];
DrawRow(ref row, CharacterWeapon.Empty, index, table);
}
}
private void DrawRow(ref MtrlFile.ColorTable.Row row, CharacterWeapon drawData, MaterialValueIndex index, in MtrlFile.ColorTable table)
{
using var id = ImRaii.PushId(index.RowIndex);
var changed = _state!.Materials.TryGetValue(index, out var value);
if (!changed)
{
var internalRow = new ColorRow(row);
value = new MaterialValueState(internalRow, internalRow, drawData, StateSource.Manual);
}
ImGui.AlignTextToFramePadding();
using (ImRaii.PushFont(UiBuilder.MonoFont))
{
ImGui.TextUnformatted($"Row {index.RowIndex + 1:D2}");
}
ImGui.SameLine();
ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Crosshairs.ToIconString(), new Vector2(ImGui.GetFrameHeight()), "Locate", false, true);
if (ImGui.IsItemHovered())
preview.OnHover(index, _actor.Index, table);
ImGui.SameLine(0, ImGui.GetStyle().ItemSpacing.X * 2);
var applied = ImGuiUtil.ColorPicker("##diffuse", "Change the diffuse value for this row.", value.Model.Diffuse,
v => value.Model.Diffuse = v, "D");
var spacing = ImGui.GetStyle().ItemInnerSpacing;
ImGui.SameLine(0, spacing.X);
applied |= ImGuiUtil.ColorPicker("##specular", "Change the specular value for this row.", value.Model.Specular,
v => value.Model.Specular = v, "S");
ImGui.SameLine(0, spacing.X);
applied |= ImGuiUtil.ColorPicker("##emissive", "Change the emissive value for this row.", value.Model.Emissive,
v => value.Model.Emissive = v, "E");
ImGui.SameLine(0, spacing.X);
ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale);
applied |= ImGui.DragFloat("##Gloss", ref value.Model.GlossStrength, 0.01f, 0.001f, float.MaxValue, "%.3f G")
&& value.Model.GlossStrength > 0;
ImGuiUtil.HoverTooltip("Change the gloss strength for this row.");
ImGui.SameLine(0, spacing.X);
ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale);
applied |= ImGui.DragFloat("##Specular Strength", ref value.Model.SpecularStrength, 0.01f, float.MinValue, float.MaxValue, "%.3f SS");
ImGuiUtil.HoverTooltip("Change the specular strength for this row.");
if (applied)
stateManager.ChangeMaterialValue(_state!, index, value, ApplySettings.Manual);
if (changed)
{
ImGui.SameLine(0, spacing.X);
using (ImRaii.PushFont(UiBuilder.IconFont))
{
using var color = ImRaii.PushColor(ImGuiCol.Text, ColorId.FavoriteStarOn.Value());
ImGui.TextUnformatted(FontAwesomeIcon.UserEdit.ToIconString());
}
}
}
}

View file

@ -32,15 +32,12 @@ public unsafe class MaterialDrawer(StateManager _stateManager, DesignManager _de
if (!table) if (!table)
return; return;
ImGui.TableSetupColumn("button", ImGuiTableColumnFlags.WidthFixed, buttonSize.X); ImGui.TableSetupColumn("button", ImGuiTableColumnFlags.WidthFixed, buttonSize.X);
ImGui.TableSetupColumn("enabled", ImGuiTableColumnFlags.WidthFixed, buttonSize.X); ImGui.TableSetupColumn("enabled", ImGuiTableColumnFlags.WidthFixed, buttonSize.X);
ImGui.TableSetupColumn("values", ImGuiTableColumnFlags.WidthFixed, ImGui.GetStyle().ItemInnerSpacing.X * 4 + 3 * buttonSize.X + 220 * ImGuiHelpers.GlobalScale); ImGui.TableSetupColumn("values", ImGuiTableColumnFlags.WidthFixed, ImGui.GetStyle().ItemInnerSpacing.X * 4 + 3 * buttonSize.X + 220 * ImGuiHelpers.GlobalScale);
ImGui.TableSetupColumn("revert", ImGuiTableColumnFlags.WidthFixed, buttonSize.X + ImGui.CalcTextSize("Revertm").X); ImGui.TableSetupColumn("revert", ImGuiTableColumnFlags.WidthFixed, buttonSize.X + ImGui.CalcTextSize("Revertm").X);
ImGui.TableSetupColumn("slot", ImGuiTableColumnFlags.WidthStretch); ImGui.TableSetupColumn("slot", ImGuiTableColumnFlags.WidthStretch);
for (var i = 0; i < design.Materials.Count; ++i) for (var i = 0; i < design.Materials.Count; ++i)
{ {
var (idx, value) = design.Materials[i]; var (idx, value) = design.Materials[i];

View file

@ -1,7 +1,6 @@
using Dalamud.Interface; using Dalamud.Interface;
using Glamourer.Interop; using Glamourer.Interop;
using Glamourer.Interop.Structs; using Glamourer.Interop.Structs;
using Glamourer.Services;
using ImGuiNET; using ImGuiNET;
using OtterGui; using OtterGui;
using OtterGui.Classes; using OtterGui.Classes;
@ -10,28 +9,17 @@ using Penumbra.GameData.Actors;
namespace Glamourer.Gui.Tabs.ActorTab; namespace Glamourer.Gui.Tabs.ActorTab;
public class ActorSelector public class ActorSelector(ObjectManager objects, ActorManager actors, EphemeralConfig config)
{ {
private readonly EphemeralConfig _config;
private readonly ObjectManager _objects;
private readonly ActorManager _actors;
private ActorIdentifier _identifier = ActorIdentifier.Invalid; private ActorIdentifier _identifier = ActorIdentifier.Invalid;
public ActorSelector(ObjectManager objects, ActorManager actors, EphemeralConfig config)
{
_objects = objects;
_actors = actors;
_config = config;
}
public bool IncognitoMode public bool IncognitoMode
{ {
get => _config.IncognitoMode; get => config.IncognitoMode;
set set
{ {
_config.IncognitoMode = value; config.IncognitoMode = value;
_config.Save(); config.Save();
} }
} }
@ -40,7 +28,7 @@ public class ActorSelector
private float _width; private float _width;
public (ActorIdentifier Identifier, ActorData Data) Selection public (ActorIdentifier Identifier, ActorData Data) Selection
=> _objects.TryGetValue(_identifier, out var data) ? (_identifier, data) : (_identifier, ActorData.Invalid); => objects.TryGetValue(_identifier, out var data) ? (_identifier, data) : (_identifier, ActorData.Invalid);
public bool HasSelection public bool HasSelection
=> _identifier.IsValid; => _identifier.IsValid;
@ -65,10 +53,10 @@ public class ActorSelector
if (!child) if (!child)
return; return;
_objects.Update(); objects.Update();
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, _defaultItemSpacing); using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, _defaultItemSpacing);
var skips = ImGuiClip.GetNecessarySkips(ImGui.GetTextLineHeight()); var skips = ImGuiClip.GetNecessarySkips(ImGui.GetTextLineHeight());
var remainder = ImGuiClip.FilteredClippedDraw(_objects, skips, CheckFilter, DrawSelectable); var remainder = ImGuiClip.FilteredClippedDraw(objects, skips, CheckFilter, DrawSelectable);
ImGuiClip.DrawEndDummy(remainder, ImGui.GetTextLineHeight()); ImGuiClip.DrawEndDummy(remainder, ImGui.GetTextLineHeight());
} }
@ -89,14 +77,14 @@ public class ActorSelector
var buttonWidth = new Vector2(_width / 2, 0); var buttonWidth = new Vector2(_width / 2, 0);
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.UserCircle.ToIconString(), buttonWidth if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.UserCircle.ToIconString(), buttonWidth
, "Select the local player character.", !_objects.Player, true)) , "Select the local player character.", !objects.Player, true))
_identifier = _objects.Player.GetIdentifier(_actors); _identifier = objects.Player.GetIdentifier(actors);
ImGui.SameLine(); ImGui.SameLine();
var (id, data) = _objects.TargetData; var (id, data) = objects.TargetData;
var tt = data.Valid ? $"Select the current target {id} in the list." : var tt = data.Valid ? $"Select the current target {id} in the list." :
id.IsValid ? $"The target {id} is not in the list." : "No target selected."; id.IsValid ? $"The target {id} is not in the list." : "No target selected.";
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.HandPointer.ToIconString(), buttonWidth, tt, _objects.IsInGPose || !data.Valid, true)) if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.HandPointer.ToIconString(), buttonWidth, tt, objects.IsInGPose || !data.Valid, true))
_identifier = id; _identifier = id;
} }
} }

View file

@ -4,24 +4,15 @@ using OtterGui.Widgets;
namespace Glamourer.Gui.Tabs.ActorTab; namespace Glamourer.Gui.Tabs.ActorTab;
public class ActorTab : ITab public class ActorTab(ActorSelector selector, ActorPanel panel) : ITab
{ {
private readonly ActorSelector _selector;
private readonly ActorPanel _panel;
public ReadOnlySpan<byte> Label public ReadOnlySpan<byte> Label
=> "Actors"u8; => "Actors"u8;
public void DrawContent() public void DrawContent()
{ {
_selector.Draw(200 * ImGuiHelpers.GlobalScale); selector.Draw(200 * ImGuiHelpers.GlobalScale);
ImGui.SameLine(); ImGui.SameLine();
_panel.Draw(); panel.Draw();
}
public ActorTab(ActorSelector selector, ActorPanel panel)
{
_selector = selector;
_panel = panel;
} }
} }

View file

@ -0,0 +1,115 @@
using Dalamud.Plugin.Services;
using Glamourer.Interop.Structs;
using ImGuiNET;
using OtterGui.Services;
using Penumbra.GameData.Files;
using Penumbra.GameData.Structs;
namespace Glamourer.Interop.Material;
public sealed unsafe class LiveColorTablePreviewer : IService, IDisposable
{
private readonly IObjectTable _objects;
private readonly IFramework _framework;
public MaterialValueIndex LastValueIndex { get; private set; } = MaterialValueIndex.Invalid;
public MtrlFile.ColorTable LastOriginalColorTable { get; private set; }
private MaterialValueIndex _valueIndex = MaterialValueIndex.Invalid;
private ObjectIndex _lastObjectIndex = ObjectIndex.AnyIndex;
private ObjectIndex _objectIndex = ObjectIndex.AnyIndex;
private MtrlFile.ColorTable _originalColorTable;
public LiveColorTablePreviewer(IObjectTable objects, IFramework framework)
{
_objects = objects;
_framework = framework;
_framework.Update += OnFramework;
}
private void Reset()
{
if (!LastValueIndex.Valid || _lastObjectIndex == ObjectIndex.AnyIndex)
return;
var actor = (Actor)_objects.GetObjectAddress(_lastObjectIndex.Index);
if (actor.IsCharacter && LastValueIndex.TryGetTexture(actor, out var texture))
MaterialService.ReplaceColorTable(texture, LastOriginalColorTable);
Glamourer.Log.Information($"Reset {_lastObjectIndex} {LastValueIndex}");
LastValueIndex = MaterialValueIndex.Invalid;
_lastObjectIndex = ObjectIndex.AnyIndex;
}
private void OnFramework(IFramework _)
{
if (!_valueIndex.Valid || _objectIndex == ObjectIndex.AnyIndex)
{
Reset();
_valueIndex = MaterialValueIndex.Invalid;
_objectIndex = ObjectIndex.AnyIndex;
return;
}
var actor = (Actor)_objects.GetObjectAddress(_objectIndex.Index);
if (!actor.IsCharacter)
{
_valueIndex = MaterialValueIndex.Invalid;
_objectIndex = ObjectIndex.AnyIndex;
return;
}
if (_valueIndex != LastValueIndex || _lastObjectIndex != _objectIndex)
{
Reset();
LastValueIndex = _valueIndex;
_lastObjectIndex = _objectIndex;
LastOriginalColorTable = _originalColorTable;
}
if (_valueIndex.TryGetTexture(actor, out var texture))
{
Glamourer.Log.Information($"Set {_objectIndex} {_valueIndex}");
var diffuse = CalculateDiffuse();
var table = LastOriginalColorTable;
table[_valueIndex.RowIndex].Diffuse = diffuse;
table[_valueIndex.RowIndex].Emissive = diffuse / 8;
MaterialService.ReplaceColorTable(texture, table);
}
_valueIndex = MaterialValueIndex.Invalid;
_objectIndex = ObjectIndex.AnyIndex;
}
public void OnHover(MaterialValueIndex index, ObjectIndex objectIndex, MtrlFile.ColorTable table)
{
if (_valueIndex.Valid)
return;
_valueIndex = index;
_objectIndex = objectIndex;
if (!LastValueIndex.Valid
|| _lastObjectIndex == ObjectIndex.AnyIndex
|| LastValueIndex.MaterialIndex != _valueIndex.MaterialIndex
|| LastValueIndex.DrawObject != _valueIndex.DrawObject
|| LastValueIndex.SlotIndex != _valueIndex.SlotIndex)
_originalColorTable = table;
}
private static Vector3 CalculateDiffuse()
{
const int frameLength = 1;
const int steps = 64;
var frame = ImGui.GetFrameCount();
var hueByte = frame % (steps * frameLength) / frameLength;
var hue = (float)hueByte / steps;
ImGui.ColorConvertHSVtoRGB(hue, 1, 1, out var r, out var g, out var b);
return new Vector3(r, g, b);
}
public void Dispose()
{
Reset();
_framework.Update -= OnFramework;
}
}

View file

@ -14,6 +14,7 @@ public readonly record struct MaterialValueIndex(
byte MaterialIndex, byte MaterialIndex,
byte RowIndex) byte RowIndex)
{ {
public static readonly MaterialValueIndex Invalid = new(DrawObjectType.Invalid, 0, 0, 0);
public uint Key public uint Key
=> ToKey(DrawObject, SlotIndex, MaterialIndex, RowIndex); => ToKey(DrawObject, SlotIndex, MaterialIndex, RowIndex);
@ -121,13 +122,14 @@ public readonly record struct MaterialValueIndex(
public enum DrawObjectType : byte public enum DrawObjectType : byte
{ {
Invalid,
Human, Human,
Mainhand, Mainhand,
Offhand, Offhand,
}; };
public static bool Validate(DrawObjectType type) public static bool Validate(DrawObjectType type)
=> Enum.IsDefined(type); => type is not DrawObjectType.Invalid && Enum.IsDefined(type);
public static bool ValidateSlot(byte slotIndex) public static bool ValidateSlot(byte slotIndex)
=> slotIndex < 10; => slotIndex < 10;