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,
Configuration config, GPoseService gPose)
{
_items = items;
_codes = codes;
_textures = textures;
_config = config;
_gPose = gPose;
_stainData = items.Stains;
_stainCombo = new GlamourerColorCombo(DefaultWidth - 20, _stainData, favorites);
_itemCombo = EquipSlotExtensions.EqdpSlots.Select(e => new ItemCombo(gameData, items, e, Glamourer.Log, favorites)).ToArray();
_weaponCombo = new Dictionary<FullEquipType, WeaponCombo>(FullEquipTypeExtensions.WeaponTypes.Count * 2);
_items = items;
_codes = codes;
_textures = textures;
_config = config;
_gPose = gPose;
_advancedDyes = advancedDyes;
_stainData = items.Stains;
_stainCombo = new GlamourerColorCombo(DefaultWidth - 20, _stainData, favorites);
_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>())
{
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),
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, false) => ("Right-click to clear. Control and Right-Click to revert to game.\nControl and mouse wheel to scroll.", clearItem, true),
(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, 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, 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),

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)
return;
ImGui.TableSetupColumn("button", 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("revert", ImGuiTableColumnFlags.WidthFixed, buttonSize.X + ImGui.CalcTextSize("Revertm").X);
ImGui.TableSetupColumn("slot", ImGuiTableColumnFlags.WidthStretch);
for (var i = 0; i < design.Materials.Count; ++i)
{
var (idx, value) = design.Materials[i];

View file

@ -1,7 +1,6 @@
using Dalamud.Interface;
using Glamourer.Interop;
using Glamourer.Interop.Structs;
using Glamourer.Services;
using ImGuiNET;
using OtterGui;
using OtterGui.Classes;
@ -10,28 +9,17 @@ using Penumbra.GameData.Actors;
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;
public ActorSelector(ObjectManager objects, ActorManager actors, EphemeralConfig config)
{
_objects = objects;
_actors = actors;
_config = config;
}
public bool IncognitoMode
{
get => _config.IncognitoMode;
get => config.IncognitoMode;
set
{
_config.IncognitoMode = value;
_config.Save();
config.IncognitoMode = value;
config.Save();
}
}
@ -40,7 +28,7 @@ public class ActorSelector
private float _width;
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
=> _identifier.IsValid;
@ -65,10 +53,10 @@ public class ActorSelector
if (!child)
return;
_objects.Update();
objects.Update();
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, _defaultItemSpacing);
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());
}
@ -89,14 +77,14 @@ public class ActorSelector
var buttonWidth = new Vector2(_width / 2, 0);
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.UserCircle.ToIconString(), buttonWidth
, "Select the local player character.", !_objects.Player, true))
_identifier = _objects.Player.GetIdentifier(_actors);
, "Select the local player character.", !objects.Player, true))
_identifier = objects.Player.GetIdentifier(actors);
ImGui.SameLine();
var (id, data) = _objects.TargetData;
var (id, data) = objects.TargetData;
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.";
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;
}
}

View file

@ -4,24 +4,15 @@ using OtterGui.Widgets;
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
=> "Actors"u8;
public void DrawContent()
{
_selector.Draw(200 * ImGuiHelpers.GlobalScale);
selector.Draw(200 * ImGuiHelpers.GlobalScale);
ImGui.SameLine();
_panel.Draw();
}
public ActorTab(ActorSelector selector, ActorPanel panel)
{
_selector = selector;
_panel = panel;
panel.Draw();
}
}

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