private unsafe void DrawResourceMap(ResourceCategory category, uint ext, StdMap <uint, Pointer <ResourceHandle> > *map) { if (map == null) { return; } var label = GetNodeLabel(( uint )category, ext, map->Count); using var tree = ImRaii.TreeNode(label); if (!tree || map->Count == 0) { return; } using var table = ImRaii.Table("##table", 4, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg); if (!table) { return; } ImGui.TableSetupColumn("Hash", ImGuiTableColumnFlags.WidthFixed, _hashColumnWidth); ImGui.TableSetupColumn("Ptr", ImGuiTableColumnFlags.WidthFixed, _hashColumnWidth); ImGui.TableSetupColumn("Path", ImGuiTableColumnFlags.WidthFixed, _pathColumnWidth); ImGui.TableSetupColumn("Refs", ImGuiTableColumnFlags.WidthFixed, _refsColumnWidth); ImGui.TableHeadersRow(); ResourceLoader.IterateResourceMap(map, (hash, r) => { // Filter unwanted names. if (_resourceManagerFilter.Length != 0 && !r->FileName.ToString().Contains(_resourceManagerFilter, StringComparison.InvariantCultureIgnoreCase)) { return; } var address = $"0x{( ulong )r:X}"; ImGuiUtil.TextNextColumn($"0x{hash:X8}"); ImGui.TableNextColumn(); ImGuiUtil.CopyOnClickSelectable(address); var resource = (Interop.Structs.ResourceHandle *)r; ImGui.TableNextColumn(); Text(resource); if (ImGui.IsItemClicked()) { var data = Interop.Structs.ResourceHandle.GetData(resource); if (data != null) { var length = ( int )Interop.Structs.ResourceHandle.GetLength(resource); ImGui.SetClipboardText(string.Join(" ", new ReadOnlySpan <byte>(data, length).ToArray().Select(b => b.ToString("X2")))); } } ImGuiUtil.HoverTooltip("Click to copy byte-wise file data to clipboard, if any."); ImGuiUtil.TextNextColumn(r->RefCount.ToString()); }); }
// Draw either a website button if the source is a valid website address, // or a source text if it is not. private void DrawWebsite() { if (_websiteValid) { if (ImGui.SmallButton(_modWebsiteButton)) { try { var process = new ProcessStartInfo(_modWebsite) { UseShellExecute = true, }; Process.Start(process); } catch { // ignored } } ImGuiUtil.HoverTooltip(_modWebsite); } else { using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero); ImGuiUtil.TextColored(Colors.MetaInfoText, "from "); ImGui.SameLine(); style.Pop(); ImGui.TextUnformatted(_mod.Website); } }
private void PathInputBox(string label, string hint, string tooltip, int which) { var tmp = which == 0 ? _pathLeft : _pathRight; using var spacing = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, new Vector2(3 * ImGuiHelpers.GlobalScale, 0)); ImGui.SetNextItemWidth(-ImGui.GetFrameHeight() - 3 * ImGuiHelpers.GlobalScale); ImGui.InputTextWithHint(label, hint, ref tmp, Utf8GamePath.MaxGamePathLength); if (ImGui.IsItemDeactivatedAfterEdit()) { UpdateImage(tmp, which); } ImGuiUtil.HoverTooltip(tooltip); ImGui.SameLine(); if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Folder.ToIconString(), new Vector2(ImGui.GetFrameHeight()), string.Empty, false, true)) { var startPath = Penumbra.Config.DefaultModImportPath.Length > 0 ? Penumbra.Config.DefaultModImportPath : _mod?.ModPath.FullName; void UpdatePath(bool success, List <string> paths) { if (success && paths.Count > 0) { UpdateImage(paths[0], which); } } _dialogManager.OpenFileDialog("Open Image...", "Textures{.png,.dds,.tex}", UpdatePath, 1, startPath); } }
// Different supported sort modes as a combo. private void DrawFolderSortType() { var sortMode = Penumbra.Config.SortMode; ImGui.SetNextItemWidth(_window._inputTextWidth.X); using var combo = ImRaii.Combo("##sortMode", sortMode.Data().Name); if (combo) { foreach (var val in Enum.GetValues <SortMode>()) { var(name, desc) = val.Data(); if (ImGui.Selectable(name, val == sortMode) && val != sortMode) { Penumbra.Config.SortMode = val; _window._selector.SetFilterDirty(); Penumbra.Config.Save(); } ImGuiUtil.HoverTooltip(desc); } } combo.Dispose(); ImGuiUtil.LabeledHelpMarker("Sort Mode", "Choose the sort mode for the mod selector in the mods tab."); }
private static void DrawReloadResourceButton() { if (ImGui.Button("Reload Resident Resources")) { Penumbra.ResidentResources.Reload(); } ImGuiUtil.HoverTooltip("Reload some specific files that the game keeps in memory at all times.\n" + "You usually should not need to do this."); }
// Draw information about the character utility class from SE, // displaying all files, their sizes, the default files and the default sizes. public unsafe void DrawDebugCharacterUtility() { if (!ImGui.CollapsingHeader("Character Utility")) { return; } using var table = ImRaii.Table("##CharacterUtility", 6, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit, -Vector2.UnitX); if (!table) { return; } for (var i = 0; i < CharacterUtility.RelevantIndices.Length; ++i) { var idx = CharacterUtility.RelevantIndices[i]; var resource = ( ResourceHandle * )Penumbra.CharacterUtility.Address->Resources[idx]; ImGui.TableNextColumn(); ImGui.TextUnformatted($"0x{( ulong )resource:X}"); ImGui.TableNextColumn(); Text(resource); ImGui.TableNextColumn(); ImGui.Selectable($"0x{resource->GetData().Data:X}"); if (ImGui.IsItemClicked()) { var(data, length) = resource->GetData(); if (data != IntPtr.Zero && length > 0) { ImGui.SetClipboardText(string.Join("\n", new ReadOnlySpan <byte>(( byte * )data, length).ToArray().Select(b => b.ToString("X2")))); } } ImGuiUtil.HoverTooltip("Click to copy bytes to clipboard."); ImGui.TableNextColumn(); ImGui.TextUnformatted($"{resource->GetData().Length}"); ImGui.TableNextColumn(); ImGui.Selectable($"0x{Penumbra.CharacterUtility.DefaultResources[ i ].Address:X}"); if (ImGui.IsItemClicked()) { ImGui.SetClipboardText(string.Join("\n", new ReadOnlySpan <byte>(( byte * )Penumbra.CharacterUtility.DefaultResources[i].Address, Penumbra.CharacterUtility.DefaultResources[i].Size).ToArray().Select(b => b.ToString("X2")))); } ImGuiUtil.HoverTooltip("Click to copy bytes to clipboard."); ImGui.TableNextColumn(); ImGui.TextUnformatted($"{Penumbra.CharacterUtility.DefaultResources[ i ].Size}"); } }
public static void DrawNew(Mod.Editor editor, Vector2 iconSize) { ImGui.TableNextColumn(); CopyToClipboardButton("Copy all current EQP manipulations to clipboard.", iconSize, editor.Meta.Eqp.Select(m => (MetaManipulation) m)); ImGui.TableNextColumn(); var canAdd = editor.Meta.CanAdd(_new); var tt = canAdd ? "Stage this edit." : "This entry is already edited."; var defaultEntry = ExpandedEqpFile.GetDefault(_new.SetId); if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), iconSize, tt, !canAdd, true)) { editor.Meta.Add(_new with { Entry = defaultEntry }); } // Identifier ImGui.TableNextColumn(); if (IdInput("##eqpId", IdWidth, _new.SetId, out var setId, ExpandedEqpGmpBase.Count - 1)) { _new = _new with { SetId = setId }; } ImGuiUtil.HoverTooltip("Model Set ID"); ImGui.TableNextColumn(); if (EqpEquipSlotCombo("##eqpSlot", _new.Slot, out var slot)) { _new = _new with { Slot = slot }; } ImGuiUtil.HoverTooltip("Equip Slot"); // Values ImGui.TableNextColumn(); using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, new Vector2(3 * ImGuiHelpers.GlobalScale, ImGui.GetStyle().ItemSpacing.Y)); foreach (var flag in Eqp.EqpAttributes[_new.Slot]) { var value = defaultEntry.HasFlag(flag); Checkmark("##eqp", flag.ToLocalName(), value, value, out _); ImGui.SameLine(); } ImGui.NewLine(); }
private static void DrawOpenDirectoryButton(int id, DirectoryInfo directory, bool condition) { using var _ = ImRaii.PushId(id); var ret = ImGui.Button("Open Directory"); ImGuiUtil.HoverTooltip("Open this directory in your configured file explorer."); if (ret && condition && Directory.Exists(directory.FullName)) { Process.Start(new ProcessStartInfo(directory.FullName) { UseShellExecute = true, }); } }
// Draw resources with unusual reference count. private static unsafe void DrawResourceProblems() { var header = ImGui.CollapsingHeader("Resource Problems"); ImGuiUtil.HoverTooltip("Draw resources with unusually high reference count to detect overflows."); if (!header) { return; } using var table = ImRaii.Table("##ProblemsTable", 6, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit); if (!table) { return; } ResourceLoader.IterateResources((_, r) => { if (r->RefCount < 10000) { return; } ImGui.TableNextColumn(); ImGui.TextUnformatted(r->Category.ToString()); ImGui.TableNextColumn(); ImGui.TextUnformatted(r->FileType.ToString("X")); ImGui.TableNextColumn(); ImGui.TextUnformatted(r->Id.ToString("X")); ImGui.TableNextColumn(); ImGui.TextUnformatted((( ulong )r).ToString("X")); ImGui.TableNextColumn(); ImGui.TextUnformatted(r->RefCount.ToString()); ImGui.TableNextColumn(); ref var name = ref r->FileName; if (name.Capacity > 15) { ImGuiNative.igTextUnformatted(name.BufferPtr, name.BufferPtr + name.Length); } else { fixed(byte *ptr = name.Buffer) { ImGuiNative.igTextUnformatted(ptr, ptr + name.Length); } } });
// Draw a big red bar if the current setting is inherited. private void DrawInheritedWarning() { if (!_inherited) { return; } using var color = ImRaii.PushColor(ImGuiCol.Button, Colors.PressEnterWarningBg); var width = new Vector2(ImGui.GetContentRegionAvail().X, 0); if (ImGui.Button($"These settings are inherited from {_collection.Name}.", width)) { Penumbra.CollectionManager.Current.SetModInheritance(_mod.Index, false); } ImGuiUtil.HoverTooltip("You can click this button to copy the current settings to the current selection.\n" + "You can also just change any setting, which will copy the settings with the single setting changed to the current selection."); }
// Draw a button to remove the current settings and inherit them instead // on the top-right corner of the window/tab. private void DrawRemoveSettings() { const string text = "Inherit Settings"; if (_inherited || _emptySetting) { return; } var scroll = ImGui.GetScrollMaxY() > 0 ? ImGui.GetStyle().ScrollbarSize : 0; ImGui.SameLine(ImGui.GetWindowWidth() - ImGui.CalcTextSize(text).X - ImGui.GetStyle().FramePadding.X * 2 - scroll); if (ImGui.Button(text)) { Penumbra.CollectionManager.Current.SetModInheritance(_mod.Index, true); } ImGuiUtil.HoverTooltip("Remove current settings from this collection so that it can inherit them.\n" + "If no inherited collection has settings for this mod, it will be disabled."); }
public static void Draw(EqpManipulation meta, Mod.Editor editor, Vector2 iconSize) { DrawMetaButtons(meta, editor, iconSize); // Identifier ImGui.TableNextColumn(); ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X); ImGui.TextUnformatted(meta.SetId.ToString()); ImGuiUtil.HoverTooltip("Model Set ID"); var defaultEntry = ExpandedEqpFile.GetDefault(meta.SetId); ImGui.TableNextColumn(); ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X); ImGui.TextUnformatted(meta.Slot.ToName()); ImGuiUtil.HoverTooltip("Equip Slot"); // Values ImGui.TableNextColumn(); using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, new Vector2(3 * ImGuiHelpers.GlobalScale, ImGui.GetStyle().ItemSpacing.Y)); var idx = 0; foreach (var flag in Eqp.EqpAttributes[meta.Slot]) { using var id = ImRaii.PushId(idx++); var defaultValue = defaultEntry.HasFlag(flag); var currentValue = meta.Entry.HasFlag(flag); if (Checkmark("##eqp", flag.ToLocalName(), currentValue, defaultValue, out var value)) { editor.Meta.Change(meta with { Entry = value ? meta.Entry | flag : meta.Entry & ~flag }); } ImGui.SameLine(); } ImGui.NewLine(); }
private void DrawTabBar() { ImGui.Dummy(_window._defaultSpace); using var tabBar = ImRaii.TabBar("##ModTabs"); if (!tabBar) { return; } _availableTabs = Tabs.Settings | (_mod.ChangedItems.Count > 0 ? Tabs.ChangedItems : 0) | (_mod.Description.Length > 0 ? Tabs.Description : 0) | (_conflicts.Count > 0 ? Tabs.Conflicts : 0) | (Penumbra.Config.ShowAdvanced ? Tabs.Edit : 0); DrawSettingsTab(); DrawDescriptionTab(); DrawChangedItemsTab(); DrawConflictsTab(); DrawEditModTab(); if (Penumbra.Config.ShowAdvanced && ImGui.TabItemButton("Advanced Editing", ImGuiTabItemFlags.Trailing | ImGuiTabItemFlags.NoTooltip)) { _window.ModEditPopup.ChangeMod(_mod); _window.ModEditPopup.ChangeOption(-1, 0); _window.ModEditPopup.IsOpen = true; } ImGuiUtil.HoverTooltip( "Clicking this will open a new window in which you can\nedit the following things per option for this mod:\n\n" + "\t\t- file redirections\n" + "\t\t- file swaps\n" + "\t\t- metadata manipulations\n" + "\t\t- model materials\n" + "\t\t- duplicates\n" + "\t\t- textures"); }
public static void DrawDiscordButton(float width) { const string discord = "Join Discord for Support"; const string address = @"https://discord.gg/kVva7DHV4r"; using var color = ImRaii.PushColor(ImGuiCol.Button, Colors.DiscordColor); if (ImGui.Button(discord, new Vector2(width, 0))) { try { var process = new ProcessStartInfo(address) { UseShellExecute = true, }; Process.Start(process); } catch { // ignored } } ImGuiUtil.HoverTooltip($"Open {address}"); }
// Draw a line for a single option. private static void EditOption(ModPanel panel, IModGroup group, int groupIdx, int optionIdx) { var option = group[optionIdx]; using var id = ImRaii.PushId(optionIdx); ImGui.TableNextColumn(); ImGui.AlignTextToFramePadding(); ImGui.Selectable($"Option #{optionIdx + 1}"); Source(group, groupIdx, optionIdx); Target(panel, group, groupIdx, optionIdx); ImGui.TableNextColumn(); if (Input.Text("##Name", groupIdx, optionIdx, option.Name, out var newOptionName, 256, -1)) { Penumbra.ModManager.RenameOption(panel._mod, groupIdx, optionIdx, newOptionName); } ImGui.TableNextColumn(); if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), panel._window._iconButtonSize, "Delete this option.\nHold Control while clicking to delete.", !ImGui.GetIO().KeyCtrl, true)) { panel._delayedActions.Enqueue(() => Penumbra.ModManager.DeleteOption(panel._mod, groupIdx, optionIdx)); } ImGui.TableNextColumn(); if (group.Type == SelectType.Multi) { if (Input.Priority("##Priority", groupIdx, optionIdx, group.OptionPriority(optionIdx), out var priority, 50 * ImGuiHelpers.GlobalScale)) { Penumbra.ModManager.ChangeOptionPriority(panel._mod, groupIdx, optionIdx, priority); } ImGuiUtil.HoverTooltip("Option priority."); } }
private void EditGroup(int groupIdx) { var group = _mod.Groups[groupIdx]; using var id = ImRaii.PushId(groupIdx); using var frame = ImRaii.FramedGroup($"Group #{groupIdx + 1}"); using var style = ImRaii.PushStyle(ImGuiStyleVar.CellPadding, _cellPadding) .Push(ImGuiStyleVar.ItemSpacing, _itemSpacing); if (Input.Text("##Name", groupIdx, Input.None, group.Name, out var newGroupName, 256, _window._inputTextWidth.X)) { Penumbra.ModManager.RenameModGroup(_mod, groupIdx, newGroupName); } ImGuiUtil.HoverTooltip("Group Name"); ImGui.SameLine(); if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), _window._iconButtonSize, "Delete this option group.\nHold Control while clicking to delete.", !ImGui.GetIO().KeyCtrl, true)) { _delayedActions.Enqueue(() => Penumbra.ModManager.DeleteModGroup(_mod, groupIdx)); } ImGui.SameLine(); if (Input.Priority("##Priority", groupIdx, Input.None, group.Priority, out var priority, 50 * ImGuiHelpers.GlobalScale)) { Penumbra.ModManager.ChangeGroupPriority(_mod, groupIdx, priority); } ImGuiUtil.HoverTooltip("Group Priority"); DrawGroupCombo(group, groupIdx); ImGui.SameLine(); var tt = groupIdx == 0 ? "Can not move this group further upwards." : $"Move this group up to group {groupIdx}."; if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.ArrowUp.ToIconString(), _window._iconButtonSize, tt, groupIdx == 0, true)) { _delayedActions.Enqueue(() => Penumbra.ModManager.MoveModGroup(_mod, groupIdx, groupIdx - 1)); } ImGui.SameLine(); tt = groupIdx == _mod.Groups.Count - 1 ? "Can not move this group further downwards." : $"Move this group down to group {groupIdx + 2}."; if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.ArrowDown.ToIconString(), _window._iconButtonSize, tt, groupIdx == _mod.Groups.Count - 1, true)) { _delayedActions.Enqueue(() => Penumbra.ModManager.MoveModGroup(_mod, groupIdx, groupIdx + 1)); } ImGui.SameLine(); if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Edit.ToIconString(), _window._iconButtonSize, "Edit group description.", false, true)) { _delayedActions.Enqueue(() => DescriptionEdit.OpenPopup(_mod, groupIdx)); } ImGui.SameLine(); var fileName = group.FileName(_mod.ModPath, groupIdx); var fileExists = File.Exists(fileName); tt = fileExists ? $"Open the {group.Name} json file in the text editor of your choice." : $"The {group.Name} json file does not exist."; if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.FileExport.ToIconString(), _window._iconButtonSize, tt, !fileExists, true)) { Process.Start(new ProcessStartInfo(fileName) { UseShellExecute = true }); } ImGui.Dummy(_window._defaultSpace); OptionTable.Draw(this, groupIdx); }
// Draw a simple clipped table containing all changed items. private void DrawChangedItemTab() { // Functions in here for less pollution. bool FilterChangedItem(KeyValuePair <string, (SingleArray <IMod>, object?)> item) => (_changedItemFilter.IsEmpty || ChangedItemName(item.Key, item.Value.Item2) .Contains(_changedItemFilter.Lower, StringComparison.InvariantCultureIgnoreCase)) && (_changedItemModFilter.IsEmpty || item.Value.Item1.Any(m => m.Name.Contains(_changedItemModFilter))); void DrawChangedItemColumn(KeyValuePair <string, (SingleArray <IMod>, object?)> item) { ImGui.TableNextColumn(); DrawChangedItem(item.Key, item.Value.Item2, false); ImGui.TableNextColumn(); if (item.Value.Item1.Count > 0) { ImGui.TextUnformatted(item.Value.Item1[0].Name); if (item.Value.Item1.Count > 1) { ImGuiUtil.HoverTooltip(string.Join("\n", item.Value.Item1.Skip(1).Select(m => m.Name))); } } ImGui.TableNextColumn(); if (item.Value.Item2 is Item it) { using var color = ImRaii.PushColor(ImGuiCol.Text, ColorId.ItemId.Value()); ImGuiUtil.RightAlign($"({( ( Quad )it.ModelMain ).A})"); } } using var tab = ImRaii.TabItem("Changed Items"); if (!tab) { return; } // Draw filters. var varWidth = ImGui.GetContentRegionAvail().X - 400 * ImGuiHelpers.GlobalScale - ImGui.GetStyle().ItemSpacing.X; ImGui.SetNextItemWidth(400 * ImGuiHelpers.GlobalScale); LowerString.InputWithHint("##changedItemsFilter", "Filter Item...", ref _changedItemFilter, 128); ImGui.SameLine(); ImGui.SetNextItemWidth(varWidth); LowerString.InputWithHint("##changedItemsModFilter", "Filter Mods...", ref _changedItemModFilter, 128); using var child = ImRaii.Child("##changedItemsChild", -Vector2.One); if (!child) { return; } // Draw table of changed items. var height = ImGui.GetTextLineHeightWithSpacing() + 2 * ImGui.GetStyle().CellPadding.Y; var skips = ImGuiClip.GetNecessarySkips(height); using var list = ImRaii.Table("##changedItems", 3, ImGuiTableFlags.RowBg, -Vector2.One); if (!list) { return; } const ImGuiTableColumnFlags flags = ImGuiTableColumnFlags.NoResize | ImGuiTableColumnFlags.WidthFixed; ImGui.TableSetupColumn("items", flags, 400 * ImGuiHelpers.GlobalScale); ImGui.TableSetupColumn("mods", flags, varWidth - 100 * ImGuiHelpers.GlobalScale); ImGui.TableSetupColumn("id", flags, 100 * ImGuiHelpers.GlobalScale); var items = Penumbra.CollectionManager.Current.ChangedItems; var rest = _changedItemFilter.IsEmpty && _changedItemModFilter.IsEmpty ? ImGuiClip.ClippedDraw(items, skips, DrawChangedItemColumn, items.Count) : ImGuiClip.FilteredClippedDraw(items, skips, FilterChangedItem, DrawChangedItemColumn); ImGuiClip.DrawEndDummy(rest, height); }