diff --git a/LightlessSync/UI/DataAnalysisUi.cs b/LightlessSync/UI/DataAnalysisUi.cs index 2958ebc..a4bbf9f 100644 --- a/LightlessSync/UI/DataAnalysisUi.cs +++ b/LightlessSync/UI/DataAnalysisUi.cs @@ -29,6 +29,9 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase private const float MaxTextureFilterPaneWidth = 405f; private const float MinTextureDetailPaneWidth = 480f; private const float MaxTextureDetailPaneWidth = 720f; + private const float TextureFilterSplitterWidth = 8f; + private const float TextureDetailSplitterWidth = 12f; + private const float TextureDetailSplitterCollapsedWidth = 18f; private const float SelectedFilePanelLogicalHeight = 90f; private static readonly Vector4 SelectedTextureRowTextColor = new(0f, 0f, 0f, 1f); @@ -80,6 +83,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase private bool _modalOpen = false; private bool _showModal = false; private bool _textureRowsDirty = true; + private bool _textureDetailCollapsed = false; private bool _conversionFailed; private bool _showAlreadyAddedTransients = false; private bool _acknowledgeReview = false; @@ -1205,35 +1209,52 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase var availableSize = ImGui.GetContentRegionAvail(); var windowPos = ImGui.GetWindowPos(); var spacingX = ImGui.GetStyle().ItemSpacing.X; - var splitterWidth = 6f * scale; + var filterSplitterWidth = TextureFilterSplitterWidth * scale; + var detailSplitterWidth = (_textureDetailCollapsed ? TextureDetailSplitterCollapsedWidth : TextureDetailSplitterWidth) * scale; + var totalSplitterWidth = filterSplitterWidth + detailSplitterWidth; + var totalSpacing = 2 * spacingX; const float minFilterWidth = MinTextureFilterPaneWidth; const float minDetailWidth = MinTextureDetailPaneWidth; const float minCenterWidth = 340f; - var dynamicFilterMax = Math.Max(minFilterWidth, availableSize.X - minDetailWidth - minCenterWidth - 2 * (splitterWidth + spacingX)); + var detailMinForLayout = _textureDetailCollapsed ? 0f : minDetailWidth; + var dynamicFilterMax = Math.Max(minFilterWidth, availableSize.X - detailMinForLayout - minCenterWidth - totalSplitterWidth - totalSpacing); var filterMaxBound = Math.Min(MaxTextureFilterPaneWidth, dynamicFilterMax); var filterWidth = Math.Clamp(_textureFilterPaneWidth, minFilterWidth, filterMaxBound); - var dynamicDetailMax = Math.Max(minDetailWidth, availableSize.X - filterWidth - minCenterWidth - 2 * (splitterWidth + spacingX)); - var detailMaxBound = Math.Min(MaxTextureDetailPaneWidth, dynamicDetailMax); - var detailWidth = Math.Clamp(_textureDetailPaneWidth, minDetailWidth, detailMaxBound); + var dynamicDetailMax = Math.Max(detailMinForLayout, availableSize.X - filterWidth - minCenterWidth - totalSplitterWidth - totalSpacing); + var detailMaxBound = _textureDetailCollapsed ? 0f : Math.Min(MaxTextureDetailPaneWidth, dynamicDetailMax); + var detailWidth = _textureDetailCollapsed ? 0f : Math.Clamp(_textureDetailPaneWidth, minDetailWidth, detailMaxBound); - var centerWidth = availableSize.X - filterWidth - detailWidth - 2 * (splitterWidth + spacingX); + var centerWidth = availableSize.X - filterWidth - detailWidth - totalSplitterWidth - totalSpacing; if (centerWidth < minCenterWidth) { var deficit = minCenterWidth - centerWidth; - detailWidth = Math.Clamp(detailWidth - deficit, minDetailWidth, - Math.Min(MaxTextureDetailPaneWidth, Math.Max(minDetailWidth, availableSize.X - filterWidth - minCenterWidth - 2 * (splitterWidth + spacingX)))); - centerWidth = availableSize.X - filterWidth - detailWidth - 2 * (splitterWidth + spacingX); - if (centerWidth < minCenterWidth) + if (!_textureDetailCollapsed) + { + detailWidth = Math.Clamp(detailWidth - deficit, minDetailWidth, + Math.Min(MaxTextureDetailPaneWidth, Math.Max(minDetailWidth, availableSize.X - filterWidth - minCenterWidth - totalSplitterWidth - totalSpacing))); + centerWidth = availableSize.X - filterWidth - detailWidth - totalSplitterWidth - totalSpacing; + if (centerWidth < minCenterWidth) + { + deficit = minCenterWidth - centerWidth; + filterWidth = Math.Clamp(filterWidth - deficit, minFilterWidth, + Math.Min(MaxTextureFilterPaneWidth, Math.Max(minFilterWidth, availableSize.X - detailWidth - minCenterWidth - totalSplitterWidth - totalSpacing))); + detailWidth = Math.Clamp(detailWidth, minDetailWidth, + Math.Min(MaxTextureDetailPaneWidth, Math.Max(minDetailWidth, availableSize.X - filterWidth - minCenterWidth - totalSplitterWidth - totalSpacing))); + centerWidth = availableSize.X - filterWidth - detailWidth - totalSplitterWidth - totalSpacing; + if (centerWidth < minCenterWidth) + { + centerWidth = minCenterWidth; + } + } + } + else { - deficit = minCenterWidth - centerWidth; filterWidth = Math.Clamp(filterWidth - deficit, minFilterWidth, - Math.Min(MaxTextureFilterPaneWidth, Math.Max(minFilterWidth, availableSize.X - detailWidth - minCenterWidth - 2 * (splitterWidth + spacingX)))); - detailWidth = Math.Clamp(detailWidth, minDetailWidth, - Math.Min(MaxTextureDetailPaneWidth, Math.Max(minDetailWidth, availableSize.X - filterWidth - minCenterWidth - 2 * (splitterWidth + spacingX)))); - centerWidth = availableSize.X - filterWidth - detailWidth - 2 * (splitterWidth + spacingX); + Math.Min(MaxTextureFilterPaneWidth, Math.Max(minFilterWidth, availableSize.X - minCenterWidth - totalSplitterWidth - totalSpacing))); + centerWidth = availableSize.X - filterWidth - detailWidth - totalSplitterWidth - totalSpacing; if (centerWidth < minCenterWidth) { centerWidth = minCenterWidth; @@ -1242,7 +1263,10 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase } _textureFilterPaneWidth = filterWidth; - _textureDetailPaneWidth = detailWidth; + if (!_textureDetailCollapsed) + { + _textureDetailPaneWidth = detailWidth; + } ImGui.BeginGroup(); using (var filters = ImRaii.Child("textureFilters", new Vector2(filterWidth, 0), true)) @@ -1264,8 +1288,8 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase var filterMax = ImGui.GetItemRectMax(); var filterHeight = filterMax.Y - filterMin.Y; var filterTopLocal = filterMin - windowPos; - var maxFilterResize = Math.Min(MaxTextureFilterPaneWidth, Math.Max(minFilterWidth, availableSize.X - minCenterWidth - minDetailWidth - 2 * (splitterWidth + spacingX))); - DrawVerticalResizeHandle("##textureFilterSplitter", filterTopLocal.Y, filterHeight, ref _textureFilterPaneWidth, minFilterWidth, maxFilterResize); + var maxFilterResize = Math.Min(MaxTextureFilterPaneWidth, Math.Max(minFilterWidth, availableSize.X - minCenterWidth - detailMinForLayout - totalSplitterWidth - totalSpacing)); + DrawVerticalResizeHandle("##textureFilterSplitter", filterTopLocal.Y, filterHeight, ref _textureFilterPaneWidth, minFilterWidth, maxFilterResize, out _); TextureRow? selectedRow; ImGui.BeginGroup(); @@ -1279,15 +1303,36 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase var tableMax = ImGui.GetItemRectMax(); var tableHeight = tableMax.Y - tableMin.Y; var tableTopLocal = tableMin - windowPos; - var maxDetailResize = Math.Min(MaxTextureDetailPaneWidth, Math.Max(minDetailWidth, availableSize.X - _textureFilterPaneWidth - minCenterWidth - 2 * (splitterWidth + spacingX))); - DrawVerticalResizeHandle("##textureDetailSplitter", tableTopLocal.Y, tableHeight, ref _textureDetailPaneWidth, minDetailWidth, maxDetailResize, invert: true); - - ImGui.BeginGroup(); - using (var detailChild = ImRaii.Child("textureDetailPane", new Vector2(detailWidth, 0), true)) + var maxDetailResize = Math.Min(MaxTextureDetailPaneWidth, Math.Max(minDetailWidth, availableSize.X - _textureFilterPaneWidth - minCenterWidth - totalSplitterWidth - totalSpacing)); + var detailToggle = DrawVerticalResizeHandle( + "##textureDetailSplitter", + tableTopLocal.Y, + tableHeight, + ref _textureDetailPaneWidth, + minDetailWidth, + maxDetailResize, + out var detailDragging, + invert: true, + showToggle: true, + isCollapsed: _textureDetailCollapsed); + if (detailToggle) { - DrawTextureDetail(selectedRow); + _textureDetailCollapsed = !_textureDetailCollapsed; + } + if (_textureDetailCollapsed && detailDragging) + { + _textureDetailCollapsed = false; + } + + if (!_textureDetailCollapsed) + { + ImGui.BeginGroup(); + using (var detailChild = ImRaii.Child("textureDetailPane", new Vector2(detailWidth, 0), true)) + { + DrawTextureDetail(selectedRow); + } + ImGui.EndGroup(); } - ImGui.EndGroup(); } private void DrawTextureFilters( @@ -1935,26 +1980,118 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase } } - private void DrawVerticalResizeHandle(string id, float topY, float height, ref float leftWidth, float minWidth, float maxWidth, bool invert = false) + private bool DrawVerticalResizeHandle( + string id, + float topY, + float height, + ref float leftWidth, + float minWidth, + float maxWidth, + out bool isDragging, + bool invert = false, + bool showToggle = false, + bool isCollapsed = false) { var scale = ImGuiHelpers.GlobalScale; - var splitterWidth = 8f * scale; + var splitterWidth = (showToggle + ? (isCollapsed ? TextureDetailSplitterCollapsedWidth : TextureDetailSplitterWidth) + : TextureFilterSplitterWidth) * scale; ImGui.SameLine(); var cursor = ImGui.GetCursorPos(); - ImGui.SetCursorPos(new Vector2(cursor.X, topY)); - ImGui.PushStyleColor(ImGuiCol.Button, UIColors.Get("ButtonDefault")); - ImGui.PushStyleColor(ImGuiCol.ButtonHovered, UIColors.Get("LightlessPurple")); - ImGui.PushStyleColor(ImGuiCol.ButtonActive, UIColors.Get("LightlessPurpleActive")); - ImGui.Button(id, new Vector2(splitterWidth, height)); - ImGui.PopStyleColor(3); + var contentMin = ImGui.GetWindowContentRegionMin(); + var contentMax = ImGui.GetWindowContentRegionMax(); + var clampedTop = MathF.Max(topY, contentMin.Y); + var clampedBottom = MathF.Min(topY + height, contentMax.Y); + var clampedHeight = MathF.Max(0f, clampedBottom - clampedTop); + var splitterRounding = ImGui.GetStyle().FrameRounding; + ImGui.SetCursorPos(new Vector2(cursor.X, clampedTop)); + if (clampedHeight <= 0f) + { + isDragging = false; + ImGui.SetCursorPos(new Vector2(cursor.X + splitterWidth + ImGui.GetStyle().ItemSpacing.X, cursor.Y)); + return false; + } - if (ImGui.IsItemActive()) + ImGui.InvisibleButton(id, new Vector2(splitterWidth, clampedHeight)); + var drawList = ImGui.GetWindowDrawList(); + var rectMin = ImGui.GetItemRectMin(); + var rectMax = ImGui.GetItemRectMax(); + var windowPos = ImGui.GetWindowPos(); + var clipMin = windowPos + contentMin; + var clipMax = windowPos + contentMax; + drawList.PushClipRect(clipMin, clipMax, true); + var clipInset = 1f * scale; + var drawMin = new Vector2( + MathF.Max(rectMin.X, clipMin.X), + MathF.Max(rectMin.Y, clipMin.Y)); + var drawMax = new Vector2( + MathF.Min(rectMax.X, clipMax.X - clipInset), + MathF.Min(rectMax.Y, clipMax.Y)); + var hovered = ImGui.IsItemHovered(); + isDragging = ImGui.IsItemActive(); + var baseColor = UIColors.Get("ButtonDefault"); + var hoverColor = UIColors.Get("LightlessPurple"); + var activeColor = UIColors.Get("LightlessPurpleActive"); + var handleColor = isDragging ? activeColor : hovered ? hoverColor : baseColor; + drawList.AddRectFilled(drawMin, drawMax, UiSharedService.Color(handleColor), splitterRounding); + drawList.AddRect(drawMin, drawMax, UiSharedService.Color(new Vector4(1f, 1f, 1f, 0.12f)), splitterRounding); + + bool toggleHovered = false; + bool toggleClicked = false; + if (showToggle) + { + var icon = isCollapsed ? FontAwesomeIcon.ChevronRight : FontAwesomeIcon.ChevronLeft; + Vector2 iconSize; + using (_uiSharedService.IconFont.Push()) + { + iconSize = ImGui.CalcTextSize(icon.ToIconString()); + } + + var toggleHeight = MathF.Min(clampedHeight, 64f * scale); + var toggleMin = new Vector2( + drawMin.X, + drawMin.Y + (drawMax.Y - drawMin.Y - toggleHeight) / 2f); + var toggleMax = new Vector2( + drawMax.X, + toggleMin.Y + toggleHeight); + var toggleColorBase = UIColors.Get("LightlessPurple"); + toggleHovered = ImGui.IsMouseHoveringRect(toggleMin, toggleMax); + var toggleBg = toggleHovered + ? new Vector4(toggleColorBase.X, toggleColorBase.Y, toggleColorBase.Z, 0.65f) + : new Vector4(toggleColorBase.X, toggleColorBase.Y, toggleColorBase.Z, 0.35f); + if (toggleHovered) + { + UiSharedService.AttachToolTip(isCollapsed ? "Show texture details." : "Hide texture details."); + } + + drawList.AddRectFilled(toggleMin, toggleMax, UiSharedService.Color(toggleBg), splitterRounding); + drawList.AddRect(toggleMin, toggleMax, UiSharedService.Color(toggleColorBase), splitterRounding); + + var iconPos = new Vector2( + drawMin.X + (drawMax.X - drawMin.X - iconSize.X) / 2f, + drawMin.Y + (drawMax.Y - drawMin.Y - iconSize.Y) / 2f); + using (_uiSharedService.IconFont.Push()) + { + drawList.AddText(iconPos, ImGui.GetColorU32(ImGuiCol.Text), icon.ToIconString()); + } + + if (toggleHovered && ImGui.IsMouseReleased(ImGuiMouseButton.Left) && !ImGui.IsMouseDragging(ImGuiMouseButton.Left)) + { + toggleClicked = true; + } + } + + if (isDragging && !toggleHovered) { var delta = ImGui.GetIO().MouseDelta.X / scale; leftWidth += invert ? -delta : delta; leftWidth = Math.Clamp(leftWidth, minWidth, maxWidth); } + + drawList.PopClipRect(); + ImGui.SetCursorPos(new Vector2(cursor.X + splitterWidth + ImGui.GetStyle().ItemSpacing.X, cursor.Y)); + return toggleClicked; } private (IDalamudTextureWrap? Texture, bool IsLoading, string? Error) GetTexturePreview(TextureRow row) @@ -2094,7 +2231,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase } else { - ImGui.TextDisabled("-"); + _uiSharedService.IconText(FontAwesomeIcon.Check, ImGuiColors.DalamudWhite); UiSharedService.AttachToolTip("Already stored in a compressed format; additional compression is disabled."); } @@ -2175,6 +2312,10 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase _textureSelections[key] = target; currentSelection = target; } + if (TextureMetadataHelper.TryGetRecommendationInfo(target, out var targetInfo)) + { + UiSharedService.AttachToolTip($"{targetInfo.Title}{UiSharedService.TooltipSeparator}{targetInfo.Description}"); + } if (targetSelected) { ImGui.SetItemDefaultFocus(); diff --git a/Penumbra.Api b/Penumbra.Api index 1750c41..52a3216 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit 1750c41b53e1000c99a7fb9d8a0f082aef639a41 +Subproject commit 52a3216a525592205198303df2844435e382cf87