using Dalamud.Bindings.ImGui; using Dalamud.Interface; using Dalamud.Interface.Colors; using Dalamud.Interface.Utility.Raii; using MareSynchronos.API.Data.Enum; using MareSynchronos.Interop.Ipc; using MareSynchronos.Services; using MareSynchronos.Services.Mediator; using MareSynchronos.Utils; using Microsoft.Extensions.Logging; using System.Numerics; using MareSynchronos.Localization; using System.Globalization; namespace MareSynchronos.UI; public class DataAnalysisUi : WindowMediatorSubscriberBase { private readonly CharacterAnalyzer _characterAnalyzer; private readonly Progress<(string, int)> _conversionProgress = new(); private readonly IpcManager _ipcManager; private readonly UiSharedService _uiSharedService; private readonly Dictionary _texturesToConvert = new(StringComparer.Ordinal); private Dictionary>? _cachedAnalysis; private CancellationTokenSource _conversionCancellationTokenSource = new(); private string _conversionCurrentFileName = string.Empty; private int _conversionCurrentFileProgress = 0; private Task? _conversionTask; private bool _enableBc7ConversionMode = false; private bool _hasUpdate = false; private bool _sortDirty = true; private bool _modalOpen = false; private string _selectedFileTypeTab = string.Empty; private string _selectedHash = string.Empty; private ObjectKind _selectedObjectTab; private bool _showModal = false; private string L(string key, string fallback, params object[] args) { var safeArgs = args ?? Array.Empty(); return _uiSharedService.Localize(key, fallback, safeArgs); } public DataAnalysisUi(ILogger logger, MareMediator mediator, CharacterAnalyzer characterAnalyzer, IpcManager ipcManager, PerformanceCollectorService performanceCollectorService, UiSharedService uiSharedService) : base(logger, mediator, uiSharedService.Localize("DataAnalysis.WindowTitle", "Character Data Analysis"), performanceCollectorService) { _characterAnalyzer = characterAnalyzer; _ipcManager = ipcManager; _uiSharedService = uiSharedService; Mediator.Subscribe(this, (_) => { _hasUpdate = true; }); SizeConstraints = new() { MinimumSize = new() { X = 800, Y = 600 }, MaximumSize = new() { X = 3840, Y = 2160 } }; _conversionProgress.ProgressChanged += ConversionProgress_ProgressChanged; } protected override void DrawInternal() { var modalTitle = L("DataAnalysis.Bc7.ModalTitle", "BC7 Conversion in Progress"); if (_conversionTask != null && !_conversionTask.IsCompleted) { _showModal = true; if (ImGui.BeginPopupModal(modalTitle)) { ImGui.TextUnformatted(L("DataAnalysis.Bc7.Status", "BC7 Conversion in progress: {0}/{1}", _conversionCurrentFileProgress, _texturesToConvert.Count)); UiSharedService.TextWrapped(L("DataAnalysis.Bc7.CurrentFile", "Current file: {0}", _conversionCurrentFileName)); if (_uiSharedService.IconTextButton(FontAwesomeIcon.StopCircle, L("DataAnalysis.Bc7.Cancel", "Cancel conversion"))) { _conversionCancellationTokenSource.Cancel(); } UiSharedService.SetScaledWindowSize(500); ImGui.EndPopup(); } else { _modalOpen = false; } } else if (_conversionTask != null && _conversionTask.IsCompleted && _texturesToConvert.Count > 0) { _conversionTask = null; _texturesToConvert.Clear(); _showModal = false; _modalOpen = false; _enableBc7ConversionMode = false; } if (_showModal && !_modalOpen) { ImGui.OpenPopup(modalTitle); _modalOpen = true; } if (_hasUpdate) { _cachedAnalysis = _characterAnalyzer.LastAnalysis.DeepClone(); _hasUpdate = false; _sortDirty = true; } UiSharedService.TextWrapped(L("DataAnalysis.Description", "This window shows you all files and their sizes that are currently in use through your character and associated entities")); if (_cachedAnalysis == null || _cachedAnalysis.Count == 0) return; bool isAnalyzing = _characterAnalyzer.IsAnalysisRunning; bool needAnalysis = _cachedAnalysis!.Any(c => c.Value.Any(f => !f.Value.IsComputed)); if (isAnalyzing) { UiSharedService.ColorTextWrapped(L("DataAnalysis.Analyzing", "Analyzing {0}/{1}", _characterAnalyzer.CurrentFile, _characterAnalyzer.TotalFiles), ImGuiColors.DalamudYellow); if (_uiSharedService.IconTextButton(FontAwesomeIcon.StopCircle, L("DataAnalysis.Button.CancelAnalysis", "Cancel analysis"))) { _characterAnalyzer.CancelAnalyze(); } } else { if (needAnalysis) { UiSharedService.ColorTextWrapped(L("DataAnalysis.Analyze.MissingNotice", "Some entries in the analysis have file size not determined yet, press the button below to analyze your current data"), ImGuiColors.DalamudYellow); if (_uiSharedService.IconTextButton(FontAwesomeIcon.PlayCircle, L("DataAnalysis.Button.StartMissing", "Start analysis (missing entries)"))) { _ = _characterAnalyzer.ComputeAnalysis(print: false); } } else { if (_uiSharedService.IconTextButton(FontAwesomeIcon.PlayCircle, L("DataAnalysis.Button.StartAll", "Start analysis (recalculate all entries)"))) { _ = _characterAnalyzer.ComputeAnalysis(print: false, recalculate: true); } } } ImGui.Separator(); ImGui.TextUnformatted(L("DataAnalysis.TotalFiles", "Total files:")); ImGui.SameLine(); ImGui.TextUnformatted(_cachedAnalysis!.Values.Sum(c => c.Values.Count).ToString()); ImGui.SameLine(); using (var font = ImRaii.PushFont(UiBuilder.IconFont)) { ImGui.TextUnformatted(FontAwesomeIcon.InfoCircle.ToIconString()); } if (ImGui.IsItemHovered()) { var groupedfiles = _cachedAnalysis.Values.SelectMany(f => f.Values).GroupBy(f => f.FileType, StringComparer.Ordinal); var summary = string.Join(Environment.NewLine, groupedfiles.OrderBy(f => f.Key, StringComparer.Ordinal) .Select(f => L("DataAnalysis.Tooltip.FileSummary", "{0}: {1} files, size: {2}, compressed: {3}", f.Key, f.Count(), UiSharedService.ByteToString(f.Sum(v => v.OriginalSize)), UiSharedService.ByteToString(f.Sum(v => v.CompressedSize))))); ImGui.SetTooltip(summary); } ImGui.TextUnformatted(L("DataAnalysis.TotalSizeActual", "Total size (actual):")); ImGui.SameLine(); ImGui.TextUnformatted(UiSharedService.ByteToString(_cachedAnalysis!.Sum(c => c.Value.Sum(c => c.Value.OriginalSize)))); ImGui.TextUnformatted(L("DataAnalysis.TotalSizeDownload", "Total size (download size):")); ImGui.SameLine(); using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudYellow, needAnalysis)) { ImGui.TextUnformatted(UiSharedService.ByteToString(_cachedAnalysis!.Sum(c => c.Value.Sum(c => c.Value.CompressedSize)))); if (needAnalysis && !isAnalyzing) { ImGui.SameLine(); using (ImRaii.PushFont(UiBuilder.IconFont)) ImGui.TextUnformatted(FontAwesomeIcon.ExclamationCircle.ToIconString()); UiSharedService.AttachToolTip(L("DataAnalysis.Tooltip.CalculateDownloadSize", "Click \"Start analysis\" to calculate download size")); } } ImGui.TextUnformatted(L("DataAnalysis.TotalTriangles", "Total modded model triangles: {0}", UiSharedService.TrisToString(_cachedAnalysis.Sum(c => c.Value.Sum(f => f.Value.Triangles))))); ImGui.Separator(); using var tabbar = ImRaii.TabBar("objectSelection"); foreach (var kvp in _cachedAnalysis) { using var id = ImRaii.PushId(kvp.Key.ToString()); string tabText = kvp.Key.ToString(); using var tab = ImRaii.TabItem(tabText + "###" + kvp.Key.ToString()); if (tab.Success) { var groupedfiles = kvp.Value.Select(v => v.Value).GroupBy(f => f.FileType, StringComparer.Ordinal) .OrderBy(k => k.Key, StringComparer.Ordinal).ToList(); ImGui.TextUnformatted(L("DataAnalysis.FilesFor", "Files for {0}", kvp.Key)); ImGui.SameLine(); ImGui.TextUnformatted(kvp.Value.Count.ToString()); ImGui.SameLine(); using (var font = ImRaii.PushFont(UiBuilder.IconFont)) { ImGui.TextUnformatted(FontAwesomeIcon.InfoCircle.ToIconString()); } if (ImGui.IsItemHovered()) { var summary = string.Join(Environment.NewLine, groupedfiles .Select(f => L("DataAnalysis.Tooltip.FileSummary", "{0}: {1} files, size: {2}, compressed: {3}", f.Key, f.Count(), UiSharedService.ByteToString(f.Sum(v => v.OriginalSize)), UiSharedService.ByteToString(f.Sum(v => v.CompressedSize))))); ImGui.SetTooltip(summary); } ImGui.TextUnformatted(L("DataAnalysis.Object.SizeActual", "{0} size (actual):", kvp.Key)); ImGui.SameLine(); ImGui.TextUnformatted(UiSharedService.ByteToString(kvp.Value.Sum(c => c.Value.OriginalSize))); ImGui.TextUnformatted(L("DataAnalysis.Object.SizeDownload", "{0} size (download size):", kvp.Key)); ImGui.SameLine(); using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudYellow, needAnalysis)) { ImGui.TextUnformatted(UiSharedService.ByteToString(kvp.Value.Sum(c => c.Value.CompressedSize))); if (needAnalysis && !isAnalyzing) { ImGui.SameLine(); using (ImRaii.PushFont(UiBuilder.IconFont)) ImGui.TextUnformatted(FontAwesomeIcon.ExclamationCircle.ToIconString()); UiSharedService.AttachToolTip(L("DataAnalysis.Tooltip.CalculateDownloadSize", "Click \"Start analysis\" to calculate download size")); } } ImGui.TextUnformatted(L("DataAnalysis.Object.Vram", "{0} VRAM usage:", kvp.Key)); ImGui.SameLine(); var vramUsage = groupedfiles.SingleOrDefault(v => string.Equals(v.Key, "tex", StringComparison.Ordinal)); if (vramUsage != null) { ImGui.TextUnformatted(UiSharedService.ByteToString(vramUsage.Sum(f => f.OriginalSize))); } ImGui.TextUnformatted(L("DataAnalysis.Object.Triangles", "{0} modded model triangles: {1}", kvp.Key, UiSharedService.TrisToString(kvp.Value.Sum(f => f.Value.Triangles)))); ImGui.Separator(); if (_selectedObjectTab != kvp.Key) { _selectedHash = string.Empty; _selectedObjectTab = kvp.Key; _selectedFileTypeTab = string.Empty; _enableBc7ConversionMode = false; _texturesToConvert.Clear(); } using var fileTabBar = ImRaii.TabBar("fileTabs"); foreach (IGrouping? fileGroup in groupedfiles) { string fileGroupText = fileGroup.Key + " [" + fileGroup.Count() + "]"; var requiresCompute = fileGroup.Any(k => !k.IsComputed); using var tabcol = ImRaii.PushColor(ImGuiCol.Tab, UiSharedService.Color(ImGuiColors.DalamudYellow), requiresCompute); ImRaii.IEndObject fileTab; using (var textcol = ImRaii.PushColor(ImGuiCol.Text, UiSharedService.Color(new(0, 0, 0, 1)), requiresCompute && !string.Equals(_selectedFileTypeTab, fileGroup.Key, StringComparison.Ordinal))) { fileTab = ImRaii.TabItem(fileGroupText + "###" + fileGroup.Key); } if (!fileTab) { fileTab.Dispose(); continue; } if (!string.Equals(fileGroup.Key, _selectedFileTypeTab, StringComparison.Ordinal)) { _selectedFileTypeTab = fileGroup.Key; _selectedHash = string.Empty; _enableBc7ConversionMode = false; _texturesToConvert.Clear(); } ImGui.TextUnformatted(L("DataAnalysis.FileGroup.Count", "{0} files", fileGroup.Key)); ImGui.SameLine(); ImGui.TextUnformatted(fileGroup.Count().ToString()); ImGui.TextUnformatted(L("DataAnalysis.FileGroup.SizeActual", "{0} files size (actual):", fileGroup.Key)); ImGui.SameLine(); ImGui.TextUnformatted(UiSharedService.ByteToString(fileGroup.Sum(c => c.OriginalSize))); ImGui.TextUnformatted(L("DataAnalysis.FileGroup.SizeDownload", "{0} files size (download size):", fileGroup.Key)); ImGui.SameLine(); ImGui.TextUnformatted(UiSharedService.ByteToString(fileGroup.Sum(c => c.CompressedSize))); if (string.Equals(_selectedFileTypeTab, "tex", StringComparison.Ordinal)) { ImGui.Checkbox(L("DataAnalysis.Bc7.EnableMode", "Enable BC7 Conversion Mode"), ref _enableBc7ConversionMode); if (_enableBc7ConversionMode) { UiSharedService.ColorText(L("DataAnalysis.Bc7.WarningTitle", "WARNING BC7 CONVERSION:"), ImGuiColors.DalamudYellow); ImGui.SameLine(); UiSharedService.ColorText(L("DataAnalysis.Bc7.WarningIrreversible", "Converting textures to BC7 is irreversible!"), ImGuiColors.DalamudRed); UiSharedService.ColorTextWrapped(L("DataAnalysis.Bc7.WarningDetails", "- Converting textures to BC7 will reduce their size (compressed and uncompressed) drastically. It is recommended to be used for large (4k+) textures.\n" + "- Some textures, especially ones utilizing colorsets, might not be suited for BC7 conversion and might produce visual artifacts.\n" + "- Before converting textures, make sure to have the original files of the mod you are converting so you can reimport it in case of issues.\n" + "- Conversion will convert all found texture duplicates (entries with more than 1 file path) automatically.\n" + "- Converting textures to BC7 is a very expensive operation and, depending on the amount of textures to convert, will take a while to complete."), ImGuiColors.DalamudYellow); if (_texturesToConvert.Count > 0 && _uiSharedService.IconTextButton(FontAwesomeIcon.PlayCircle, L("DataAnalysis.Bc7.StartConversion", "Start conversion of {0} texture(s)", _texturesToConvert.Count))) { _conversionCancellationTokenSource = _conversionCancellationTokenSource.CancelRecreate(); _conversionTask = _ipcManager.Penumbra.ConvertTextureFiles(_logger, _texturesToConvert, _conversionProgress, _conversionCancellationTokenSource.Token); } } } ImGui.Separator(); DrawTable(fileGroup); fileTab.Dispose(); } } } ImGui.Separator(); ImGui.TextUnformatted(L("DataAnalysis.SelectedFile", "Selected file:")); ImGui.SameLine(); UiSharedService.ColorText(_selectedHash, ImGuiColors.DalamudYellow); if (_cachedAnalysis[_selectedObjectTab].TryGetValue(_selectedHash, out CharacterAnalyzer.FileDataEntry? item)) { var filePaths = item.FilePaths; ImGui.TextUnformatted(L("DataAnalysis.LocalFilePath", "Local file path:")); ImGui.SameLine(); UiSharedService.TextWrapped(filePaths[0]); if (filePaths.Count > 1) { ImGui.SameLine(); ImGui.TextUnformatted(L("DataAnalysis.MoreCount", "(and {0} more)", filePaths.Count - 1)); ImGui.SameLine(); _uiSharedService.IconText(FontAwesomeIcon.InfoCircle); UiSharedService.AttachToolTip(string.Join(Environment.NewLine, filePaths.Skip(1))); } var gamepaths = item.GamePaths; ImGui.TextUnformatted(L("DataAnalysis.GamePath", "Used by game path:")); ImGui.SameLine(); UiSharedService.TextWrapped(gamepaths[0]); if (gamepaths.Count > 1) { ImGui.SameLine(); ImGui.TextUnformatted(L("DataAnalysis.MoreCount", "(and {0} more)", gamepaths.Count - 1)); ImGui.SameLine(); _uiSharedService.IconText(FontAwesomeIcon.InfoCircle); UiSharedService.AttachToolTip(string.Join(Environment.NewLine, gamepaths.Skip(1))); } } } public override void OnOpen() { _hasUpdate = true; _selectedHash = string.Empty; _enableBc7ConversionMode = false; _texturesToConvert.Clear(); } protected override void Dispose(bool disposing) { base.Dispose(disposing); _conversionProgress.ProgressChanged -= ConversionProgress_ProgressChanged; } private void ConversionProgress_ProgressChanged(object? sender, (string, int) e) { _conversionCurrentFileName = e.Item1; _conversionCurrentFileProgress = e.Item2; } private void DrawTable(IGrouping fileGroup) { var tableColumns = string.Equals(fileGroup.Key, "tex", StringComparison.Ordinal) ? (_enableBc7ConversionMode ? 7 : 6) : (string.Equals(fileGroup.Key, "mdl", StringComparison.Ordinal) ? 6 : 5); using var table = ImRaii.Table("Analysis", tableColumns, ImGuiTableFlags.Sortable | ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY | ImGuiTableFlags.SizingFixedFit, new Vector2(0, 300)); if (!table.Success) return; ImGui.TableSetupColumn(L("DataAnalysis.Table.Hash", "Hash")); ImGui.TableSetupColumn(L("DataAnalysis.Table.Filepaths", "Filepaths"), ImGuiTableColumnFlags.PreferSortDescending); ImGui.TableSetupColumn(L("DataAnalysis.Table.Gamepaths", "Gamepaths"), ImGuiTableColumnFlags.PreferSortDescending); ImGui.TableSetupColumn(L("DataAnalysis.Table.FileSize", "File Size"), ImGuiTableColumnFlags.DefaultSort | ImGuiTableColumnFlags.PreferSortDescending); ImGui.TableSetupColumn(L("DataAnalysis.Table.DownloadSize", "Download Size"), ImGuiTableColumnFlags.PreferSortDescending); if (string.Equals(fileGroup.Key, "tex", StringComparison.Ordinal)) { ImGui.TableSetupColumn(L("DataAnalysis.Table.Format", "Format")); if (_enableBc7ConversionMode) ImGui.TableSetupColumn(L("DataAnalysis.Table.ConvertToBc7", "Convert to BC7")); } if (string.Equals(fileGroup.Key, "mdl", StringComparison.Ordinal)) { ImGui.TableSetupColumn(L("DataAnalysis.Table.Triangles", "Triangles"), ImGuiTableColumnFlags.PreferSortDescending); } ImGui.TableSetupScrollFreeze(0, 1); ImGui.TableHeadersRow(); var sortSpecs = ImGui.TableGetSortSpecs(); if (sortSpecs.SpecsDirty || _sortDirty) { var idx = sortSpecs.Specs.ColumnIndex; if (idx == 0 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Ascending) _cachedAnalysis![_selectedObjectTab] = _cachedAnalysis[_selectedObjectTab].OrderBy(k => k.Key, StringComparer.Ordinal).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal); if (idx == 0 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Descending) _cachedAnalysis![_selectedObjectTab] = _cachedAnalysis[_selectedObjectTab].OrderByDescending(k => k.Key, StringComparer.Ordinal).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal); if (idx == 1 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Ascending) _cachedAnalysis![_selectedObjectTab] = _cachedAnalysis[_selectedObjectTab].OrderBy(k => k.Value.FilePaths.Count).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal); if (idx == 1 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Descending) _cachedAnalysis![_selectedObjectTab] = _cachedAnalysis[_selectedObjectTab].OrderByDescending(k => k.Value.FilePaths.Count).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal); if (idx == 2 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Ascending) _cachedAnalysis![_selectedObjectTab] = _cachedAnalysis[_selectedObjectTab].OrderBy(k => k.Value.GamePaths.Count).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal); if (idx == 2 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Descending) _cachedAnalysis![_selectedObjectTab] = _cachedAnalysis[_selectedObjectTab].OrderByDescending(k => k.Value.GamePaths.Count).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal); if (idx == 3 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Ascending) _cachedAnalysis![_selectedObjectTab] = _cachedAnalysis[_selectedObjectTab].OrderBy(k => k.Value.OriginalSize).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal); if (idx == 3 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Descending) _cachedAnalysis![_selectedObjectTab] = _cachedAnalysis[_selectedObjectTab].OrderByDescending(k => k.Value.OriginalSize).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal); if (idx == 4 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Ascending) _cachedAnalysis![_selectedObjectTab] = _cachedAnalysis[_selectedObjectTab].OrderBy(k => k.Value.CompressedSize).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal); if (idx == 4 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Descending) _cachedAnalysis![_selectedObjectTab] = _cachedAnalysis[_selectedObjectTab].OrderByDescending(k => k.Value.CompressedSize).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal); if (string.Equals(fileGroup.Key, "mdl", StringComparison.Ordinal) && idx == 5 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Ascending) _cachedAnalysis![_selectedObjectTab] = _cachedAnalysis[_selectedObjectTab].OrderBy(k => k.Value.Triangles).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal); if (string.Equals(fileGroup.Key, "mdl", StringComparison.Ordinal) && idx == 5 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Descending) _cachedAnalysis![_selectedObjectTab] = _cachedAnalysis[_selectedObjectTab].OrderByDescending(k => k.Value.Triangles).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal); if (string.Equals(fileGroup.Key, "tex", StringComparison.Ordinal) && idx == 5 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Ascending) _cachedAnalysis![_selectedObjectTab] = _cachedAnalysis[_selectedObjectTab].OrderBy(k => k.Value.Format.Value, StringComparer.Ordinal).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal); if (string.Equals(fileGroup.Key, "tex", StringComparison.Ordinal) && idx == 5 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Descending) _cachedAnalysis![_selectedObjectTab] = _cachedAnalysis[_selectedObjectTab].OrderByDescending(k => k.Value.Format.Value, StringComparer.Ordinal).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal); sortSpecs.SpecsDirty = false; _sortDirty = false; } foreach (var item in fileGroup) { using var text = ImRaii.PushColor(ImGuiCol.Text, new Vector4(0, 0, 0, 1), string.Equals(item.Hash, _selectedHash, StringComparison.Ordinal)); using var text2 = ImRaii.PushColor(ImGuiCol.Text, new Vector4(1, 1, 1, 1), !item.IsComputed); ImGui.TableNextColumn(); if (string.Equals(_selectedHash, item.Hash, StringComparison.Ordinal)) { ImGui.TableSetBgColor(ImGuiTableBgTarget.RowBg1, UiSharedService.Color(ImGuiColors.DalamudYellow)); ImGui.TableSetBgColor(ImGuiTableBgTarget.RowBg0, UiSharedService.Color(ImGuiColors.DalamudYellow)); } ImGui.TextUnformatted(item.Hash); if (ImGui.IsItemClicked()) _selectedHash = item.Hash; ImGui.TableNextColumn(); ImGui.TextUnformatted(item.FilePaths.Count.ToString()); if (ImGui.IsItemClicked()) _selectedHash = item.Hash; ImGui.TableNextColumn(); ImGui.TextUnformatted(item.GamePaths.Count.ToString()); if (ImGui.IsItemClicked()) _selectedHash = item.Hash; ImGui.TableNextColumn(); ImGui.TextUnformatted(UiSharedService.ByteToString(item.OriginalSize)); if (ImGui.IsItemClicked()) _selectedHash = item.Hash; ImGui.TableNextColumn(); using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudYellow, !item.IsComputed)) ImGui.TextUnformatted(UiSharedService.ByteToString(item.CompressedSize)); if (ImGui.IsItemClicked()) _selectedHash = item.Hash; if (string.Equals(fileGroup.Key, "tex", StringComparison.Ordinal)) { ImGui.TableNextColumn(); ImGui.TextUnformatted(item.Format.Value); if (ImGui.IsItemClicked()) _selectedHash = item.Hash; if (_enableBc7ConversionMode) { ImGui.TableNextColumn(); if (item.Format.Value.StartsWith("BC", StringComparison.Ordinal) || item.Format.Value.StartsWith("DXT", StringComparison.Ordinal) || item.Format.Value.StartsWith("24864", StringComparison.Ordinal)) // BC4 { ImGui.TextUnformatted(""); continue; } var filePath = item.FilePaths[0]; bool toConvert = _texturesToConvert.ContainsKey(filePath); if (ImGui.Checkbox("###convert" + item.Hash, ref toConvert)) { if (toConvert && !_texturesToConvert.ContainsKey(filePath)) { _texturesToConvert[filePath] = item.FilePaths.Skip(1).ToArray(); } else if (!toConvert && _texturesToConvert.ContainsKey(filePath)) { _texturesToConvert.Remove(filePath); } } } } if (string.Equals(fileGroup.Key, "mdl", StringComparison.Ordinal)) { ImGui.TableNextColumn(); ImGui.TextUnformatted(UiSharedService.TrisToString(item.Triangles)); if (ImGui.IsItemClicked()) _selectedHash = item.Hash; } } } }