diff --git a/Glamourer/Configuration.cs b/Glamourer/Configuration.cs
index 676cacc..a0d5ff2 100644
--- a/Glamourer/Configuration.cs
+++ b/Glamourer/Configuration.cs
@@ -36,6 +36,7 @@ public class Configuration : IPluginConfiguration, ISavable
public bool OpenWindowAtStart { get; set; } = false;
public bool UseAdvancedParameters { get; set; } = true;
public bool UseAdvancedDyes { get; set; } = false;
+ public bool KeepAdvancedDyesAttached { get; set; } = true;
public bool ShowRevertAdvancedParametersButton { get; set; } = true;
public bool ShowPalettePlusImport { get; set; } = true;
public bool UseFloatForColors { get; set; } = true;
diff --git a/Glamourer/Events/StateChanged.cs b/Glamourer/Events/StateChanged.cs
index e5e0cb5..ad2ab7b 100644
--- a/Glamourer/Events/StateChanged.cs
+++ b/Glamourer/Events/StateChanged.cs
@@ -42,7 +42,7 @@ namespace Glamourer.Events
/// A characters saved state had its customize parameter changed. Data is the old value, the new value and the type [(CustomizeParameterValue, CustomizeParameterValue, CustomizeParameterFlag)].
Parameter,
- /// 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)].
+ /// 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.
MaterialValue,
/// A characters saved state had a design applied. This means everything may have changed. Data is the applied design. [DesignBase]
diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.cs
index 25732e0..0e47839 100644
--- a/Glamourer/Gui/Equipment/EquipmentDrawer.cs
+++ b/Glamourer/Gui/Equipment/EquipmentDrawer.cs
@@ -2,6 +2,7 @@
using Dalamud.Interface.Utility;
using Dalamud.Plugin.Services;
using Glamourer.Events;
+using Glamourer.Gui.Materials;
using Glamourer.Services;
using Glamourer.Unlocks;
using ImGuiNET;
@@ -27,12 +28,13 @@ public class EquipmentDrawer
private readonly TextureService _textures;
private readonly Configuration _config;
private readonly GPoseService _gPose;
+ private readonly AdvancedDyePopup _advancedDyes;
private float _requiredComboWidthUnscaled;
private float _requiredComboWidth;
public EquipmentDrawer(FavoriteManager favorites, IDataManager gameData, ItemManager items, CodeService codes, TextureService textures,
- Configuration config, GPoseService gPose)
+ Configuration config, GPoseService gPose, AdvancedDyePopup advancedDyes)
{
_items = items;
_codes = codes;
@@ -283,6 +285,10 @@ public class EquipmentDrawer
ImGui.SameLine();
DrawApplyStain(equipDrawData);
}
+ else
+ {
+ _advancedDyes.DrawButton(equipDrawData.Slot);
+ }
if (VerifyRestrictedGear(equipDrawData))
label += " (Restricted)";
@@ -303,6 +309,10 @@ public class EquipmentDrawer
ImGui.SameLine();
DrawApplyStain(mainhand);
}
+ else
+ {
+ _advancedDyes.DrawButton(EquipSlot.MainHand);
+ }
if (allWeapons)
mainhandLabel += $" ({mainhand.CurrentItem.Type.ToName()})";
@@ -321,6 +331,10 @@ public class EquipmentDrawer
ImGui.SameLine();
DrawApplyStain(offhand);
}
+ else
+ {
+ _advancedDyes.DrawButton(EquipSlot.OffHand);
+ }
WeaponHelpMarker(offhandLabel);
}
@@ -351,6 +365,10 @@ public class EquipmentDrawer
ImGui.SameLine();
DrawApplyStain(equipDrawData);
}
+ else
+ {
+ _advancedDyes.DrawButton(equipDrawData.Slot);
+ }
if (VerifyRestrictedGear(equipDrawData))
{
@@ -384,6 +402,10 @@ public class EquipmentDrawer
ImGui.SameLine();
DrawApplyStain(mainhand);
}
+ else
+ {
+ _advancedDyes.DrawButton(EquipSlot.MainHand);
+ }
}
if (offhand.CurrentItem.Type is FullEquipType.Unknown)
@@ -410,6 +432,10 @@ public class EquipmentDrawer
ImGui.SameLine();
DrawApplyStain(offhand);
}
+ else
+ {
+ _advancedDyes.DrawButton(EquipSlot.OffHand);
+ }
}
}
diff --git a/Glamourer/Gui/GenericPopupWindow.cs b/Glamourer/Gui/GenericPopupWindow.cs
index 77257b5..502af14 100644
--- a/Glamourer/Gui/GenericPopupWindow.cs
+++ b/Glamourer/Gui/GenericPopupWindow.cs
@@ -2,6 +2,7 @@
using Dalamud.Interface.Utility;
using Dalamud.Interface.Windowing;
using Dalamud.Plugin.Services;
+using Glamourer.Gui.Materials;
using ImGuiNET;
using OtterGui;
using OtterGui.Raii;
@@ -10,12 +11,13 @@ namespace Glamourer.Gui;
public class GenericPopupWindow : Window
{
- private readonly Configuration _config;
- private readonly ICondition _condition;
- private readonly IClientState _state;
- public bool OpenFestivalPopup { get; internal set; } = false;
+ private readonly Configuration _config;
+ private readonly AdvancedDyePopup _advancedDye;
+ private readonly ICondition _condition;
+ 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",
ImGuiWindowFlags.NoBringToFrontOnFocus
| ImGuiWindowFlags.NoDecoration
@@ -29,6 +31,7 @@ public class GenericPopupWindow : Window
_config = config;
_state = state;
_condition = condition;
+ _advancedDye = advancedDye;
DisableWindowSounds = true;
IsOpen = true;
}
@@ -42,6 +45,7 @@ public class GenericPopupWindow : Window
}
DrawFestivalPopup();
+ //_advancedDye.Draw();
}
private bool CheckFestivalPopupConditions()
diff --git a/Glamourer/Gui/MainWindow.cs b/Glamourer/Gui/MainWindow.cs
index cb21a91..d9a6e1f 100644
--- a/Glamourer/Gui/MainWindow.cs
+++ b/Glamourer/Gui/MainWindow.cs
@@ -13,10 +13,18 @@ using Glamourer.Gui.Tabs.SettingsTab;
using Glamourer.Gui.Tabs.UnlocksTab;
using ImGuiNET;
using OtterGui.Custom;
+using OtterGui.Services;
using OtterGui.Widgets;
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 enum TabType
@@ -32,10 +40,11 @@ public class MainWindow : Window, IDisposable
Npcs = 7,
}
- private readonly Configuration _config;
- private readonly DesignQuickBar _quickBar;
- private readonly TabSelected _event;
- private readonly ITab[] _tabs;
+ private readonly Configuration _config;
+ private readonly DesignQuickBar _quickBar;
+ private readonly TabSelected _event;
+ private readonly MainWindowPosition _position;
+ private readonly ITab[] _tabs;
public readonly SettingsTab Settings;
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,
DebugTab debugTab, AutomationTab automation, UnlocksTab unlocks, TabSelected @event, MessagesTab messages, DesignQuickBar quickBar,
- NpcTab npcs)
+ NpcTab npcs, MainWindowPosition position)
: base(GetLabel())
{
pi.UiBuilder.DisableGposeUiHide = true;
@@ -69,6 +78,7 @@ public class MainWindow : Window, IDisposable
Messages = messages;
_quickBar = quickBar;
Npcs = npcs;
+ _position = position;
_config = config;
_tabs =
[
@@ -90,6 +100,7 @@ public class MainWindow : Window, IDisposable
Flags = _config.Ephemeral.LockMainWindow
? Flags | ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize
: Flags & ~(ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize);
+ _position.IsOpen = IsOpen;
}
public void Dispose()
@@ -98,9 +109,14 @@ public class MainWindow : Window, IDisposable
public override void Draw()
{
var yPos = ImGui.GetCursorPosY();
+ _position.Size = ImGui.GetWindowSize();
+ _position.Position = ImGui.GetWindowPos();
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.Save();
}
diff --git a/Glamourer/Gui/Materials/AdvancedDyePopup.cs b/Glamourer/Gui/Materials/AdvancedDyePopup.cs
index 4f4c76f..eadf321 100644
--- a/Glamourer/Gui/Materials/AdvancedDyePopup.cs
+++ b/Glamourer/Gui/Materials/AdvancedDyePopup.cs
@@ -1,7 +1,9 @@
-using Dalamud.Interface;
+using System.Reflection.Metadata.Ecma335;
+using Dalamud.Interface;
using Dalamud.Interface.Utility;
+using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
+using FFXIVClientStructs.Interop;
using Glamourer.Designs;
-using Glamourer.Gui.Tabs.ActorTab;
using Glamourer.Interop.Material;
using Glamourer.Interop.Structs;
using Glamourer.State;
@@ -9,148 +11,145 @@ using ImGuiNET;
using OtterGui;
using OtterGui.Raii;
using OtterGui.Services;
-using Penumbra.GameData.Actors;
+using Penumbra.GameData.Enums;
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 ActorState _state = null!;
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)
+ if (_drawIndex is not { Valid: true })
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)
+ if (!_actor.IsCharacter || !_state.ModelData.IsHuman || !_actor.Model.IsHuman)
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 void DrawButton(EquipSlot slot)
+ => DrawButton(MaterialValueIndex.FromSlot(slot));
- public unsafe void Draw()
+ private void DrawButton(MaterialValueIndex index)
{
- if (!ShouldBeDrawn())
+ if (!config.UseAdvancedDyes)
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
+ ImGui.SameLine();
+ using var id = ImRaii.PushId(index.SlotIndex | ((int)index.DrawObject << 8));
+ var isOpen = index == _drawIndex;
+
+ using (ImRaii.PushColor(ImGuiCol.Button, ImGui.GetColorU32(ImGuiCol.ButtonActive), isOpen)
+ .Push(ImGuiCol.Text, ColorId.HeaderButtons.Value(), isOpen)
+ .Push(ImGuiCol.Border, ColorId.HeaderButtons.Value(), isOpen))
+ {
+ 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> 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> textures)
+ {
+ var firstAvailable = true;
+ DrawTabBar(textures, ref firstAvailable);
+
+ if (firstAvailable)
+ ImGui.TextUnformatted("No Editable Materials available.");
+ }
+
+ private void DrawWindow(ReadOnlySpan> textures)
+ {
+ var flags = ImGuiWindowFlags.NoFocusOnAppearing
| ImGuiWindowFlags.NoCollapse
| 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
{
- if (!window)
- return;
-
- using var bar = ImRaii.TabBar("tabs");
- if (!bar)
- return;
-
- var model = _actor.Model.AsCharacterBase;
- var firstAvailable = true;
- Span 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);
- }
- }
+ if (window)
+ DrawContent(textures);
}
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)
{
+ using var disabled = ImRaii.Disabled(_state.IsLocked);
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);
+ 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);
var changed = _state!.Materials.TryGetValue(index, out var value);
if (!changed)
{
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();
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");
@@ -209,16 +226,54 @@ public sealed unsafe class AdvancedDyePopup(
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.");
+ 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)
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);
- using (ImRaii.PushFont(UiBuilder.IconFont))
+ _label[10] = (byte)('1' + materialIndex);
+ fixed (byte* ptr = _label)
{
- using var color = ImRaii.PushColor(ImGuiCol.Text, ColorId.FavoriteStarOn.Value());
- ImGui.TextUnformatted(FontAwesomeIcon.UserEdit.ToIconString());
+ return ImRaii.TabItem(ptr, flags | ImGuiTabItemFlags.NoTooltip);
}
}
+
+ 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;
+ }
}
}
diff --git a/Glamourer/Gui/Materials/ColorRowClipboard.cs b/Glamourer/Gui/Materials/ColorRowClipboard.cs
new file mode 100644
index 0000000..74c1c68
--- /dev/null
+++ b/Glamourer/Gui/Materials/ColorRowClipboard.cs
@@ -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;
+ }
+ }
+}
diff --git a/Glamourer/Gui/Materials/MaterialDrawer.cs b/Glamourer/Gui/Materials/MaterialDrawer.cs
index c3efa1f..b5f9d58 100644
--- a/Glamourer/Gui/Materials/MaterialDrawer.cs
+++ b/Glamourer/Gui/Materials/MaterialDrawer.cs
@@ -32,12 +32,13 @@ public unsafe class MaterialDrawer(StateManager _stateManager, DesignManager _de
if (!table)
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("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);
-
+ 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];
@@ -64,6 +65,17 @@ public unsafe class MaterialDrawer(StateManager _stateManager, DesignManager _de
--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();
var enabled = value.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);
- 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;
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);
- 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.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.");
ImGui.SameLine(0, spacing.X);
ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale);
diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs
index e1c8356..25b71b3 100644
--- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs
+++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs
@@ -36,7 +36,8 @@ public class ActorPanel(
ICondition _conditions,
DictModelChara _modelChara,
CustomizeParameterDrawer _parameterDrawer,
- MaterialDrawer _materialDrawer)
+ MaterialDrawer _materialDrawer,
+ AdvancedDyePopup _advancedDyes)
{
private ActorIdentifier _identifier;
private string _actorName = string.Empty;
@@ -117,13 +118,12 @@ public class ActorPanel(
RevertButtons();
- if (_config.UseAdvancedDyes && ImGui.CollapsingHeader("Material Shit"))
- _materialDrawer.DrawActorPanel(_actor);
using var disabled = ImRaii.Disabled(transformationId != 0);
if (_state.ModelData.IsHuman)
DrawHumanPanel();
else
DrawMonsterPanel();
+ _advancedDyes.Draw(_actor, _state);
}
private void DrawHumanPanel()
@@ -320,7 +320,7 @@ public class ActorPanel(
private void SaveDesignOpen()
{
ImGui.OpenPopup("Save as Design");
- _newName = _state!.Identifier.ToName();
+ _newName = _state!.Identifier.ToName();
_newDesign = _converter.Convert(_state, ApplicationRules.FromModifiers(_state));
}
diff --git a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs
index 5d0fcca..f56a988 100644
--- a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs
+++ b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs
@@ -189,6 +189,11 @@ public class SettingsTab(
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,
v => config.DebugMode = v);
ImGui.NewLine();
diff --git a/Glamourer/Interop/Material/LiveColorTablePreviewer.cs b/Glamourer/Interop/Material/LiveColorTablePreviewer.cs
index 752101a..7b10829 100644
--- a/Glamourer/Interop/Material/LiveColorTablePreviewer.cs
+++ b/Glamourer/Interop/Material/LiveColorTablePreviewer.cs
@@ -69,7 +69,6 @@ public sealed unsafe class LiveColorTablePreviewer : IService, IDisposable
if (_valueIndex.TryGetTexture(actor, out var texture))
{
- Glamourer.Log.Information($"Set {_objectIndex} {_valueIndex}");
var diffuse = CalculateDiffuse();
var table = LastOriginalColorTable;
table[_valueIndex.RowIndex].Diffuse = diffuse;
diff --git a/Glamourer/Interop/Material/MaterialManager.cs b/Glamourer/Interop/Material/MaterialManager.cs
index 38e45fe..40af5a2 100644
--- a/Glamourer/Interop/Material/MaterialManager.cs
+++ b/Glamourer/Interop/Material/MaterialManager.cs
@@ -110,7 +110,7 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable
}
foreach (var idx in deleteList)
- _stateManager.ChangeMaterialValue(state, idx, default, ApplySettings.Game);
+ _stateManager.ResetMaterialValue(state, idx, ApplySettings.Game);
}
///
diff --git a/Glamourer/Interop/Material/MaterialValueIndex.cs b/Glamourer/Interop/Material/MaterialValueIndex.cs
index 13c7577..a53e162 100644
--- a/Glamourer/Interop/Material/MaterialValueIndex.cs
+++ b/Glamourer/Interop/Material/MaterialValueIndex.cs
@@ -15,6 +15,7 @@ public readonly record struct MaterialValueIndex(
byte RowIndex)
{
public static readonly MaterialValueIndex Invalid = new(DrawObjectType.Invalid, 0, 0, 0);
+
public uint Key
=> ToKey(DrawObject, SlotIndex, MaterialIndex, RowIndex);
@@ -27,6 +28,29 @@ public readonly record struct MaterialValueIndex(
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)
{
if (!actor.Valid)
@@ -154,14 +178,12 @@ public readonly record struct MaterialValueIndex(
{ }
public override string ToString()
- => DrawObject switch
- {
- DrawObjectType.Human when SlotIndex < 10 =>
- $"{((uint)SlotIndex).ToEquipSlot().ToName()} Material #{MaterialIndex + 1} Row #{RowIndex + 1}",
- DrawObjectType.Mainhand when SlotIndex == 0 => $"{EquipSlot.MainHand.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}",
- };
+ {
+ var slot = ToSlot();
+ return slot is EquipSlot.Unknown
+ ? $"{DrawObject} Slot {SlotIndex} Material #{MaterialIndex + 1} Row #{RowIndex + 1}"
+ : $"{slot.ToName()} Material #{MaterialIndex + 1} Row #{RowIndex + 1}";
+ }
private class Converter : JsonConverter
{
diff --git a/Glamourer/State/InternalStateEditor.cs b/Glamourer/State/InternalStateEditor.cs
index 35587fe..045c0df 100644
--- a/Glamourer/State/InternalStateEditor.cs
+++ b/Glamourer/State/InternalStateEditor.cs
@@ -222,7 +222,8 @@ public class InternalStateEditor(
}
/// Change the value of a single material color table entry.
- 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)
{
// We already have an existing value.
@@ -254,6 +255,10 @@ public class InternalStateEditor(
return state.Materials.TryAddValue(index, newValue);
}
+ /// Reset the value of a single material color table entry.
+ 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,
uint key = 0)
{
diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs
index f77e75a..3d1b27c 100644
--- a/Glamourer/State/StateEditor.cs
+++ b/Glamourer/State/StateEditor.cs
@@ -177,6 +177,18 @@ public class StateEditor(
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);
+ }
+
///
public void ChangeMetaState(object data, MetaIndex index, bool value, ApplySettings settings)
{