From 59529476ebd1322304741607f8e30c328616ba3c Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 16 Feb 2024 17:48:40 +0100 Subject: [PATCH] Further improvements. --- Glamourer/Configuration.cs | 1 + Glamourer/Events/StateChanged.cs | 2 +- Glamourer/Gui/Equipment/EquipmentDrawer.cs | 28 +- Glamourer/Gui/GenericPopupWindow.cs | 14 +- Glamourer/Gui/MainWindow.cs | 28 +- Glamourer/Gui/Materials/AdvancedDyePopup.cs | 303 +++++++++++------- Glamourer/Gui/Materials/ColorRowClipboard.cs | 20 ++ Glamourer/Gui/Materials/MaterialDrawer.cs | 34 +- Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 8 +- Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs | 5 + .../Material/LiveColorTablePreviewer.cs | 1 - Glamourer/Interop/Material/MaterialManager.cs | 2 +- .../Interop/Material/MaterialValueIndex.cs | 38 ++- Glamourer/State/InternalStateEditor.cs | 7 +- Glamourer/State/StateEditor.cs | 12 + 15 files changed, 342 insertions(+), 161 deletions(-) create mode 100644 Glamourer/Gui/Materials/ColorRowClipboard.cs 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) {