Further improvements.

This commit is contained in:
Ottermandias 2024-02-16 17:48:40 +01:00
parent 5f74f4b4d9
commit 59529476eb
15 changed files with 342 additions and 161 deletions

View file

@ -36,6 +36,7 @@ public class Configuration : IPluginConfiguration, ISavable
public bool OpenWindowAtStart { get; set; } = false; public bool OpenWindowAtStart { get; set; } = false;
public bool UseAdvancedParameters { get; set; } = true; public bool UseAdvancedParameters { get; set; } = true;
public bool UseAdvancedDyes { get; set; } = false; public bool UseAdvancedDyes { get; set; } = false;
public bool KeepAdvancedDyesAttached { get; set; } = true;
public bool ShowRevertAdvancedParametersButton { get; set; } = true; public bool ShowRevertAdvancedParametersButton { get; set; } = true;
public bool ShowPalettePlusImport { get; set; } = true; public bool ShowPalettePlusImport { get; set; } = true;
public bool UseFloatForColors { get; set; } = true; public bool UseFloatForColors { get; set; } = true;

View file

@ -42,7 +42,7 @@ namespace Glamourer.Events
/// <summary> A characters saved state had its customize parameter changed. Data is the old value, the new value and the type [(CustomizeParameterValue, CustomizeParameterValue, CustomizeParameterFlag)]. </summary> /// <summary> A characters saved state had its customize parameter changed. Data is the old value, the new value and the type [(CustomizeParameterValue, CustomizeParameterValue, CustomizeParameterFlag)]. </summary>
Parameter, Parameter,
/// <summary> A characters saved state had a material color table value changed. Data is the old value, the new value and the index [(Vector3, Vector3, MaterialValueIndex)]. </summary> /// <summary> A characters saved state had a material color table value changed. Data is the old value, the new value and the index [(Vector3, Vector3, MaterialValueIndex)] or just the index for resets. </summary>
MaterialValue, MaterialValue,
/// <summary> A characters saved state had a design applied. This means everything may have changed. Data is the applied design. [DesignBase] </summary> /// <summary> A characters saved state had a design applied. This means everything may have changed. Data is the applied design. [DesignBase] </summary>

View file

@ -2,6 +2,7 @@
using Dalamud.Interface.Utility; using Dalamud.Interface.Utility;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using Glamourer.Events; using Glamourer.Events;
using Glamourer.Gui.Materials;
using Glamourer.Services; using Glamourer.Services;
using Glamourer.Unlocks; using Glamourer.Unlocks;
using ImGuiNET; using ImGuiNET;
@ -27,12 +28,13 @@ public class EquipmentDrawer
private readonly TextureService _textures; private readonly TextureService _textures;
private readonly Configuration _config; private readonly Configuration _config;
private readonly GPoseService _gPose; private readonly GPoseService _gPose;
private readonly AdvancedDyePopup _advancedDyes;
private float _requiredComboWidthUnscaled; private float _requiredComboWidthUnscaled;
private float _requiredComboWidth; private float _requiredComboWidth;
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, AdvancedDyePopup advancedDyes)
{ {
_items = items; _items = items;
_codes = codes; _codes = codes;
@ -283,6 +285,10 @@ public class EquipmentDrawer
ImGui.SameLine(); ImGui.SameLine();
DrawApplyStain(equipDrawData); DrawApplyStain(equipDrawData);
} }
else
{
_advancedDyes.DrawButton(equipDrawData.Slot);
}
if (VerifyRestrictedGear(equipDrawData)) if (VerifyRestrictedGear(equipDrawData))
label += " (Restricted)"; label += " (Restricted)";
@ -303,6 +309,10 @@ public class EquipmentDrawer
ImGui.SameLine(); ImGui.SameLine();
DrawApplyStain(mainhand); DrawApplyStain(mainhand);
} }
else
{
_advancedDyes.DrawButton(EquipSlot.MainHand);
}
if (allWeapons) if (allWeapons)
mainhandLabel += $" ({mainhand.CurrentItem.Type.ToName()})"; mainhandLabel += $" ({mainhand.CurrentItem.Type.ToName()})";
@ -321,6 +331,10 @@ public class EquipmentDrawer
ImGui.SameLine(); ImGui.SameLine();
DrawApplyStain(offhand); DrawApplyStain(offhand);
} }
else
{
_advancedDyes.DrawButton(EquipSlot.OffHand);
}
WeaponHelpMarker(offhandLabel); WeaponHelpMarker(offhandLabel);
} }
@ -351,6 +365,10 @@ public class EquipmentDrawer
ImGui.SameLine(); ImGui.SameLine();
DrawApplyStain(equipDrawData); DrawApplyStain(equipDrawData);
} }
else
{
_advancedDyes.DrawButton(equipDrawData.Slot);
}
if (VerifyRestrictedGear(equipDrawData)) if (VerifyRestrictedGear(equipDrawData))
{ {
@ -384,6 +402,10 @@ public class EquipmentDrawer
ImGui.SameLine(); ImGui.SameLine();
DrawApplyStain(mainhand); DrawApplyStain(mainhand);
} }
else
{
_advancedDyes.DrawButton(EquipSlot.MainHand);
}
} }
if (offhand.CurrentItem.Type is FullEquipType.Unknown) if (offhand.CurrentItem.Type is FullEquipType.Unknown)
@ -410,6 +432,10 @@ public class EquipmentDrawer
ImGui.SameLine(); ImGui.SameLine();
DrawApplyStain(offhand); DrawApplyStain(offhand);
} }
else
{
_advancedDyes.DrawButton(EquipSlot.OffHand);
}
} }
} }

View file

@ -2,6 +2,7 @@
using Dalamud.Interface.Utility; using Dalamud.Interface.Utility;
using Dalamud.Interface.Windowing; using Dalamud.Interface.Windowing;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using Glamourer.Gui.Materials;
using ImGuiNET; using ImGuiNET;
using OtterGui; using OtterGui;
using OtterGui.Raii; using OtterGui.Raii;
@ -10,12 +11,13 @@ namespace Glamourer.Gui;
public class GenericPopupWindow : Window public class GenericPopupWindow : Window
{ {
private readonly Configuration _config; private readonly Configuration _config;
private readonly ICondition _condition; private readonly AdvancedDyePopup _advancedDye;
private readonly IClientState _state; private readonly ICondition _condition;
public bool OpenFestivalPopup { get; internal set; } = false; private readonly IClientState _state;
public bool OpenFestivalPopup { get; internal set; } = false;
public GenericPopupWindow(Configuration config, IClientState state, ICondition condition) public GenericPopupWindow(Configuration config, IClientState state, ICondition condition, AdvancedDyePopup advancedDye)
: base("Glamourer Popups", : base("Glamourer Popups",
ImGuiWindowFlags.NoBringToFrontOnFocus ImGuiWindowFlags.NoBringToFrontOnFocus
| ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoDecoration
@ -29,6 +31,7 @@ public class GenericPopupWindow : Window
_config = config; _config = config;
_state = state; _state = state;
_condition = condition; _condition = condition;
_advancedDye = advancedDye;
DisableWindowSounds = true; DisableWindowSounds = true;
IsOpen = true; IsOpen = true;
} }
@ -42,6 +45,7 @@ public class GenericPopupWindow : Window
} }
DrawFestivalPopup(); DrawFestivalPopup();
//_advancedDye.Draw();
} }
private bool CheckFestivalPopupConditions() private bool CheckFestivalPopupConditions()

View file

@ -13,10 +13,18 @@ using Glamourer.Gui.Tabs.SettingsTab;
using Glamourer.Gui.Tabs.UnlocksTab; using Glamourer.Gui.Tabs.UnlocksTab;
using ImGuiNET; using ImGuiNET;
using OtterGui.Custom; using OtterGui.Custom;
using OtterGui.Services;
using OtterGui.Widgets; using OtterGui.Widgets;
namespace Glamourer.Gui; namespace Glamourer.Gui;
public class MainWindowPosition : IService
{
public bool IsOpen { get; set; }
public Vector2 Position { get; set; }
public Vector2 Size { get; set; }
}
public class MainWindow : Window, IDisposable public class MainWindow : Window, IDisposable
{ {
public enum TabType public enum TabType
@ -32,10 +40,11 @@ public class MainWindow : Window, IDisposable
Npcs = 7, Npcs = 7,
} }
private readonly Configuration _config; private readonly Configuration _config;
private readonly DesignQuickBar _quickBar; private readonly DesignQuickBar _quickBar;
private readonly TabSelected _event; private readonly TabSelected _event;
private readonly ITab[] _tabs; private readonly MainWindowPosition _position;
private readonly ITab[] _tabs;
public readonly SettingsTab Settings; public readonly SettingsTab Settings;
public readonly ActorTab Actors; public readonly ActorTab Actors;
@ -50,7 +59,7 @@ public class MainWindow : Window, IDisposable
public MainWindow(DalamudPluginInterface pi, Configuration config, SettingsTab settings, ActorTab actors, DesignTab designs, public MainWindow(DalamudPluginInterface pi, Configuration config, SettingsTab settings, ActorTab actors, DesignTab designs,
DebugTab debugTab, AutomationTab automation, UnlocksTab unlocks, TabSelected @event, MessagesTab messages, DesignQuickBar quickBar, DebugTab debugTab, AutomationTab automation, UnlocksTab unlocks, TabSelected @event, MessagesTab messages, DesignQuickBar quickBar,
NpcTab npcs) NpcTab npcs, MainWindowPosition position)
: base(GetLabel()) : base(GetLabel())
{ {
pi.UiBuilder.DisableGposeUiHide = true; pi.UiBuilder.DisableGposeUiHide = true;
@ -69,6 +78,7 @@ public class MainWindow : Window, IDisposable
Messages = messages; Messages = messages;
_quickBar = quickBar; _quickBar = quickBar;
Npcs = npcs; Npcs = npcs;
_position = position;
_config = config; _config = config;
_tabs = _tabs =
[ [
@ -90,6 +100,7 @@ public class MainWindow : Window, IDisposable
Flags = _config.Ephemeral.LockMainWindow Flags = _config.Ephemeral.LockMainWindow
? Flags | ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize ? Flags | ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize
: Flags & ~(ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize); : Flags & ~(ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize);
_position.IsOpen = IsOpen;
} }
public void Dispose() public void Dispose()
@ -98,9 +109,14 @@ public class MainWindow : Window, IDisposable
public override void Draw() public override void Draw()
{ {
var yPos = ImGui.GetCursorPosY(); var yPos = ImGui.GetCursorPosY();
_position.Size = ImGui.GetWindowSize();
_position.Position = ImGui.GetWindowPos();
if (TabBar.Draw("##tabs", ImGuiTabBarFlags.None, ToLabel(SelectTab), out var currentTab, () => { }, _tabs)) if (TabBar.Draw("##tabs", ImGuiTabBarFlags.None, ToLabel(SelectTab), out var currentTab, () => { }, _tabs))
SelectTab = TabType.None;
var tab = FromLabel(currentTab);
if (tab != _config.Ephemeral.SelectedTab)
{ {
SelectTab = TabType.None;
_config.Ephemeral.SelectedTab = FromLabel(currentTab); _config.Ephemeral.SelectedTab = FromLabel(currentTab);
_config.Ephemeral.Save(); _config.Ephemeral.Save();
} }

View file

@ -1,7 +1,9 @@
using Dalamud.Interface; using System.Reflection.Metadata.Ecma335;
using Dalamud.Interface;
using Dalamud.Interface.Utility; using Dalamud.Interface.Utility;
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
using FFXIVClientStructs.Interop;
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.Gui.Tabs.ActorTab;
using Glamourer.Interop.Material; using Glamourer.Interop.Material;
using Glamourer.Interop.Structs; using Glamourer.Interop.Structs;
using Glamourer.State; using Glamourer.State;
@ -9,148 +11,145 @@ using ImGuiNET;
using OtterGui; using OtterGui;
using OtterGui.Raii; using OtterGui.Raii;
using OtterGui.Services; using OtterGui.Services;
using Penumbra.GameData.Actors; using Penumbra.GameData.Enums;
using Penumbra.GameData.Files; using Penumbra.GameData.Files;
using Penumbra.GameData.Structs;
namespace Glamourer.Gui.Materials; namespace Glamourer.Gui.Materials;
public sealed unsafe class AdvancedDyePopup( public sealed unsafe class AdvancedDyePopup(
MainWindowPosition mainPosition,
Configuration config, Configuration config,
StateManager stateManager, StateManager stateManager,
ActorSelector actorSelector,
MaterialDrawer materials,
LiveColorTablePreviewer preview) : IService LiveColorTablePreviewer preview) : IService
{ {
private MaterialValueIndex? _drawIndex; private MaterialValueIndex? _drawIndex;
private ActorIdentifier _identifier; private ActorState _state = null!;
private ActorState? _state;
private Actor _actor; private Actor _actor;
private byte _selectedMaterial = byte.MaxValue; private byte _selectedMaterial = byte.MaxValue;
private bool ShouldBeDrawn() private bool ShouldBeDrawn()
{ {
if (!mainPosition.IsOpen)
return false;
if (!config.UseAdvancedDyes) if (!config.UseAdvancedDyes)
return false; return false;
if (config.Ephemeral.SelectedTab is not MainWindow.TabType.Actors) if (_drawIndex is not { Valid: true })
return false; return false;
if (!_drawIndex.HasValue) if (!_actor.IsCharacter || !_state.ModelData.IsHuman || !_actor.Model.IsHuman)
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 false;
return true; return true;
} }
public void DrawButton(ActorIdentifier identifier, ActorState state, MaterialValueIndex index) public void DrawButton(EquipSlot slot)
{ => DrawButton(MaterialValueIndex.FromSlot(slot));
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() private void DrawButton(MaterialValueIndex index)
{ {
if (!ShouldBeDrawn()) if (!config.UseAdvancedDyes)
return; return;
var position = mainPosition.Position; ImGui.SameLine();
position.X += mainPosition.Size.X; using var id = ImRaii.PushId(index.SlotIndex | ((int)index.DrawObject << 8));
position.Y += ImGui.GetFrameHeightWithSpacing() * 3; var isOpen = index == _drawIndex;
var size = new Vector2(3 * ImGui.GetFrameHeight() + 300 * ImGuiHelpers.GlobalScale, 18.5f * ImGui.GetFrameHeightWithSpacing());
ImGui.SetNextWindowPos(position); using (ImRaii.PushColor(ImGuiCol.Button, ImGui.GetColorU32(ImGuiCol.ButtonActive), isOpen)
ImGui.SetNextWindowSize(size); .Push(ImGuiCol.Text, ColorId.HeaderButtons.Value(), isOpen)
var window = ImGui.Begin("###Glamourer Advanced Dyes", .Push(ImGuiCol.Border, ColorId.HeaderButtons.Value(), isOpen))
ImGuiWindowFlags.NoFocusOnAppearing {
using var frame = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, 2 * ImGuiHelpers.GlobalScale, isOpen);
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Palette.ToIconString(), new Vector2(ImGui.GetFrameHeight()),
string.Empty, false, true))
{
_selectedMaterial = byte.MaxValue;
_drawIndex = isOpen ? null : index;
}
}
ImGuiUtil.HoverTooltip("Open advanced dyes for this slot.");
}
private void DrawTabBar(ReadOnlySpan<Pointer<Texture>> textures, ref bool firstAvailable)
{
using var bar = ImRaii.TabBar("tabs");
if (!bar)
return;
for (byte i = 0; i < MaterialService.MaterialsPerModel; ++i)
{
var index = _drawIndex!.Value with { MaterialIndex = i };
var available = index.TryGetTexture(textures, out var texture) && index.TryGetColorTable(texture, out var table);
if (index == preview.LastValueIndex with { RowIndex = 0 })
table = preview.LastOriginalColorTable;
using var disable = ImRaii.Disabled(!available);
var select = available && firstAvailable && _selectedMaterial == byte.MaxValue
? ImGuiTabItemFlags.SetSelected
: ImGuiTabItemFlags.None;
if (available)
firstAvailable = false;
using var tab = _label.TabItem(i, select);
if (!available)
{
using var disabled = ImRaii.Enabled();
ImGuiUtil.HoverTooltip("This material does not exist or does not have an associated color set.",
ImGuiHoveredFlags.AllowWhenDisabled);
}
if ((tab.Success || select is ImGuiTabItemFlags.SetSelected) && available)
{
_selectedMaterial = i;
DrawTable(index, table);
}
}
using (ImRaii.PushFont(UiBuilder.IconFont))
{
if (ImGui.TabItemButton($"{FontAwesomeIcon.Times.ToIconString()} ", ImGuiTabItemFlags.NoTooltip))
_drawIndex = null;
}
ImGuiUtil.HoverTooltip("Close the advanced dye window.");
}
private void DrawContent(ReadOnlySpan<Pointer<Texture>> textures)
{
var firstAvailable = true;
DrawTabBar(textures, ref firstAvailable);
if (firstAvailable)
ImGui.TextUnformatted("No Editable Materials available.");
}
private void DrawWindow(ReadOnlySpan<Pointer<Texture>> textures)
{
var flags = ImGuiWindowFlags.NoFocusOnAppearing
| ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoCollapse
| ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoDecoration
| ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize;
| ImGuiWindowFlags.NoResize);
// Set position to the right of the main window when attached
// The downwards offset is implicit through child position.
if (config.KeepAdvancedDyesAttached)
{
var position = ImGui.GetWindowPos();
position.X += ImGui.GetWindowSize().X;
ImGui.SetNextWindowPos(position);
flags |= ImGuiWindowFlags.NoMove;
}
var size = new Vector2(7 * ImGui.GetFrameHeight() + 3 * ImGui.GetStyle().ItemInnerSpacing.X + 300 * ImGuiHelpers.GlobalScale,
17f * ImGui.GetFrameHeightWithSpacing() + ImGui.GetStyle().WindowPadding.Y);
ImGui.SetNextWindowSize(size);
var window = ImGui.Begin("###Glamourer Advanced Dyes", flags);
try try
{ {
if (!window) if (window)
return; DrawContent(textures);
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 finally
{ {
@ -158,37 +157,55 @@ public sealed unsafe class AdvancedDyePopup(
} }
} }
public unsafe void Draw(Actor actor, ActorState state)
{
_actor = actor;
_state = state;
if (!ShouldBeDrawn())
return;
if (_drawIndex!.Value.TryGetTextures(actor, out var textures))
DrawWindow(textures);
}
private void DrawTable(MaterialValueIndex materialIndex, in MtrlFile.ColorTable table) private void DrawTable(MaterialValueIndex materialIndex, in MtrlFile.ColorTable table)
{ {
using var disabled = ImRaii.Disabled(_state.IsLocked);
for (byte i = 0; i < MtrlFile.ColorTable.NumRows; ++i) for (byte i = 0; i < MtrlFile.ColorTable.NumRows; ++i)
{ {
var index = materialIndex with { RowIndex = i }; var index = materialIndex with { RowIndex = i };
ref var row = ref table[i]; ref var row = ref table[i];
DrawRow(ref row, CharacterWeapon.Empty, index, table); DrawRow(ref row, index, table);
} }
} }
private void DrawRow(ref MtrlFile.ColorTable.Row row, CharacterWeapon drawData, MaterialValueIndex index, in MtrlFile.ColorTable table) private void DrawRow(ref MtrlFile.ColorTable.Row row, MaterialValueIndex index, in MtrlFile.ColorTable table)
{ {
using var id = ImRaii.PushId(index.RowIndex); using var id = ImRaii.PushId(index.RowIndex);
var changed = _state!.Materials.TryGetValue(index, out var value); var changed = _state!.Materials.TryGetValue(index, out var value);
if (!changed) if (!changed)
{ {
var internalRow = new ColorRow(row); var internalRow = new ColorRow(row);
value = new MaterialValueState(internalRow, internalRow, drawData, StateSource.Manual); var slot = index.ToSlot();
var weapon = slot is EquipSlot.MainHand or EquipSlot.OffHand
? _state.ModelData.Weapon(slot)
: _state.ModelData.Armor(slot).ToWeapon(0);
value = new MaterialValueState(internalRow, internalRow, weapon, StateSource.Manual);
} }
var buttonSize = new Vector2(ImGui.GetFrameHeight());
ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Crosshairs.ToIconString(), buttonSize, "Highlight the affected colors on the character.",
false, true);
if (ImGui.IsItemHovered())
preview.OnHover(index, _actor.Index, table);
ImGui.SameLine();
ImGui.AlignTextToFramePadding(); ImGui.AlignTextToFramePadding();
using (ImRaii.PushFont(UiBuilder.MonoFont)) using (ImRaii.PushFont(UiBuilder.MonoFont))
{ {
ImGui.TextUnformatted($"Row {index.RowIndex + 1:D2}"); 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); ImGui.SameLine(0, ImGui.GetStyle().ItemSpacing.X * 2);
var applied = ImGuiUtil.ColorPicker("##diffuse", "Change the diffuse value for this row.", value.Model.Diffuse, var applied = ImGuiUtil.ColorPicker("##diffuse", "Change the diffuse value for this row.", value.Model.Diffuse,
v => value.Model.Diffuse = v, "D"); v => value.Model.Diffuse = v, "D");
@ -209,16 +226,54 @@ public sealed unsafe class AdvancedDyePopup(
ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale); ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale);
applied |= ImGui.DragFloat("##Specular Strength", ref value.Model.SpecularStrength, 0.01f, float.MinValue, float.MaxValue, "%.3f SS"); 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."); ImGuiUtil.HoverTooltip("Change the specular strength for this row.");
ImGui.SameLine(0, spacing.X);
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Clipboard.ToIconString(), buttonSize, "Export this row to your clipboard.", false,
true))
ColorRowClipboard.Row = value.Model;
ImGui.SameLine(0, spacing.X);
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Paste.ToIconString(), buttonSize,
"Import an exported row from your clipboard onto this row.", !ColorRowClipboard.IsSet, true))
{
value.Model = ColorRowClipboard.Row;
applied = true;
}
ImGui.SameLine(0, spacing.X);
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.UndoAlt.ToIconString(), buttonSize, "Reset this row to game state.", !changed, true))
stateManager.ResetMaterialValue(_state, index, ApplySettings.Game);
if (applied) if (applied)
stateManager.ChangeMaterialValue(_state!, index, value, ApplySettings.Manual); stateManager.ChangeMaterialValue(_state!, index, value, ApplySettings.Manual);
if (changed) }
private LabelStruct _label = new();
private struct LabelStruct
{
private fixed byte _label[12];
public ImRaii.IEndObject TabItem(byte materialIndex, ImGuiTabItemFlags flags)
{ {
ImGui.SameLine(0, spacing.X); _label[10] = (byte)('1' + materialIndex);
using (ImRaii.PushFont(UiBuilder.IconFont)) fixed (byte* ptr = _label)
{ {
using var color = ImRaii.PushColor(ImGuiCol.Text, ColorId.FavoriteStarOn.Value()); return ImRaii.TabItem(ptr, flags | ImGuiTabItemFlags.NoTooltip);
ImGui.TextUnformatted(FontAwesomeIcon.UserEdit.ToIconString());
} }
} }
public LabelStruct()
{
_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;
}
} }
} }

View file

@ -0,0 +1,20 @@
using Glamourer.Interop.Material;
namespace Glamourer.Gui.Materials;
public static class ColorRowClipboard
{
private static ColorRow _row;
public static bool IsSet { get; private set; }
public static ColorRow Row
{
get => _row;
set
{
IsSet = true;
_row = value;
}
}
}

View file

@ -32,12 +32,13 @@ public unsafe class MaterialDrawer(StateManager _stateManager, DesignManager _de
if (!table) if (!table)
return; return;
ImGui.TableSetupColumn("button", ImGuiTableColumnFlags.WidthFixed, buttonSize.X); ImGui.TableSetupColumn("buttons", ImGuiTableColumnFlags.WidthFixed, buttonSize.X * 3 + 2 * ImGui.GetStyle().ItemInnerSpacing.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.TableSetupColumn("revert", ImGuiTableColumnFlags.WidthFixed, buttonSize.X + ImGui.CalcTextSize("Revertm").X); ImGui.GetStyle().ItemInnerSpacing.X * 4 + 3 * buttonSize.X + 220 * ImGuiHelpers.GlobalScale);
ImGui.TableSetupColumn("slot", ImGuiTableColumnFlags.WidthStretch); 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) for (var i = 0; i < design.Materials.Count; ++i)
{ {
var (idx, value) = design.Materials[i]; var (idx, value) = design.Materials[i];
@ -64,6 +65,17 @@ public unsafe class MaterialDrawer(StateManager _stateManager, DesignManager _de
--i; --i;
} }
ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X);
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Clipboard.ToIconString(), buttonSize, "Export this row to your clipboard.",
false,
true))
ColorRowClipboard.Row = value.Value;
ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X);
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Paste.ToIconString(), buttonSize,
"Import an exported row from your clipboard onto this row.", !ColorRowClipboard.IsSet, true))
_designManager.ChangeMaterialValue(design, key, ColorRowClipboard.Row);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
var enabled = value.Enabled; var enabled = value.Enabled;
if (ImGui.Checkbox("Enabled", ref enabled)) if (ImGui.Checkbox("Enabled", ref enabled))
@ -233,16 +245,20 @@ public unsafe class MaterialDrawer(StateManager _stateManager, DesignManager _de
} }
ImGui.SameLine(0, ImGui.GetStyle().ItemSpacing.X * 2); 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 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; var spacing = ImGui.GetStyle().ItemInnerSpacing;
ImGui.SameLine(0, spacing.X); 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"); 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); 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"); 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.SameLine(0, spacing.X);
ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale); 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; 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."); ImGuiUtil.HoverTooltip("Change the gloss strength for this row.");
ImGui.SameLine(0, spacing.X); ImGui.SameLine(0, spacing.X);
ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale); ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale);

View file

@ -36,7 +36,8 @@ public class ActorPanel(
ICondition _conditions, ICondition _conditions,
DictModelChara _modelChara, DictModelChara _modelChara,
CustomizeParameterDrawer _parameterDrawer, CustomizeParameterDrawer _parameterDrawer,
MaterialDrawer _materialDrawer) MaterialDrawer _materialDrawer,
AdvancedDyePopup _advancedDyes)
{ {
private ActorIdentifier _identifier; private ActorIdentifier _identifier;
private string _actorName = string.Empty; private string _actorName = string.Empty;
@ -117,13 +118,12 @@ public class ActorPanel(
RevertButtons(); RevertButtons();
if (_config.UseAdvancedDyes && ImGui.CollapsingHeader("Material Shit"))
_materialDrawer.DrawActorPanel(_actor);
using var disabled = ImRaii.Disabled(transformationId != 0); using var disabled = ImRaii.Disabled(transformationId != 0);
if (_state.ModelData.IsHuman) if (_state.ModelData.IsHuman)
DrawHumanPanel(); DrawHumanPanel();
else else
DrawMonsterPanel(); DrawMonsterPanel();
_advancedDyes.Draw(_actor, _state);
} }
private void DrawHumanPanel() private void DrawHumanPanel()
@ -320,7 +320,7 @@ public class ActorPanel(
private void SaveDesignOpen() private void SaveDesignOpen()
{ {
ImGui.OpenPopup("Save as Design"); ImGui.OpenPopup("Save as Design");
_newName = _state!.Identifier.ToName(); _newName = _state!.Identifier.ToName();
_newDesign = _converter.Convert(_state, ApplicationRules.FromModifiers(_state)); _newDesign = _converter.Convert(_state, ApplicationRules.FromModifiers(_state));
} }

View file

@ -189,6 +189,11 @@ public class SettingsTab(
PaletteImportButton(); PaletteImportButton();
} }
if (config.UseAdvancedDyes)
Checkbox("Keep Advanced Dye Window Attached",
"Keeps the advanced dye window expansion attached to the main window, or makes it freely movable.",
config.KeepAdvancedDyesAttached, v => config.KeepAdvancedDyesAttached = v);
Checkbox("Debug Mode", "Show the debug tab. Only useful for debugging or advanced use. Not recommended in general.", config.DebugMode, Checkbox("Debug Mode", "Show the debug tab. Only useful for debugging or advanced use. Not recommended in general.", config.DebugMode,
v => config.DebugMode = v); v => config.DebugMode = v);
ImGui.NewLine(); ImGui.NewLine();

View file

@ -69,7 +69,6 @@ public sealed unsafe class LiveColorTablePreviewer : IService, IDisposable
if (_valueIndex.TryGetTexture(actor, out var texture)) if (_valueIndex.TryGetTexture(actor, out var texture))
{ {
Glamourer.Log.Information($"Set {_objectIndex} {_valueIndex}");
var diffuse = CalculateDiffuse(); var diffuse = CalculateDiffuse();
var table = LastOriginalColorTable; var table = LastOriginalColorTable;
table[_valueIndex.RowIndex].Diffuse = diffuse; table[_valueIndex.RowIndex].Diffuse = diffuse;

View file

@ -110,7 +110,7 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable
} }
foreach (var idx in deleteList) foreach (var idx in deleteList)
_stateManager.ChangeMaterialValue(state, idx, default, ApplySettings.Game); _stateManager.ResetMaterialValue(state, idx, ApplySettings.Game);
} }
/// <summary> /// <summary>

View file

@ -15,6 +15,7 @@ public readonly record struct MaterialValueIndex(
byte RowIndex) byte RowIndex)
{ {
public static readonly MaterialValueIndex Invalid = new(DrawObjectType.Invalid, 0, 0, 0); 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);
@ -27,6 +28,29 @@ public readonly record struct MaterialValueIndex(
return index.Valid; return index.Valid;
} }
public static MaterialValueIndex FromSlot(EquipSlot slot)
{
if (slot is EquipSlot.MainHand)
return new MaterialValueIndex(DrawObjectType.Mainhand, 0, 0, 0);
if (slot is EquipSlot.OffHand)
return new MaterialValueIndex(DrawObjectType.Offhand, 0, 0, 0);
var idx = slot.ToIndex();
if (idx < 10)
return new MaterialValueIndex(DrawObjectType.Human, (byte)idx, 0, 0);
return Invalid;
}
public EquipSlot ToSlot()
=> DrawObject switch
{
DrawObjectType.Human when SlotIndex < 10 => ((uint)SlotIndex).ToEquipSlot(),
DrawObjectType.Mainhand when SlotIndex == 0 => EquipSlot.MainHand,
DrawObjectType.Offhand when SlotIndex == 0 => EquipSlot.OffHand,
_ => EquipSlot.Unknown,
};
public unsafe bool TryGetModel(Actor actor, out Model model) public unsafe bool TryGetModel(Actor actor, out Model model)
{ {
if (!actor.Valid) if (!actor.Valid)
@ -154,14 +178,12 @@ public readonly record struct MaterialValueIndex(
{ } { }
public override string ToString() public override string ToString()
=> DrawObject switch {
{ var slot = ToSlot();
DrawObjectType.Human when SlotIndex < 10 => return slot is EquipSlot.Unknown
$"{((uint)SlotIndex).ToEquipSlot().ToName()} Material #{MaterialIndex + 1} Row #{RowIndex + 1}", ? $"{DrawObject} Slot {SlotIndex} Material #{MaterialIndex + 1} Row #{RowIndex + 1}"
DrawObjectType.Mainhand when SlotIndex == 0 => $"{EquipSlot.MainHand.ToName()} Material #{MaterialIndex + 1} Row #{RowIndex + 1}", : $"{slot.ToName()} Material #{MaterialIndex + 1} Row #{RowIndex + 1}";
DrawObjectType.Offhand when SlotIndex == 0 => $"{EquipSlot.OffHand.ToName()} Material #{MaterialIndex + 1} Row #{RowIndex + 1}", }
_ => $"{DrawObject} Slot {SlotIndex} Material #{MaterialIndex + 1} Row #{RowIndex + 1}",
};
private class Converter : JsonConverter<MaterialValueIndex> private class Converter : JsonConverter<MaterialValueIndex>
{ {

View file

@ -222,7 +222,8 @@ public class InternalStateEditor(
} }
/// <summary> Change the value of a single material color table entry. </summary> /// <summary> Change the value of a single material color table entry. </summary>
public bool ChangeMaterialValue(ActorState state, MaterialValueIndex index, in MaterialValueState newValue, StateSource source, out ColorRow? oldValue, public bool ChangeMaterialValue(ActorState state, MaterialValueIndex index, in MaterialValueState newValue, StateSource source,
out ColorRow? oldValue,
uint key = 0) uint key = 0)
{ {
// We already have an existing value. // We already have an existing value.
@ -254,6 +255,10 @@ public class InternalStateEditor(
return state.Materials.TryAddValue(index, newValue); return state.Materials.TryAddValue(index, newValue);
} }
/// <summary> Reset the value of a single material color table entry. </summary>
public bool ResetMaterialValue(ActorState state, MaterialValueIndex index, uint key = 0)
=> state.CanUnlock(key) && state.Materials.RemoveValue(index);
public bool ChangeMetaState(ActorState state, MetaIndex index, bool value, StateSource source, out bool oldValue, public bool ChangeMetaState(ActorState state, MetaIndex index, bool value, StateSource source, out bool oldValue,
uint key = 0) uint key = 0)
{ {

View file

@ -177,6 +177,18 @@ public class StateEditor(
StateChanged.Invoke(StateChanged.Type.MaterialValue, settings.Source, state, actors, (oldValue, newValue.Game, index)); StateChanged.Invoke(StateChanged.Type.MaterialValue, settings.Source, state, actors, (oldValue, newValue.Game, index));
} }
public void ResetMaterialValue(object data, MaterialValueIndex index, ApplySettings settings)
{
var state = (ActorState)data;
if (!Editor.ResetMaterialValue(state, index, settings.Key))
return;
var actors = Applier.ChangeMaterialValue(state, index, true);
Glamourer.Log.Verbose(
$"Reset material value in state {state.Identifier.Incognito(null)} to game value. [Affecting {actors.ToLazyString("nothing")}.]");
StateChanged.Invoke(StateChanged.Type.MaterialValue, settings.Source, state, actors, index);
}
/// <inheritdoc/> /// <inheritdoc/>
public void ChangeMetaState(object data, MetaIndex index, bool value, ApplySettings settings) public void ChangeMetaState(object data, MetaIndex index, bool value, ApplySettings settings)
{ {