Code commit

This commit is contained in:
RisaDev
2024-01-06 01:21:41 +03:00
parent a7d7297c59
commit a486dd2c96
90 changed files with 11576 additions and 0 deletions

View File

@@ -0,0 +1,237 @@
using Dalamud.Interface;
using Dalamud.Plugin.Services;
using ImGuiNET;
using OtterGui.Classes;
using OtterGui.FileSystem.Selector;
using OtterGui.Filesystem;
using OtterGui.Log;
using OtterGui;
using System;
using static CustomizePlus.UI.Windows.MainWindow.Tabs.Profiles.ProfileFileSystemSelector;
using OtterGui.Raii;
using System.Numerics;
using System.Reflection;
using CustomizePlus.Profiles;
using CustomizePlus.Configuration.Data;
using CustomizePlus.Profiles.Data;
using CustomizePlus.Game.Services;
using CustomizePlus.Profiles.Events;
namespace CustomizePlus.UI.Windows.MainWindow.Tabs.Profiles;
public class ProfileFileSystemSelector : FileSystemSelector<Profile, ProfileState>
{
private readonly PluginConfiguration _configuration;
private readonly ProfileManager _profileManager;
private readonly ProfileChanged _event;
private readonly GameObjectService _gameObjectService;
private Profile? _cloneProfile;
private string _newName = string.Empty;
public bool IncognitoMode
{
get => _configuration.UISettings.IncognitoMode;
set
{
_configuration.UISettings.IncognitoMode = value;
_configuration.Save();
}
}
public struct ProfileState
{
public ColorId Color;
}
public ProfileFileSystemSelector(
ProfileFileSystem fileSystem,
IKeyState keyState,
Logger logger,
PluginConfiguration configuration,
ProfileManager profileManager,
ProfileChanged @event,
GameObjectService gameObjectService)
: base(fileSystem, keyState, logger, allowMultipleSelection: true)
{
_configuration = configuration;
_profileManager = profileManager;
_event = @event;
_gameObjectService = gameObjectService;
_event.Subscribe(OnProfileChange, ProfileChanged.Priority.ProfileFileSystemSelector);
AddButton(NewButton, 0);
AddButton(CloneButton, 20);
AddButton(DeleteButton, 1000);
SetFilterTooltip();
}
public void Dispose()
{
base.Dispose();
_event.Unsubscribe(OnProfileChange);
}
protected override uint ExpandedFolderColor
=> ColorId.FolderExpanded.Value();
protected override uint CollapsedFolderColor
=> ColorId.FolderCollapsed.Value();
protected override uint FolderLineColor
=> ColorId.FolderLine.Value();
protected override bool FoldersDefaultOpen
=> _configuration.UISettings.FoldersDefaultOpen;
protected override void DrawLeafName(FileSystem<Profile>.Leaf leaf, in ProfileState state, bool selected)
{
var flag = selected ? ImGuiTreeNodeFlags.Selected | LeafFlags : LeafFlags;
var name = IncognitoMode ? leaf.Value.Incognito : leaf.Value.Name.Text;
using var color = ImRaii.PushColor(ImGuiCol.Text, state.Color.Value());
using var _ = ImRaii.TreeNode(name, flag);
}
protected override void DrawPopups()
{
DrawNewProfilePopup();
}
private void DrawNewProfilePopup()
{
if (!ImGuiUtil.OpenNameField("##NewProfile", ref _newName))
return;
if (_cloneProfile != null)
{
_profileManager.Clone(_cloneProfile, _newName, true);
_cloneProfile = null;
}
else
{
_profileManager.Create(_newName, true);
}
_newName = string.Empty;
}
private void OnProfileChange(ProfileChanged.Type type, Profile? profile, object? arg3 = null)
{
switch (type)
{
case ProfileChanged.Type.Created:
case ProfileChanged.Type.Deleted:
case ProfileChanged.Type.Renamed:
case ProfileChanged.Type.Toggled:
case ProfileChanged.Type.ChangedCharacterName:
case ProfileChanged.Type.ReloadedAll:
SetFilterDirty();
break;
}
}
private void NewButton(Vector2 size)
{
if (!ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), size, "Create a new profile with default configuration.", false,
true))
return;
ImGui.OpenPopup("##NewProfile");
}
private void CloneButton(Vector2 size)
{
var tt = SelectedLeaf == null
? "No profile selected."
: "Clone the currently selected profile to a duplicate";
if (!ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Clone.ToIconString(), size, tt, SelectedLeaf == null, true))
return;
_cloneProfile = Selected!;
ImGui.OpenPopup("##NewProfile");
}
private void DeleteButton(Vector2 size)
=> DeleteSelectionButton(size, _configuration.UISettings.DeleteTemplateModifier, "profile", "profiles", _profileManager.Delete);
#region Filters
private const StringComparison IgnoreCase = StringComparison.OrdinalIgnoreCase;
private LowerString _filter = LowerString.Empty;
private int _filterType = -1;
private void SetFilterTooltip()
{
FilterTooltip = "Filter profiles for those where their full paths or names contain the given substring.\n"
+ "Enter n:[string] to filter only for profile names and no paths.";
}
/// <summary> Appropriately identify and set the string filter and its type. </summary>
protected override bool ChangeFilter(string filterValue)
{
(_filter, _filterType) = filterValue.Length switch
{
0 => (LowerString.Empty, -1),
> 1 when filterValue[1] == ':' =>
filterValue[0] switch
{
'n' => filterValue.Length == 2 ? (LowerString.Empty, -1) : (new LowerString(filterValue[2..]), 1),
'N' => filterValue.Length == 2 ? (LowerString.Empty, -1) : (new LowerString(filterValue[2..]), 1),
_ => (new LowerString(filterValue), 0),
},
_ => (new LowerString(filterValue), 0),
};
return true;
}
/// <summary>
/// The overwritten filter method also computes the state.
/// Folders have default state and are filtered out on the direct string instead of the other options.
/// If any filter is set, they should be hidden by default unless their children are visible,
/// or they contain the path search string.
/// </summary>
protected override bool ApplyFiltersAndState(FileSystem<Profile>.IPath path, out ProfileState state)
{
if (path is ProfileFileSystem.Folder f)
{
state = default;
return FilterValue.Length > 0 && !f.FullName().Contains(FilterValue, IgnoreCase);
}
return ApplyFiltersAndState((ProfileFileSystem.Leaf)path, out state);
}
/// <summary> Apply the string filters. </summary>
private bool ApplyStringFilters(ProfileFileSystem.Leaf leaf, Profile profile)
{
return _filterType switch
{
-1 => false,
0 => !(_filter.IsContained(leaf.FullName()) || profile.Name.Contains(_filter)),
1 => !profile.Name.Contains(_filter),
_ => false, // Should never happen
};
}
/// <summary> Combined wrapper for handling all filters and setting state. </summary>
private bool ApplyFiltersAndState(ProfileFileSystem.Leaf leaf, out ProfileState state)
{
//Do not display temporary profiles;
if (leaf.Value.IsTemporary)
{
state.Color = ColorId.DisabledProfile;
return false;
}
if (leaf.Value.Enabled)
state.Color = leaf.Value.CharacterName == _gameObjectService.GetCurrentPlayerName() ? ColorId.LocalCharacterEnabledProfile : ColorId.EnabledProfile;
else
state.Color = leaf.Value.CharacterName == _gameObjectService.GetCurrentPlayerName() ? ColorId.LocalCharacterDisabledProfile : ColorId.DisabledProfile;
return ApplyStringFilters(leaf, leaf.Value);
}
#endregion
}

View File

@@ -0,0 +1,307 @@
using Dalamud.Interface;
using Dalamud.Interface.Utility;
using ImGuiNET;
using OtterGui;
using OtterGui.Raii;
using System;
using System.Linq;
using System.Numerics;
using CustomizePlus.Profiles;
using CustomizePlus.Game.Services;
using CustomizePlus.Configuration.Data;
using CustomizePlus.Profiles.Data;
using CustomizePlus.UI.Windows.Controls;
namespace CustomizePlus.UI.Windows.MainWindow.Tabs.Profiles;
public class ProfilePanel
{
private readonly ProfileFileSystemSelector _selector;
private readonly ProfileManager _manager;
private readonly PluginConfiguration _configuration;
private readonly TemplateCombo _templateCombo;
private readonly GameStateService _gameStateService;
private string? _newName;
private string? _newCharacterName;
private Profile? _changedProfile;
private Action? _endAction;
private int _dragIndex = -1;
private string SelectionName
=> _selector.Selected == null ? "No Selection" : _selector.IncognitoMode ? _selector.Selected.Incognito : _selector.Selected.Name.Text;
public ProfilePanel(
ProfileFileSystemSelector selector,
ProfileManager manager,
PluginConfiguration configuration,
TemplateCombo templateCombo,
GameStateService gameStateService)
{
_selector = selector;
_manager = manager;
_configuration = configuration;
_templateCombo = templateCombo;
_gameStateService = gameStateService;
}
public void Draw()
{
using var group = ImRaii.Group();
if (_selector.SelectedPaths.Count > 1)
{
DrawMultiSelection();
}
else
{
DrawHeader();
DrawPanel();
}
}
private HeaderDrawer.Button LockButton()
=> _selector.Selected == null
? HeaderDrawer.Button.Invisible
: _selector.Selected.IsWriteProtected
? new HeaderDrawer.Button
{
Description = "Make this profile editable.",
Icon = FontAwesomeIcon.Lock,
OnClick = () => _manager.SetWriteProtection(_selector.Selected!, false)
}
: new HeaderDrawer.Button
{
Description = "Write-protect this profile.",
Icon = FontAwesomeIcon.LockOpen,
OnClick = () => _manager.SetWriteProtection(_selector.Selected!, true)
};
private void DrawHeader()
=> HeaderDrawer.Draw(SelectionName, 0, ImGui.GetColorU32(ImGuiCol.FrameBg),
0, LockButton(),
HeaderDrawer.Button.IncognitoButton(_selector.IncognitoMode, v => _selector.IncognitoMode = v));
private void DrawMultiSelection()
{
if (_selector.SelectedPaths.Count == 0)
return;
var sizeType = ImGui.GetFrameHeight();
var availableSizePercent = (ImGui.GetContentRegionAvail().X - sizeType - 4 * ImGui.GetStyle().CellPadding.X) / 100;
var sizeMods = availableSizePercent * 35;
var sizeFolders = availableSizePercent * 65;
ImGui.NewLine();
ImGui.TextUnformatted("Currently Selected Profiles");
ImGui.Separator();
using var table = ImRaii.Table("profile", 3, ImGuiTableFlags.RowBg);
ImGui.TableSetupColumn("btn", ImGuiTableColumnFlags.WidthFixed, sizeType);
ImGui.TableSetupColumn("name", ImGuiTableColumnFlags.WidthFixed, sizeMods);
ImGui.TableSetupColumn("path", ImGuiTableColumnFlags.WidthFixed, sizeFolders);
var i = 0;
foreach (var (fullName, path) in _selector.SelectedPaths.Select(p => (p.FullName(), p))
.OrderBy(p => p.Item1, StringComparer.OrdinalIgnoreCase))
{
using var id = ImRaii.PushId(i++);
ImGui.TableNextColumn();
var icon = (path is ProfileFileSystem.Leaf ? FontAwesomeIcon.FileCircleMinus : FontAwesomeIcon.FolderMinus).ToIconString();
if (ImGuiUtil.DrawDisabledButton(icon, new Vector2(sizeType), "Remove from selection.", false, true))
_selector.RemovePathFromMultiSelection(path);
ImGui.TableNextColumn();
ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted(path is ProfileFileSystem.Leaf l ? _selector.IncognitoMode ? l.Value.Incognito : l.Value.Name.Text : string.Empty);
ImGui.TableNextColumn();
ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted(_selector.IncognitoMode ? "Incognito is active" : fullName);
}
}
private void DrawPanel()
{
using var child = ImRaii.Child("##Panel", -Vector2.One, true);
if (!child || _selector.Selected == null)
return;
DrawEnabledSetting();
using (var disabled = ImRaii.Disabled(_selector.Selected?.IsWriteProtected ?? true))
{
DrawBasicSettings();
DrawTemplateArea();
}
}
private void DrawEnabledSetting()
{
var spacing = ImGui.GetStyle().ItemInnerSpacing with { X = ImGui.GetStyle().ItemSpacing.X, Y = ImGui.GetStyle().ItemSpacing.Y };
using (var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing))
{
var enabled = _selector.Selected?.Enabled ?? false;
if (ImGui.Checkbox("##Enabled", ref enabled))
_manager.SetEnabled(_selector.Selected!, enabled);
ImGuiUtil.LabeledHelpMarker("Enabled",
"Whether the templates in this profile should be applied at all. Only one profile can be enabled for a character at the same time.");
ImGui.SameLine();
var isDefault = _manager.DefaultProfile == _selector.Selected;
if (ImGui.Checkbox("##DefaultProfile", ref isDefault))
_manager.SetDefaultProfile(isDefault ? _selector.Selected! : null);
ImGuiUtil.LabeledHelpMarker("Default profile (Players and Retainers only)",
"Whether the templates in this profile are applied to all players and retainers without a specific profile. Only one profile can be default at the same time.");
}
}
private void DrawBasicSettings()
{
using (var style = ImRaii.PushStyle(ImGuiStyleVar.ButtonTextAlign, new Vector2(0, 0.5f)))
{
using (var table = ImRaii.Table("BasicSettings", 2))
{
ImGui.TableSetupColumn("BasicCol1", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("lorem ipsum dolor").X);
ImGui.TableSetupColumn("BasicCol2", ImGuiTableColumnFlags.WidthStretch);
ImGuiUtil.DrawFrameColumn("Profile Name");
ImGui.TableNextColumn();
var width = new Vector2(ImGui.GetContentRegionAvail().X, 0);
var name = _newName ?? _selector.Selected!.Name;
ImGui.SetNextItemWidth(width.X);
if (!_selector.IncognitoMode)
{
if (ImGui.InputText("##ProfileName", ref name, 128))
{
_newName = name;
_changedProfile = _selector.Selected;
}
if (ImGui.IsItemDeactivatedAfterEdit() && _changedProfile != null)
{
_manager.Rename(_changedProfile, name);
_newName = null;
_changedProfile = null;
}
}
else
ImGui.TextUnformatted(_selector.Selected!.Incognito);
ImGui.TableNextRow();
ImGuiUtil.DrawFrameColumn("Character Name");
ImGui.TableNextColumn();
width = new Vector2(ImGui.GetContentRegionAvail().X - ImGui.CalcTextSize("Limit to my creatures").X - 68, 0);
name = _newCharacterName ?? _selector.Selected!.CharacterName;
ImGui.SetNextItemWidth(width.X);
using (var disabled = ImRaii.Disabled(_gameStateService.GameInPosingMode()))
{
if (!_selector.IncognitoMode)
{
if (ImGui.InputText("##CharacterName", ref name, 128))
{
_newCharacterName = name;
_changedProfile = _selector.Selected;
}
if (ImGui.IsItemDeactivatedAfterEdit() && _changedProfile != null)
{
_manager.ChangeCharacterName(_changedProfile, name);
_newCharacterName = null;
_changedProfile = null;
}
}
else
ImGui.TextUnformatted("Incognito active");
ImGui.SameLine();
var enabled = _selector.Selected?.LimitLookupToOwnedObjects ?? false;
if (ImGui.Checkbox("##LimitLookupToOwnedObjects", ref enabled))
_manager.SetLimitLookupToOwned(_selector.Selected!, enabled);
ImGuiUtil.LabeledHelpMarker("Limit to my creatures",
"When enabled limits the character search to only your own summons, mounts and minions.\nUseful when there is possibility there will be another character with that name owned by another player.\n* For battle chocobo use \"Chocobo\" as character name.\n** If you are changing root scale for mount and want to keep your scale make sure your own scale is set to anything other than default value.");
}
}
}
}
private void DrawTemplateArea()
{
using var disabled = ImRaii.Disabled(_gameStateService.GameInPosingMode());
using var table = ImRaii.Table("SetTable", 3, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollX | ImGuiTableFlags.ScrollY);
if (!table)
return;
ImGui.TableSetupColumn("##del", ImGuiTableColumnFlags.WidthFixed, ImGui.GetFrameHeight());
ImGui.TableSetupColumn("##Index", ImGuiTableColumnFlags.WidthFixed, 30 * ImGuiHelpers.GlobalScale);
ImGui.TableSetupColumn("Template", ImGuiTableColumnFlags.WidthFixed, 220 * ImGuiHelpers.GlobalScale);
ImGui.TableHeadersRow();
//warn: .ToList() might be performance critical at some point
//the copying via ToList is done because manipulations with .Templates list result in "Collection was modified" exception here
foreach (var (template, idx) in _selector.Selected!.Templates.WithIndex().ToList())
{
using var id = ImRaii.PushId(idx);
ImGui.TableNextColumn();
var keyValid = _configuration.UISettings.DeleteTemplateModifier.IsActive();
var tt = keyValid
? "Remove this template from the profile."
: $"Remove this template from the profile.\nHold {_configuration.UISettings.DeleteTemplateModifier} to remove.";
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), new Vector2(ImGui.GetFrameHeight()), tt, !keyValid, true))
_endAction = () => _manager.DeleteTemplate(_selector.Selected!, idx);
ImGui.TableNextColumn();
ImGui.Selectable($"#{idx + 1:D2}");
DrawDragDrop(_selector.Selected!, idx);
ImGui.TableNextColumn();
_templateCombo.Draw(_selector.Selected!, template, idx);
DrawDragDrop(_selector.Selected!, idx);
}
ImGui.TableNextColumn();
ImGui.TableNextColumn();
ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted("New");
ImGui.TableNextColumn();
_templateCombo.Draw(_selector.Selected!, null, -1);
ImGui.TableNextRow();
_endAction?.Invoke();
_endAction = null;
}
private void DrawDragDrop(Profile profile, int index)
{
const string dragDropLabel = "TemplateDragDrop";
using (var target = ImRaii.DragDropTarget())
{
if (target.Success && ImGuiUtil.IsDropping(dragDropLabel))
{
if (_dragIndex >= 0)
{
var idx = _dragIndex;
_endAction = () => _manager.MoveTemplate(profile, idx, index);
}
_dragIndex = -1;
}
}
using (var source = ImRaii.DragDropSource())
{
if (source)
{
ImGui.TextUnformatted($"Moving template #{index + 1:D2}...");
if (ImGui.SetDragDropPayload(dragDropLabel, nint.Zero, 0))
{
_dragIndex = index;
}
}
}
}
}

View File

@@ -0,0 +1,23 @@
using Dalamud.Interface.Utility;
using ImGuiNET;
namespace CustomizePlus.UI.Windows.MainWindow.Tabs.Profiles;
public class ProfilesTab
{
private readonly ProfileFileSystemSelector _selector;
private readonly ProfilePanel _panel;
public ProfilesTab(ProfileFileSystemSelector selector, ProfilePanel panel)
{
_selector = selector;
_panel = panel;
}
public void Draw()
{
_selector.Draw(200f * ImGuiHelpers.GlobalScale);
ImGui.SameLine();
_panel.Draw();
}
}