diff --git a/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.ColorTable.cs b/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.ColorTable.cs new file mode 100644 index 00000000..dc87ec41 --- /dev/null +++ b/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.ColorTable.cs @@ -0,0 +1,562 @@ +using Dalamud.Interface; +using ImGuiNET; +using OtterGui; +using OtterGui.Raii; +using OtterGui.Text; +using Penumbra.GameData.Files.MaterialStructs; +using Penumbra.GameData.Files.StainMapStructs; +using Penumbra.Services; + +namespace Penumbra.UI.AdvancedWindow.Materials; + +public partial class MtrlTab +{ + private const float ColorTableScalarSize = 65.0f; + + private int _colorTableSelectedPair = 0; + + private bool DrawColorTable(ColorTable table, ColorDyeTable? dyeTable, bool disabled) + { + DrawColorTablePairSelector(table, disabled); + return DrawColorTablePairEditor(table, dyeTable, disabled); + } + + private void DrawColorTablePairSelector(ColorTable table, bool disabled) + { + using var font = ImRaii.PushFont(UiBuilder.MonoFont); + using var alignment = ImRaii.PushStyle(ImGuiStyleVar.ButtonTextAlign, new Vector2(0, 0.5f)); + var style = ImGui.GetStyle(); + var itemSpacing = style.ItemSpacing.X; + var itemInnerSpacing = style.ItemInnerSpacing.X; + var framePadding = style.FramePadding; + var buttonWidth = (ImGui.GetContentRegionAvail().X - itemSpacing * 7.0f) * 0.125f; + var frameHeight = ImGui.GetFrameHeight(); + var highlighterSize = ImUtf8.CalcIconSize(FontAwesomeIcon.Crosshairs) + framePadding * 2.0f; + var spaceWidth = ImUtf8.CalcTextSize(" "u8).X; + var spacePadding = (int)MathF.Ceiling((highlighterSize.X + framePadding.X + itemInnerSpacing) / spaceWidth); + for (var i = 0; i < ColorTable.NumRows >> 1; i += 8) + { + for (var j = 0; j < 8; ++j) + { + var pairIndex = i + j; + using (var color = ImRaii.PushColor(ImGuiCol.Button, ImGui.GetColorU32(ImGuiCol.ButtonActive), pairIndex == _colorTableSelectedPair)) + { + if (ImUtf8.Button($"#{pairIndex + 1}".PadLeft(3 + spacePadding), new Vector2(buttonWidth, ImGui.GetFrameHeightWithSpacing() + frameHeight))) + _colorTableSelectedPair = pairIndex; + } + + var rcMin = ImGui.GetItemRectMin() + framePadding; + var rcMax = ImGui.GetItemRectMax() - framePadding; + CtBlendRect( + rcMin with { X = rcMax.X - frameHeight * 3 - itemInnerSpacing * 2 }, + rcMax with { X = rcMax.X - (frameHeight + itemInnerSpacing) * 2 }, + ImGuiUtil.ColorConvertFloat3ToU32(PseudoSqrtRgb((Vector3)table[pairIndex << 1].DiffuseColor)), + ImGuiUtil.ColorConvertFloat3ToU32(PseudoSqrtRgb((Vector3)table[(pairIndex << 1) | 1].DiffuseColor)) + ); + CtBlendRect( + rcMin with { X = rcMax.X - frameHeight * 2 - itemInnerSpacing }, + rcMax with { X = rcMax.X - frameHeight - itemInnerSpacing }, + ImGuiUtil.ColorConvertFloat3ToU32(PseudoSqrtRgb((Vector3)table[pairIndex << 1].SpecularColor)), + ImGuiUtil.ColorConvertFloat3ToU32(PseudoSqrtRgb((Vector3)table[(pairIndex << 1) | 1].SpecularColor)) + ); + CtBlendRect( + rcMin with { X = rcMax.X - frameHeight }, rcMax, + ImGuiUtil.ColorConvertFloat3ToU32(PseudoSqrtRgb((Vector3)table[pairIndex << 1].EmissiveColor)), + ImGuiUtil.ColorConvertFloat3ToU32(PseudoSqrtRgb((Vector3)table[(pairIndex << 1) | 1].EmissiveColor)) + ); + if (j < 7) + ImGui.SameLine(); + + var cursor = ImGui.GetCursorScreenPos(); + ImGui.SetCursorScreenPos(rcMin with { Y = float.Lerp(rcMin.Y, rcMax.Y, 0.5f) - highlighterSize.Y * 0.5f }); + font.Pop(); + ColorTableHighlightButton(pairIndex, disabled); + font.Push(UiBuilder.MonoFont); + ImGui.SetCursorScreenPos(cursor); + } + } + } + + private bool DrawColorTablePairEditor(ColorTable table, ColorDyeTable? dyeTable, bool disabled) + { + var retA = false; + var retB = false; + ref var rowA = ref table[_colorTableSelectedPair << 1]; + ref var rowB = ref table[(_colorTableSelectedPair << 1) | 1]; + var dyeA = dyeTable != null ? dyeTable[_colorTableSelectedPair << 1] : default; + var dyeB = dyeTable != null ? dyeTable[(_colorTableSelectedPair << 1) | 1] : default; + var previewDyeA = _stainService.GetStainCombo(dyeA.Channel).CurrentSelection.Key; + var previewDyeB = _stainService.GetStainCombo(dyeB.Channel).CurrentSelection.Key; + var dyePackA = _stainService.GudStmFile.GetValueOrNull(dyeA.Template, previewDyeA); + var dyePackB = _stainService.GudStmFile.GetValueOrNull(dyeB.Template, previewDyeB); + using (var columns = ImUtf8.Columns(2, "ColorTable"u8)) + { + ColorTableCopyClipboardButton(_colorTableSelectedPair << 1); + ImUtf8.SameLineInner(); + retA |= ColorTablePasteFromClipboardButton(_colorTableSelectedPair << 1, disabled); + ImGui.SameLine(); + CenteredTextInRest($"Row {_colorTableSelectedPair + 1}A"); + columns.Next(); + ColorTableCopyClipboardButton((_colorTableSelectedPair << 1) | 1); + ImUtf8.SameLineInner(); + retB |= ColorTablePasteFromClipboardButton((_colorTableSelectedPair << 1) | 1, disabled); + ImGui.SameLine(); + CenteredTextInRest($"Row {_colorTableSelectedPair + 1}B"); + } + + DrawHeader(" Colors"u8); + using (var columns = ImUtf8.Columns(2, "ColorTable"u8)) + { + using var dis = ImRaii.Disabled(disabled); + using (var id = ImUtf8.PushId("ColorsA"u8)) + retA |= DrawColors(table, dyeTable, dyePackA, _colorTableSelectedPair << 1); + columns.Next(); + using (var id = ImUtf8.PushId("ColorsB"u8)) + retB |= DrawColors(table, dyeTable, dyePackB, (_colorTableSelectedPair << 1) | 1); + } + + DrawHeader(" Physical Parameters"u8); + using (var columns = ImUtf8.Columns(2, "ColorTable"u8)) + { + using var dis = ImRaii.Disabled(disabled); + using (var id = ImUtf8.PushId("PbrA"u8)) + retA |= DrawPbr(table, dyeTable, dyePackA, _colorTableSelectedPair << 1); + columns.Next(); + using (var id = ImUtf8.PushId("PbrB"u8)) + retB |= DrawPbr(table, dyeTable, dyePackB, (_colorTableSelectedPair << 1) | 1); + } + + DrawHeader(" Sheen Layer Parameters"u8); + using (var columns = ImUtf8.Columns(2, "ColorTable"u8)) + { + using var dis = ImRaii.Disabled(disabled); + using (var id = ImUtf8.PushId("SheenA"u8)) + retA |= DrawSheen(table, dyeTable, dyePackA, _colorTableSelectedPair << 1); + columns.Next(); + using (var id = ImUtf8.PushId("SheenB"u8)) + retB |= DrawSheen(table, dyeTable, dyePackB, (_colorTableSelectedPair << 1) | 1); + } + + DrawHeader(" Pair Blending"u8); + using (var columns = ImUtf8.Columns(2, "ColorTable"u8)) + { + using var dis = ImRaii.Disabled(disabled); + using (var id = ImUtf8.PushId("BlendingA"u8)) + retA |= DrawBlending(table, dyeTable, dyePackA, _colorTableSelectedPair << 1); + columns.Next(); + using (var id = ImUtf8.PushId("BlendingB"u8)) + retB |= DrawBlending(table, dyeTable, dyePackB, (_colorTableSelectedPair << 1) | 1); + } + + DrawHeader(" Material Template"u8); + using (var columns = ImUtf8.Columns(2, "ColorTable"u8)) + { + using var dis = ImRaii.Disabled(disabled); + using (var id = ImUtf8.PushId("TemplateA"u8)) + retA |= DrawTemplate(table, dyeTable, dyePackA, _colorTableSelectedPair << 1); + columns.Next(); + using (var id = ImUtf8.PushId("TemplateB"u8)) + retB |= DrawTemplate(table, dyeTable, dyePackB, (_colorTableSelectedPair << 1) | 1); + } + + if (dyeTable != null) + { + DrawHeader(" Dye Properties"u8); + using (var columns = ImUtf8.Columns(2, "ColorTable"u8)) + { + using var dis = ImRaii.Disabled(disabled); + using (var id = ImUtf8.PushId("DyeA"u8)) + retA |= DrawDye(dyeTable, dyePackA, _colorTableSelectedPair << 1); + columns.Next(); + using (var id = ImUtf8.PushId("DyeB"u8)) + retB |= DrawDye(dyeTable, dyePackB, (_colorTableSelectedPair << 1) | 1); + } + } + + DrawHeader(" Further Content"u8); + using (var columns = ImUtf8.Columns(2, "ColorTable"u8)) + { + using var dis = ImRaii.Disabled(disabled); + using (var id = ImUtf8.PushId("FurtherA"u8)) + retA |= DrawFurther(table, dyeTable, dyePackA, _colorTableSelectedPair << 1); + columns.Next(); + using (var id = ImUtf8.PushId("FurtherB"u8)) + retB |= DrawFurther(table, dyeTable, dyePackB, (_colorTableSelectedPair << 1) | 1); + } + + if (retA) + UpdateColorTableRowPreview(_colorTableSelectedPair << 1); + if (retB) + UpdateColorTableRowPreview((_colorTableSelectedPair << 1) | 1); + + return retA | retB; + } + + /// Padding styles do not seem to apply to this component. It is recommended to prepend two spaces. + private static void DrawHeader(ReadOnlySpan label) + { + var headerColor = ImGui.GetColorU32(ImGuiCol.Header); + using var _ = ImRaii.PushColor(ImGuiCol.HeaderHovered, headerColor).Push(ImGuiCol.HeaderActive, headerColor); + ImUtf8.CollapsingHeader(label, ImGuiTreeNodeFlags.Leaf); + } + + private static bool DrawColors(ColorTable table, ColorDyeTable? dyeTable, DyePack? dyePack, int rowIdx) + { + var dyeOffset = ImGui.GetContentRegionAvail().X + ImGui.GetStyle().ItemSpacing.X - ImGui.GetStyle().ItemInnerSpacing.X - ImGui.GetFrameHeight() * 2.0f; + + var ret = false; + ref var row = ref table[rowIdx]; + var dye = dyeTable != null ? dyeTable[rowIdx] : default; + + ret |= CtColorPicker("Diffuse Color"u8, default, row.DiffuseColor, + c => table[rowIdx].DiffuseColor = c); + if (dyeTable != null) + { + ImGui.SameLine(dyeOffset); + ret |= CtApplyStainCheckbox("##dyeDiffuseColor"u8, "Apply Diffuse Color on Dye"u8, dye.DiffuseColor, + b => dyeTable[rowIdx].DiffuseColor = b); + ImUtf8.SameLineInner(); + CtColorPicker("##dyePreviewDiffuseColor"u8, "Dye Preview for Diffuse Color"u8, dyePack?.DiffuseColor); + } + + ret |= CtColorPicker("Specular Color"u8, default, row.SpecularColor, + c => table[rowIdx].SpecularColor = c); + if (dyeTable != null) + { + ImGui.SameLine(dyeOffset); + ret |= CtApplyStainCheckbox("##dyeSpecularColor"u8, "Apply Specular Color on Dye"u8, dye.SpecularColor, + b => dyeTable[rowIdx].SpecularColor = b); + ImUtf8.SameLineInner(); + CtColorPicker("##dyePreviewSpecularColor"u8, "Dye Preview for Specular Color"u8, dyePack?.SpecularColor); + } + + ret |= CtColorPicker("Emissive Color"u8, default, row.EmissiveColor, + c => table[rowIdx].EmissiveColor = c); + if (dyeTable != null) + { + ImGui.SameLine(dyeOffset); + ret |= CtApplyStainCheckbox("##dyeEmissiveColor"u8, "Apply Emissive Color on Dye"u8, dye.EmissiveColor, + b => dyeTable[rowIdx].EmissiveColor = b); + ImUtf8.SameLineInner(); + CtColorPicker("##dyePreviewEmissiveColor"u8, "Dye Preview for Emissive Color"u8, dyePack?.EmissiveColor); + } + + return ret; + } + + private static bool DrawBlending(ColorTable table, ColorDyeTable? dyeTable, DyePack? dyePack, int rowIdx) + { + var scalarSize = ColorTableScalarSize * UiHelpers.Scale; + var dyeOffset = ImGui.GetContentRegionAvail().X + ImGui.GetStyle().ItemSpacing.X - ImGui.GetStyle().ItemInnerSpacing.X - ImGui.GetFrameHeight() - scalarSize; + + var isRowB = (rowIdx & 1) != 0; + + var ret = false; + ref var row = ref table[rowIdx]; + var dye = dyeTable != null ? dyeTable[rowIdx] : default; + + ImGui.SetNextItemWidth(scalarSize); + ret |= CtDragHalf(isRowB ? "Field #19"u8 : "Anisotropy Degree"u8, default, row.Anisotropy, "%.2f"u8, 0.0f, HalfMaxValue, 0.1f, + v => table[rowIdx].Anisotropy = v); + if (dyeTable != null) + { + ImGui.SameLine(dyeOffset); + ret |= CtApplyStainCheckbox("##dyeAnisotropy"u8, isRowB ? "Apply Field #19 on Dye"u8 : "Apply Anisotropy Degree on Dye"u8, dye.Anisotropy, + b => dyeTable[rowIdx].Anisotropy = b); + ImUtf8.SameLineInner(); + ImGui.SetNextItemWidth(scalarSize); + CtDragHalf("##dyePreviewAnisotropy"u8, isRowB ? "Dye Preview for Field #19"u8 : "Dye Preview for Anisotropy Degree"u8, dyePack?.Anisotropy, "%.2f"u8); + } + + return ret; + } + + private bool DrawTemplate(ColorTable table, ColorDyeTable? dyeTable, DyePack? dyePack, int rowIdx) + { + var scalarSize = ColorTableScalarSize * UiHelpers.Scale; + var itemSpacing = ImGui.GetStyle().ItemSpacing.X; + var dyeOffset = ImGui.GetContentRegionAvail().X - ImGui.GetStyle().ItemInnerSpacing.X - ImGui.GetFrameHeight() - scalarSize - 64.0f; + var subcolWidth = CalculateSubcolumnWidth(2); + + var ret = false; + ref var row = ref table[rowIdx]; + var dye = dyeTable != null ? dyeTable[rowIdx] : default; + + ImGui.SetNextItemWidth(scalarSize); + ret |= CtDragScalar("Shader ID"u8, default, row.ShaderId, "%d"u8, (ushort)0, (ushort)255, 0.25f, + v => table[rowIdx].ShaderId = v); + + ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); + + ImGui.SetNextItemWidth(scalarSize + itemSpacing + 64.0f); + ret |= CtSphereMapIndexPicker("###SphereMapIndex"u8, default, row.SphereMapIndex, false, + v => table[rowIdx].SphereMapIndex = v); + ImUtf8.SameLineInner(); + ImUtf8.Text("Sphere Map"u8); + if (dyeTable != null) + { + var textRectMin = ImGui.GetItemRectMin(); + var textRectMax = ImGui.GetItemRectMax(); + ImGui.SameLine(dyeOffset); + var cursor = ImGui.GetCursorScreenPos(); + ImGui.SetCursorScreenPos(cursor with { Y = float.Lerp(textRectMin.Y, textRectMax.Y, 0.5f) - ImGui.GetFrameHeight() * 0.5f }); + ret |= CtApplyStainCheckbox("##dyeSphereMapIndex"u8, "Apply Sphere Map on Dye"u8, dye.SphereMapIndex, + b => dyeTable[rowIdx].SphereMapIndex = b); + ImUtf8.SameLineInner(); + ImGui.SetCursorScreenPos(ImGui.GetCursorScreenPos() with { Y = cursor.Y }); + ImGui.SetNextItemWidth(scalarSize + itemSpacing + 64.0f); + using var dis = ImRaii.Disabled(); + CtSphereMapIndexPicker("###SphereMapIndexDye"u8, "Dye Preview for Sphere Map"u8, dyePack?.SphereMapIndex ?? ushort.MaxValue, false, Nop); + } + + ImGui.Dummy(new Vector2(64.0f, 0.0f)); + ImGui.SameLine(); + ImGui.SetNextItemWidth(scalarSize); + ret |= CtDragScalar("Sphere Map Intensity"u8, default, (float)row.SphereMapMask * 100.0f, "%.0f%%"u8, HalfMinValue * 100.0f, HalfMaxValue * 100.0f, 1.0f, + v => table[rowIdx].SphereMapMask = (Half)(v * 0.01f)); + if (dyeTable != null) + { + ImGui.SameLine(dyeOffset); + ret |= CtApplyStainCheckbox("##dyeSphereMapMask"u8, "Apply Sphere Map Intensity on Dye"u8, dye.SphereMapMask, + b => dyeTable[rowIdx].SphereMapMask = b); + ImUtf8.SameLineInner(); + ImGui.SetNextItemWidth(scalarSize); + CtDragScalar("##dyeSphereMapMask"u8, "Dye Preview for Sphere Map Intensity"u8, (float?)dyePack?.SphereMapMask * 100.0f, "%.0f%%"u8); + } + + ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); + + var leftLineHeight = 64.0f + ImGui.GetStyle().FramePadding.Y * 2.0f; + var rightLineHeight = 3.0f * ImGui.GetFrameHeight() + 2.0f * ImGui.GetStyle().ItemSpacing.Y; + var lineHeight = Math.Max(leftLineHeight, rightLineHeight); + var cursorPos = ImGui.GetCursorScreenPos(); + ImGui.SetCursorScreenPos(cursorPos + new Vector2(0.0f, (lineHeight - leftLineHeight) * 0.5f)); + ImGui.SetNextItemWidth(scalarSize + (itemSpacing + 64.0f) * 2.0f); + ret |= CtTileIndexPicker("###TileIndex"u8, default, row.TileIndex, false, + v => table[rowIdx].TileIndex = v); + ImUtf8.SameLineInner(); + ImUtf8.Text("Tile"u8); + + ImGui.SameLine(subcolWidth); + ImGui.SetCursorScreenPos(ImGui.GetCursorScreenPos() with { Y = cursorPos.Y + (lineHeight - rightLineHeight) * 0.5f, }); + using (var cld = ImUtf8.Child("###TileProperties"u8, new(ImGui.GetContentRegionAvail().X, float.Lerp(rightLineHeight, lineHeight, 0.5f)), false)) + { + ImGui.Dummy(new Vector2(scalarSize, 0.0f)); + ImUtf8.SameLineInner(); + ImGui.SetNextItemWidth(scalarSize); + ret |= CtDragScalar("Tile Opacity"u8, default, (float)row.TileAlpha * 100.0f, "%.0f%%"u8, 0.0f, HalfMaxValue * 100.0f, 1.0f, + v => table[rowIdx].TileAlpha = (Half)(v * 0.01f)); + + ret |= CtTileTransformMatrix(row.TileTransform, scalarSize, true, + m => table[rowIdx].TileTransform = m); + ImUtf8.SameLineInner(); + ImGui.SetCursorScreenPos(ImGui.GetCursorScreenPos() - new Vector2(0.0f, (ImGui.GetFrameHeight() + ImGui.GetStyle().ItemSpacing.Y) * 0.5f)); + ImUtf8.Text("Tile Transform"u8); + } + + return ret; + } + + private static bool DrawPbr(ColorTable table, ColorDyeTable? dyeTable, DyePack? dyePack, int rowIdx) + { + var scalarSize = ColorTableScalarSize * UiHelpers.Scale; + var subcolWidth = CalculateSubcolumnWidth(2) + ImGui.GetStyle().ItemSpacing.X; + var dyeOffset = subcolWidth - ImGui.GetStyle().ItemSpacing.X * 2.0f - ImGui.GetStyle().ItemInnerSpacing.X - ImGui.GetFrameHeight() - scalarSize; + + var ret = false; + ref var row = ref table[rowIdx]; + var dye = dyeTable != null ? dyeTable[rowIdx] : default; + + ImGui.SetNextItemWidth(scalarSize); + ret |= CtDragScalar("Roughness"u8, default, (float)row.Roughness * 100.0f, "%.0f%%"u8, HalfMinValue * 100.0f, HalfMaxValue * 100.0f, 1.0f, + v => table[rowIdx].Roughness = (Half)(v * 0.01f)); + if (dyeTable != null) + { + ImGui.SameLine(dyeOffset); + ret |= CtApplyStainCheckbox("##dyeRoughness"u8, "Apply Roughness on Dye"u8, dye.Roughness, + b => dyeTable[rowIdx].Roughness = b); + ImUtf8.SameLineInner(); + ImGui.SetNextItemWidth(scalarSize); + CtDragScalar("##dyePreviewRoughness"u8, "Dye Preview for Roughness"u8, (float?)dyePack?.Roughness * 100.0f, "%.0f%%"u8); + } + + ImGui.SameLine(subcolWidth); + ImGui.SetNextItemWidth(scalarSize); + ret |= CtDragScalar("Metalness"u8, default, (float)row.Metalness * 100.0f, "%.0f%%"u8, HalfMinValue * 100.0f, HalfMaxValue * 100.0f, 1.0f, + v => table[rowIdx].Metalness = (Half)(v * 0.01f)); + if (dyeTable != null) + { + ImGui.SameLine(subcolWidth + dyeOffset); + ret |= CtApplyStainCheckbox("##dyeMetalness"u8, "Apply Metalness on Dye"u8, dye.Metalness, + b => dyeTable[rowIdx].Metalness = b); + ImUtf8.SameLineInner(); + ImGui.SetNextItemWidth(scalarSize); + CtDragScalar("##dyePreviewMetalness"u8, "Dye Preview for Metalness"u8, (float?)dyePack?.Metalness * 100.0f, "%.0f%%"u8); + } + + return ret; + } + + private static bool DrawSheen(ColorTable table, ColorDyeTable? dyeTable, DyePack? dyePack, int rowIdx) + { + var scalarSize = ColorTableScalarSize * UiHelpers.Scale; + var subcolWidth = CalculateSubcolumnWidth(2) + ImGui.GetStyle().ItemSpacing.X; + var dyeOffset = subcolWidth - ImGui.GetStyle().ItemSpacing.X * 2.0f - ImGui.GetStyle().ItemInnerSpacing.X - ImGui.GetFrameHeight() - scalarSize; + + var ret = false; + ref var row = ref table[rowIdx]; + var dye = dyeTable != null ? dyeTable[rowIdx] : default; + + ImGui.SetNextItemWidth(scalarSize); + ret |= CtDragScalar("Sheen"u8, default, (float)row.SheenRate * 100.0f, "%.0f%%"u8, HalfMinValue * 100.0f, HalfMaxValue * 100.0f, 1.0f, + v => table[rowIdx].SheenRate = (Half)(v * 0.01f)); + if (dyeTable != null) + { + ImGui.SameLine(dyeOffset); + ret |= CtApplyStainCheckbox("##dyeSheenRate"u8, "Apply Sheen on Dye"u8, dye.SheenRate, + b => dyeTable[rowIdx].SheenRate = b); + ImUtf8.SameLineInner(); + ImGui.SetNextItemWidth(scalarSize); + CtDragScalar("##dyePreviewSheenRate"u8, "Dye Preview for Sheen"u8, (float?)dyePack?.SheenRate * 100.0f, "%.0f%%"u8); + } + + ImGui.SameLine(subcolWidth); + ImGui.SetNextItemWidth(scalarSize); + ret |= CtDragScalar("Sheen Tint"u8, default, (float)row.SheenTintRate * 100.0f, "%.0f%%"u8, HalfMinValue * 100.0f, HalfMaxValue * 100.0f, 1.0f, + v => table[rowIdx].SheenTintRate = (Half)(v * 0.01f)); + if (dyeTable != null) + { + ImGui.SameLine(subcolWidth + dyeOffset); + ret |= CtApplyStainCheckbox("##dyeSheenTintRate"u8, "Apply Sheen Tint on Dye"u8, dye.SheenTintRate, + b => dyeTable[rowIdx].SheenTintRate = b); + ImUtf8.SameLineInner(); + ImGui.SetNextItemWidth(scalarSize); + CtDragScalar("##dyePreviewSheenTintRate"u8, "Dye Preview for Sheen Tint"u8, (float?)dyePack?.SheenTintRate * 100.0f, "%.0f%%"u8); + } + + ImGui.SetNextItemWidth(scalarSize); + ret |= CtDragScalar("Sheen Roughness"u8, default, 100.0f / (float)row.SheenAperture, "%.0f%%"u8, 100.0f / HalfMaxValue, 100.0f / HalfEpsilon, 1.0f, + v => table[rowIdx].SheenAperture = (Half)(100.0f / v)); + if (dyeTable != null) + { + ImGui.SameLine(dyeOffset); + ret |= CtApplyStainCheckbox("##dyeSheenRoughness"u8, "Apply Sheen Roughness on Dye"u8, dye.SheenAperture, + b => dyeTable[rowIdx].SheenAperture = b); + ImUtf8.SameLineInner(); + ImGui.SetNextItemWidth(scalarSize); + CtDragScalar("##dyePreviewSheenRoughness"u8, "Dye Preview for Sheen Roughness"u8, 100.0f / (float?)dyePack?.SheenAperture, "%.0f%%"u8); + } + + return ret; + } + + private static bool DrawFurther(ColorTable table, ColorDyeTable? dyeTable, DyePack? dyePack, int rowIdx) + { + var scalarSize = ColorTableScalarSize * UiHelpers.Scale; + var subcolWidth = CalculateSubcolumnWidth(2) + ImGui.GetStyle().ItemSpacing.X; + var dyeOffset = subcolWidth - ImGui.GetStyle().ItemSpacing.X * 2.0f - ImGui.GetStyle().ItemInnerSpacing.X - ImGui.GetFrameHeight() - scalarSize; + + var ret = false; + ref var row = ref table[rowIdx]; + var dye = dyeTable != null ? dyeTable[rowIdx] : default; + + ImGui.SetNextItemWidth(scalarSize); + ret |= CtDragHalf("Field #11"u8, default, row.Scalar11, "%.2f"u8, HalfMinValue, HalfMaxValue, 0.1f, + v => table[rowIdx].Scalar11 = v); + if (dyeTable != null) + { + ImGui.SameLine(dyeOffset); + ret |= CtApplyStainCheckbox("##dyeScalar11"u8, "Apply Field #11 on Dye"u8, dye.Scalar3, + b => dyeTable[rowIdx].Scalar3 = b); + ImUtf8.SameLineInner(); + ImGui.SetNextItemWidth(scalarSize); + CtDragHalf("##dyePreviewScalar11"u8, "Dye Preview for Field #11"u8, dyePack?.Scalar3, "%.2f"u8); + } + + ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); + + ImGui.SetNextItemWidth(scalarSize); + ret |= CtDragHalf("Field #3"u8, default, row.Scalar3, "%.2f"u8, HalfMinValue, HalfMaxValue, 0.1f, + v => table[rowIdx].Scalar3 = v); + + ImGui.SameLine(subcolWidth); + ImGui.SetNextItemWidth(scalarSize); + ret |= CtDragHalf("Field #7"u8, default, row.Scalar7, "%.2f"u8, HalfMinValue, HalfMaxValue, 0.1f, + v => table[rowIdx].Scalar7 = v); + + ImGui.SetNextItemWidth(scalarSize); + ret |= CtDragHalf("Field #15"u8, default, row.Scalar15, "%.2f"u8, HalfMinValue, HalfMaxValue, 0.1f, + v => table[rowIdx].Scalar15 = v); + + ImGui.SameLine(subcolWidth); + ImGui.SetNextItemWidth(scalarSize); + ret |= CtDragHalf("Field #17"u8, default, row.Scalar17, "%.2f"u8, HalfMinValue, HalfMaxValue, 0.1f, + v => table[rowIdx].Scalar17 = v); + + ImGui.SetNextItemWidth(scalarSize); + ret |= CtDragHalf("Field #20"u8, default, row.Scalar20, "%.2f"u8, HalfMinValue, HalfMaxValue, 0.1f, + v => table[rowIdx].Scalar20 = v); + + ImGui.SameLine(subcolWidth); + ImGui.SetNextItemWidth(scalarSize); + ret |= CtDragHalf("Field #22"u8, default, row.Scalar22, "%.2f"u8, HalfMinValue, HalfMaxValue, 0.1f, + v => table[rowIdx].Scalar22 = v); + + ImGui.SetNextItemWidth(scalarSize); + ret |= CtDragHalf("Field #23"u8, default, row.Scalar23, "%.2f"u8, HalfMinValue, HalfMaxValue, 0.1f, + v => table[rowIdx].Scalar23 = v); + + return ret; + } + + private bool DrawDye(ColorDyeTable dyeTable, DyePack? dyePack, int rowIdx) + { + var scalarSize = ColorTableScalarSize * UiHelpers.Scale; + var applyButtonWidth = ImUtf8.CalcTextSize("Apply Preview Dye"u8).X + ImGui.GetStyle().FramePadding.X * 2.0f; + var subcolWidth = CalculateSubcolumnWidth(2, applyButtonWidth); + + var ret = false; + ref var dye = ref dyeTable[rowIdx]; + + ImGui.SetNextItemWidth(scalarSize); + ret |= CtDragScalar("Dye Channel"u8, default, dye.Channel + 1, "%d"u8, 1, StainService.ChannelCount, 0.1f, + value => dyeTable[rowIdx].Channel = (byte)(Math.Clamp(value, 1, StainService.ChannelCount) - 1)); + ImGui.SameLine(subcolWidth); + ImGui.SetNextItemWidth(scalarSize); + if (_stainService.GudTemplateCombo.Draw("##dyeTemplate", dye.Template.ToString(), string.Empty, + scalarSize + ImGui.GetStyle().ScrollbarSize / 2, ImGui.GetTextLineHeightWithSpacing(), ImGuiComboFlags.NoArrowButton)) + { + dye.Template = _stainService.LegacyTemplateCombo.CurrentSelection; + ret = true; + } + ImUtf8.SameLineInner(); + ImUtf8.Text("Dye Template"u8); + ImGui.SameLine(ImGui.GetContentRegionAvail().X - applyButtonWidth + ImGui.GetStyle().ItemSpacing.X); + using var dis = ImRaii.Disabled(!dyePack.HasValue); + if (ImUtf8.Button("Apply Preview Dye"u8)) + { + ret |= Mtrl.ApplyDyeToRow(_stainService.GudStmFile, [ + _stainService.StainCombo1.CurrentSelection.Key, + _stainService.StainCombo2.CurrentSelection.Key, + ], rowIdx); + } + + return ret; + } + + private static void CenteredTextInRest(string text) + => AlignedTextInRest(text, 0.5f); + + private static void AlignedTextInRest(string text, float alignment) + { + var width = ImGui.CalcTextSize(text).X; + ImGui.SetCursorScreenPos(ImGui.GetCursorScreenPos() + new Vector2((ImGui.GetContentRegionAvail().X - width) * alignment, 0.0f)); + ImGui.TextUnformatted(text); + } + + private static float CalculateSubcolumnWidth(int numSubcolumns, float reservedSpace = 0.0f) + { + var itemSpacing = ImGui.GetStyle().ItemSpacing.X; + return (ImGui.GetContentRegionAvail().X - reservedSpace - itemSpacing * (numSubcolumns - 1)) / numSubcolumns + itemSpacing; + } +} diff --git a/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.CommonColorTable.cs b/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.CommonColorTable.cs index 937614de..2b093e23 100644 --- a/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.CommonColorTable.cs +++ b/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.CommonColorTable.cs @@ -52,6 +52,7 @@ public partial class MtrlTab { LegacyColorTable legacyTable => DrawLegacyColorTable(legacyTable, Mtrl.DyeTable as LegacyColorDyeTable, disabled), ColorTable table when Mtrl.ShaderPackage.Name is "characterlegacy.shpk" => DrawLegacyColorTable(table, Mtrl.DyeTable as ColorDyeTable, disabled), + ColorTable table => DrawColorTable(table, Mtrl.DyeTable as ColorDyeTable, disabled), _ => false, };