mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2026-02-23 16:07:44 +01:00
Finish NPC Tab update.
This commit is contained in:
parent
ea573ccb6b
commit
f2ac7e3353
14 changed files with 544 additions and 402 deletions
|
|
@ -41,7 +41,6 @@ public enum DesignPanelFlag : uint
|
|||
public static partial class DesignPanelFlagExtensions
|
||||
{
|
||||
private static readonly StringU8 Expand = new("Expand"u8);
|
||||
private static readonly StringU8 AdvancedCustomization = DesignPanelFlag.AdvancedCustomizations.ToNameU8();
|
||||
|
||||
public static Im.HeaderDisposable Header(this DesignPanelFlag flag, Configuration config)
|
||||
{
|
||||
|
|
@ -56,12 +55,13 @@ public static partial class DesignPanelFlagExtensions
|
|||
Action<DesignPanelFlag> setterExpand)
|
||||
{
|
||||
var checkBoxWidth = Math.Max(Im.Style.FrameHeight, Expand.CalculateSize().X);
|
||||
var textWidth = AdvancedCustomization.CalculateSize().X;
|
||||
var test = DesignPanelFlag.AdvancedCustomizations.ToNameU8();
|
||||
var textWidth = AdvancedCustomizations_Name__GenU8.CalculateSize().X;
|
||||
var tableSize = 2 * (textWidth + 2 * checkBoxWidth)
|
||||
+ 10 * Im.Style.CellPadding.X
|
||||
+ 2 * Im.Style.WindowPadding.X
|
||||
+ 2 * Im.Style.FrameBorderThickness;
|
||||
using var table = Im.Table.Begin(label, 6, TableFlags.RowBackground | TableFlags.SizingFixedFit | TableFlags.Borders,
|
||||
using var table = Im.Table.Begin(label, 6, TableFlags.RowBackground | TableFlags.Borders,
|
||||
new Vector2(tableSize, 6 * Im.Style.FrameHeight));
|
||||
if (!table)
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -1,27 +1,29 @@
|
|||
using Glamourer.Designs;
|
||||
using ImSharp;
|
||||
using OtterGui;
|
||||
using OtterGui.Widgets;
|
||||
using MouseWheelType = OtterGui.Widgets.MouseWheelType;
|
||||
|
||||
namespace Glamourer.Gui.Tabs.DesignTab;
|
||||
|
||||
public sealed class DesignColorCombo(DesignColors designColors, bool skipAutomatic) :
|
||||
FilterComboCache<string>(skipAutomatic
|
||||
? designColors.Keys.OrderBy(k => k)
|
||||
: designColors.Keys.OrderBy(k => k).Prepend(DesignColors.AutomaticName),
|
||||
MouseWheelType.Control, Glamourer.Log)
|
||||
public sealed class DesignColorCombo(DesignColors designColors, bool skipAutomatic) : SimpleFilterCombo<string>(SimpleFilterType.Text)
|
||||
{
|
||||
protected override bool DrawSelectable(int globalIdx, bool selected)
|
||||
public override StringU8 DisplayString(in string value)
|
||||
=> new(value);
|
||||
|
||||
public override string FilterString(in string value)
|
||||
=> value;
|
||||
|
||||
public override IEnumerable<string> GetBaseItems()
|
||||
=> skipAutomatic ? designColors.Keys.OrderBy(k => k) : designColors.Keys.OrderBy(k => k).Prepend(DesignColors.AutomaticName);
|
||||
|
||||
public override ColorParameter TextColor(in string value)
|
||||
=> value is DesignColors.AutomaticName ? ColorParameter.Default : designColors[value];
|
||||
|
||||
protected override bool DrawItem(in SimpleCacheItem<string> item, int globalIndex, bool selected)
|
||||
{
|
||||
var isAutomatic = !skipAutomatic && globalIdx is 0;
|
||||
var key = Items[globalIdx];
|
||||
var color = isAutomatic ? 0 : designColors[key];
|
||||
using var c = ImGuiColor.Text.Push(color, !color.IsTransparent);
|
||||
var ret = base.DrawSelectable(globalIdx, selected);
|
||||
var isAutomatic = !skipAutomatic && globalIndex is 0;
|
||||
var ret = base.DrawItem(item, globalIndex, selected);
|
||||
if (isAutomatic)
|
||||
ImGuiUtil.HoverTooltip(
|
||||
"The automatic color uses the colors dependent on the design state, as defined in the regular color definitions.");
|
||||
Im.Tooltip.OnHover(
|
||||
"The automatic color uses the colors dependent on the design state, as defined in the regular color definitions."u8);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -167,17 +167,13 @@ public class DesignDetailTab
|
|||
"Set this design to reset any temporary settings previously applied to the associated collection when it is applied through any means."u8);
|
||||
|
||||
ImUtf8.DrawFrameColumn("Color"u8);
|
||||
var colorName = _selector.Selected!.Color.Length == 0 ? DesignColors.AutomaticName : _selector.Selected!.Color;
|
||||
ImGui.TableNextColumn();
|
||||
if (_colorCombo.Draw("##colorCombo", colorName, "Associate a color with this design.\n"
|
||||
+ "Right-Click to revert to automatic coloring.\n"
|
||||
+ "Hold Control and scroll the mousewheel to scroll.",
|
||||
width.X - Im.Style.ItemSpacing.X - Im.Style.FrameHeight, Im.Style.TextHeight)
|
||||
&& _colorCombo.CurrentSelection != null)
|
||||
{
|
||||
colorName = _colorCombo.CurrentSelection is DesignColors.AutomaticName ? string.Empty : _colorCombo.CurrentSelection;
|
||||
_manager.ChangeColor(_selector.Selected!, colorName);
|
||||
}
|
||||
if (_colorCombo.Draw("##colorCombo"u8, _selector.Selected!.Color.Length is 0 ? DesignColors.AutomaticName : _selector.Selected!.Color,
|
||||
"Associate a color with this design.\n"u8
|
||||
+ "Right-Click to revert to automatic coloring.\n"u8
|
||||
+ "Hold Control and scroll the mousewheel to scroll."u8,
|
||||
width.X - Im.Style.ItemSpacing.X - Im.Style.FrameHeight, out var newColorName))
|
||||
_manager.ChangeColor(_selector.Selected!, newColorName == DesignColors.AutomaticName ? string.Empty : newColorName);
|
||||
|
||||
if (Im.Item.RightClicked())
|
||||
_manager.ChangeColor(_selector.Selected!, string.Empty);
|
||||
|
|
|
|||
|
|
@ -315,30 +315,33 @@ public class MultiDesignPanel(
|
|||
Im.Separator();
|
||||
}
|
||||
|
||||
private string _colorComboSelection = string.Empty;
|
||||
|
||||
private void DrawMultiColor(Vector2 width, float offset)
|
||||
{
|
||||
ImUtf8.TextFrameAligned("Multi Colors:"u8);
|
||||
ImGui.SameLine(offset, Im.Style.ItemSpacing.X);
|
||||
_colorCombo.Draw("##color", _colorCombo.CurrentSelection ?? string.Empty, "Select a design color.",
|
||||
Im.ContentRegion.Available.X - 2 * (width.X + Im.Style.ItemSpacing.X), Im.Style.TextHeight);
|
||||
if (_colorCombo.Draw("##color"u8, _colorComboSelection, "Select a design color."u8,
|
||||
Im.ContentRegion.Available.X - 2 * (width.X + Im.Style.ItemSpacing.X), out var newSelection))
|
||||
_colorComboSelection = newSelection;
|
||||
|
||||
UpdateColorCache();
|
||||
var label = _addDesigns.Count > 0
|
||||
? $"Set for {_addDesigns.Count} Designs"
|
||||
: "Set";
|
||||
var tooltip = _addDesigns.Count == 0
|
||||
? _colorCombo.CurrentSelection switch
|
||||
var tooltip = _addDesigns.Count is 0
|
||||
? _colorComboSelection switch
|
||||
{
|
||||
null => "No color specified.",
|
||||
DesignColors.AutomaticName => "Use the other button to set to automatic.",
|
||||
_ => $"All designs selected are already set to the color \"{_colorCombo.CurrentSelection}\".",
|
||||
_ => $"All designs selected are already set to the color \"{_colorComboSelection}\".",
|
||||
}
|
||||
: $"Set the color of {_addDesigns.Count} designs to \"{_colorCombo.CurrentSelection}\"\n\n\t{string.Join("\n\t", _addDesigns.Select(m => m.Name.Text))}";
|
||||
: $"Set the color of {_addDesigns.Count} designs to \"{_colorComboSelection}\"\n\n\t{string.Join("\n\t", _addDesigns.Select(m => m.Name.Text))}";
|
||||
Im.Line.Same();
|
||||
if (ImUtf8.ButtonEx(label, tooltip, width, _addDesigns.Count == 0))
|
||||
if (ImEx.Button(label, width, tooltip, _addDesigns.Count is 0))
|
||||
{
|
||||
foreach (var design in _addDesigns)
|
||||
editor.ChangeColor(design, _colorCombo.CurrentSelection!);
|
||||
editor.ChangeColor(design, _colorComboSelection!);
|
||||
}
|
||||
|
||||
label = _removeDesigns.Count > 0
|
||||
|
|
@ -348,7 +351,7 @@ public class MultiDesignPanel(
|
|||
? "No selected design is set to a non-automatic color."
|
||||
: $"Set {_removeDesigns.Count} designs to use automatic color again:\n\n\t{string.Join("\n\t", _removeDesigns.Select(m => m.Item1.Name.Text))}";
|
||||
Im.Line.Same();
|
||||
if (ImUtf8.ButtonEx(label, tooltip, width, _removeDesigns.Count == 0))
|
||||
if (ImEx.Button(label, width, tooltip, _removeDesigns.Count is 0))
|
||||
{
|
||||
foreach (var (design, _) in _removeDesigns)
|
||||
editor.ChangeColor(design, string.Empty);
|
||||
|
|
@ -503,7 +506,7 @@ public class MultiDesignPanel(
|
|||
{
|
||||
_addDesigns.Clear();
|
||||
_removeDesigns.Clear();
|
||||
var selection = _colorCombo.CurrentSelection ?? DesignColors.AutomaticName;
|
||||
var selection = string.IsNullOrEmpty(_colorComboSelection) ? DesignColors.AutomaticName : _colorComboSelection;
|
||||
foreach (var leaf in selector.SelectedPaths.OfType<DesignFileSystem.Leaf>())
|
||||
{
|
||||
if (leaf.Value.Color.Length > 0)
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ public class LocalNpcAppearanceData : ISavable
|
|||
|
||||
private Rgba32 GetColor(string color, bool favorite, ObjectKind kind)
|
||||
{
|
||||
if (color.Length == 0)
|
||||
if (color.Length is 0)
|
||||
{
|
||||
if (favorite)
|
||||
return ColorId.FavoriteStarOn.Value();
|
||||
|
|
|
|||
14
Glamourer/Gui/Tabs/NpcTab/NpcCacheItem.cs
Normal file
14
Glamourer/Gui/Tabs/NpcTab/NpcCacheItem.cs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
using Glamourer.GameData;
|
||||
using ImSharp;
|
||||
|
||||
namespace Glamourer.Gui.Tabs.NpcTab;
|
||||
|
||||
public readonly struct NpcCacheItem(in NpcData npc, string colorText, Rgba32 color, bool favorite)
|
||||
{
|
||||
public readonly StringPair Name = new(npc.Name);
|
||||
public readonly StringU8 Id = new($"({npc.Id})");
|
||||
public readonly NpcData Npc = npc;
|
||||
public readonly string ColorText = colorText;
|
||||
public readonly Vector4 Color = color.ToVector();
|
||||
public readonly bool Favorite = favorite;
|
||||
}
|
||||
|
|
@ -1,44 +1,92 @@
|
|||
using Glamourer.Designs;
|
||||
using Glamourer.GameData;
|
||||
using OtterGui.Classes;
|
||||
using ImSharp;
|
||||
using Luna;
|
||||
|
||||
namespace Glamourer.Gui.Tabs.NpcTab;
|
||||
|
||||
public sealed class NpcFilter(LocalNpcAppearanceData _favorites) : FilterUtility<NpcData>
|
||||
public sealed class NpcFilter : TokenizedFilter<NpcFilter.TokenType, NpcCacheItem, NpcFilter.NpcFilterToken>, IUiService
|
||||
{
|
||||
protected override string Tooltip
|
||||
=> "Filter NPC appearances for those where their names contain the given substring.\n"
|
||||
+ "Enter i:[number] to filter for NPCs of certain IDs.\n"
|
||||
+ "Enter c:[string] to filter for NPC appearances set to specific colors.";
|
||||
|
||||
protected override (LowerString, long, int) FilterChange(string input)
|
||||
=> input.Length switch
|
||||
{
|
||||
0 => (LowerString.Empty, 0, -1),
|
||||
> 1 when input[1] == ':' =>
|
||||
input[0] switch
|
||||
{
|
||||
'i' or 'I' => input.Length == 2 ? (LowerString.Empty, 0, -1) :
|
||||
long.TryParse(input.AsSpan(2), out var r) ? (LowerString.Empty, r, 1) : (LowerString.Empty, 0, -1),
|
||||
'c' or 'C' => input.Length == 2 ? (LowerString.Empty, 0, -1) : (new LowerString(input[2..]), 0, 2),
|
||||
_ => (new LowerString(input), 0, 0),
|
||||
},
|
||||
_ => (new LowerString(input), 0, 0),
|
||||
};
|
||||
|
||||
public override bool ApplyFilter(in NpcData value)
|
||||
=> FilterMode switch
|
||||
{
|
||||
-1 => false,
|
||||
0 => Filter.IsContained(value.Name),
|
||||
1 => value.Id.Id == NumericalFilter,
|
||||
2 => Filter.IsContained(GetColor(value)),
|
||||
_ => false, // Should never happen
|
||||
};
|
||||
|
||||
private string GetColor(in NpcData value)
|
||||
public enum TokenType : byte
|
||||
{
|
||||
var color = _favorites.GetColor(value);
|
||||
return color.Length == 0 ? DesignColors.AutomaticName : color;
|
||||
Name,
|
||||
Id,
|
||||
Color,
|
||||
}
|
||||
|
||||
protected override void DrawTooltip()
|
||||
{
|
||||
if (!Im.Item.Hovered())
|
||||
return;
|
||||
|
||||
using var style = Im.Style.PushDefault();
|
||||
using var tt = Im.Tooltip.Begin();
|
||||
Im.Text("Filter NPC appearances for those where their names contain the given substring."u8);
|
||||
ImEx.TextMultiColored("Enter "u8).Then("i:[number]"u8, ColorId.TriStateCheck.Value()).Then(" to filter for NPCs of certain IDs."u8)
|
||||
.End();
|
||||
ImEx.TextMultiColored("Enter "u8).Then("c:[string]"u8, ColorId.TriStateCheck.Value())
|
||||
.Then(" to filter for NPC appearances set to specific colors."u8).End();
|
||||
}
|
||||
|
||||
public readonly struct NpcFilterToken() : IFilterToken<TokenType, NpcFilterToken>
|
||||
{
|
||||
public string Needle { get; init; } = string.Empty;
|
||||
public uint ParsedNeedle { get; private init; } = 0;
|
||||
public TokenType Type { get; init; }
|
||||
|
||||
public bool Contains(NpcFilterToken other)
|
||||
{
|
||||
if (Type != other.Type)
|
||||
return false;
|
||||
if (Type is TokenType.Id)
|
||||
return ParsedNeedle == other.ParsedNeedle;
|
||||
|
||||
return Needle.Contains(other.Needle);
|
||||
}
|
||||
|
||||
public static bool ConvertToken(char tokenCharacter, out TokenType type)
|
||||
{
|
||||
type = tokenCharacter switch
|
||||
{
|
||||
'i' or 'I' => TokenType.Id,
|
||||
'c' or 'C' => TokenType.Color,
|
||||
_ => TokenType.Name,
|
||||
};
|
||||
return type is not TokenType.Name;
|
||||
}
|
||||
|
||||
public static bool AllowsNone(TokenType type)
|
||||
=> false;
|
||||
|
||||
public static void ProcessList(List<NpcFilterToken> list)
|
||||
{
|
||||
for (var i = 0; i < list.Count; ++i)
|
||||
{
|
||||
var entry = list[i];
|
||||
if (entry.Type is not TokenType.Id)
|
||||
continue;
|
||||
|
||||
if (!uint.TryParse(entry.Needle, out var value))
|
||||
list.RemoveAt(i--);
|
||||
else
|
||||
list[i] = new NpcFilterToken
|
||||
{
|
||||
ParsedNeedle = value,
|
||||
Type = TokenType.Id,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool Matches(in NpcFilterToken token, in NpcCacheItem npcCacheItem)
|
||||
{
|
||||
return token.Type switch
|
||||
{
|
||||
TokenType.Name => npcCacheItem.Name.Utf16.Contains(token.Needle, StringComparison.InvariantCultureIgnoreCase),
|
||||
TokenType.Id => npcCacheItem.Npc.Id == token.ParsedNeedle,
|
||||
TokenType.Color => npcCacheItem.ColorText.Contains(token.Needle, StringComparison.InvariantCultureIgnoreCase),
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
protected override bool MatchesNone(TokenType type, bool negated, in NpcCacheItem npcCacheItem)
|
||||
=> false;
|
||||
}
|
||||
|
|
|
|||
126
Glamourer/Gui/Tabs/NpcTab/NpcHeader.cs
Normal file
126
Glamourer/Gui/Tabs/NpcTab/NpcHeader.cs
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
using Dalamud.Interface.ImGuiNotification;
|
||||
using Glamourer.Designs;
|
||||
using ImSharp;
|
||||
using Luna;
|
||||
|
||||
namespace Glamourer.Gui.Tabs.NpcTab;
|
||||
|
||||
public sealed class NpcHeader : SplitButtonHeader
|
||||
{
|
||||
private readonly NpcSelection _selection;
|
||||
|
||||
public NpcHeader(NpcSelection selection, LocalNpcAppearanceData favorites, DesignManager designs)
|
||||
{
|
||||
_selection = selection;
|
||||
LeftButtons.AddButton(new ExportToClipboardButton(selection), 100);
|
||||
LeftButtons.AddButton(new SaveAsDesignButton(selection, designs), 50);
|
||||
|
||||
RightButtons.AddButton(new FavoriteButton(selection, favorites), 0);
|
||||
}
|
||||
|
||||
public override ReadOnlySpan<byte> Text
|
||||
=> _selection.HasSelection ? _selection.Name : "No Selection"u8;
|
||||
|
||||
public override ColorParameter TextColor
|
||||
=> ColorId.NormalDesign.Value();
|
||||
|
||||
public override void Draw(Vector2 size)
|
||||
{
|
||||
var color = ColorId.HeaderButtons.Value();
|
||||
using var _ = ImGuiColor.Text.Push(color).Push(ImGuiColor.Border, color);
|
||||
base.Draw(size with { Y = Im.Style.FrameHeight });
|
||||
}
|
||||
|
||||
private sealed class FavoriteButton(NpcSelection selection, LocalNpcAppearanceData favorites) : BaseIconButton<AwesomeIcon>
|
||||
{
|
||||
public override bool HasTooltip
|
||||
=> true;
|
||||
|
||||
public override void DrawTooltip()
|
||||
=> Im.Text(
|
||||
selection.Favorite ? "Remove this NPC appearance from your favorites."u8 : "Add this NPC Appearance to your favorites."u8);
|
||||
|
||||
public override AwesomeIcon Icon
|
||||
=> LunaStyle.FavoriteIcon;
|
||||
|
||||
public override bool IsVisible
|
||||
=> selection.HasSelection;
|
||||
|
||||
public override void OnClick()
|
||||
=> favorites.ToggleFavorite(selection.Data);
|
||||
|
||||
protected override void PreDraw()
|
||||
=> ImGuiColor.Text.Push(selection.Favorite ? ColorId.FavoriteStarOn.Value() : 0x80000000);
|
||||
|
||||
protected override void PostDraw()
|
||||
=> Im.ColorDisposable.PopUnsafe();
|
||||
}
|
||||
|
||||
private sealed class ExportToClipboardButton(NpcSelection selection) : BaseIconButton<AwesomeIcon>
|
||||
{
|
||||
public override bool HasTooltip
|
||||
=> true;
|
||||
|
||||
public override void DrawTooltip()
|
||||
=> Im.Text(
|
||||
"Copy the current NPCs appearance to your clipboard.\nHold Control to disable applying of customizations for the copied design.\nHold Shift to disable applying of gear for the copied design."u8);
|
||||
|
||||
public override AwesomeIcon Icon
|
||||
=> LunaStyle.ToClipboardIcon;
|
||||
|
||||
public override bool IsVisible
|
||||
=> selection.HasSelection;
|
||||
|
||||
public override void OnClick()
|
||||
{
|
||||
try
|
||||
{
|
||||
var text = selection.ToBase64();
|
||||
Im.Clipboard.Set(text);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Glamourer.Messager.NotificationMessage(ex, $"Could not copy {selection.Data.Name}'s data to clipboard.",
|
||||
$"Could not copy data from NPC appearance {selection.Data.Kind} {selection.Data.Id.Id} to clipboard",
|
||||
NotificationType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class SaveAsDesignButton(NpcSelection selection, DesignManager designs) : BaseIconButton<AwesomeIcon>
|
||||
{
|
||||
private StringU8 _newName = StringU8.Empty;
|
||||
private DesignBase? _newDesign;
|
||||
|
||||
public override bool HasTooltip
|
||||
=> true;
|
||||
|
||||
public override void DrawTooltip()
|
||||
=> Im.Text(
|
||||
"Save this NPCs appearance as a design.\nHold Control to disable applying of customizations for the saved design.\nHold Shift to disable applying of gear for the saved design."u8);
|
||||
|
||||
public override AwesomeIcon Icon
|
||||
=> LunaStyle.SaveIcon;
|
||||
|
||||
public override bool IsVisible
|
||||
=> selection.HasSelection;
|
||||
|
||||
public override void OnClick()
|
||||
{
|
||||
Im.Popup.Open("Save as Design"u8);
|
||||
_newName = new StringU8(selection.Data.Name);
|
||||
_newDesign = selection.ToDesignBase();
|
||||
}
|
||||
|
||||
protected override void PostDraw()
|
||||
{
|
||||
if (!InputPopup.OpenName("Save as Design"u8, _newName, out var name))
|
||||
return;
|
||||
|
||||
if (_newDesign is not null && name.Length > 0)
|
||||
designs.CreateClone(_newDesign, name, true);
|
||||
_newDesign = null;
|
||||
_newName = StringU8.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,4 @@
|
|||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.ImGuiNotification;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Gui.Customization;
|
||||
using Glamourer.Gui.Equipment;
|
||||
|
|
@ -9,126 +6,38 @@ using Glamourer.Gui.Tabs.DesignTab;
|
|||
using Glamourer.State;
|
||||
using ImSharp;
|
||||
using Luna;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using OtterGui.Text;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Interop;
|
||||
using static Glamourer.Gui.Tabs.HeaderDrawer;
|
||||
|
||||
namespace Glamourer.Gui.Tabs.NpcTab;
|
||||
|
||||
public class NpcPanel
|
||||
public sealed class NpcPanel(
|
||||
Configuration config,
|
||||
NpcSelection selection,
|
||||
CustomizationDrawer customizeDrawer,
|
||||
EquipmentDrawer equipmentDrawer,
|
||||
ActorObjectManager objects,
|
||||
StateManager stateManager,
|
||||
LocalNpcAppearanceData favorites,
|
||||
DesignColors designColors) : IPanel
|
||||
{
|
||||
private readonly Configuration _config;
|
||||
private readonly DesignColorCombo _colorCombo;
|
||||
private string _newName = string.Empty;
|
||||
private DesignBase? _newDesign;
|
||||
private readonly NpcSelector _selector;
|
||||
private readonly LocalNpcAppearanceData _favorites;
|
||||
private readonly CustomizationDrawer _customizeDrawer;
|
||||
private readonly EquipmentDrawer _equipDrawer;
|
||||
private readonly DesignConverter _converter;
|
||||
private readonly DesignManager _designManager;
|
||||
private readonly StateManager _state;
|
||||
private readonly ActorObjectManager _objects;
|
||||
private readonly DesignColors _colors;
|
||||
private readonly Button[] _leftButtons;
|
||||
private readonly Button[] _rightButtons;
|
||||
private readonly DesignColorCombo _combo = new(designColors, true);
|
||||
|
||||
public NpcPanel(NpcSelector selector,
|
||||
LocalNpcAppearanceData favorites,
|
||||
CustomizationDrawer customizeDrawer,
|
||||
EquipmentDrawer equipDrawer,
|
||||
DesignConverter converter,
|
||||
DesignManager designManager,
|
||||
StateManager state,
|
||||
ActorObjectManager objects,
|
||||
DesignColors colors,
|
||||
Configuration config)
|
||||
{
|
||||
_selector = selector;
|
||||
_favorites = favorites;
|
||||
_customizeDrawer = customizeDrawer;
|
||||
_equipDrawer = equipDrawer;
|
||||
_converter = converter;
|
||||
_designManager = designManager;
|
||||
_state = state;
|
||||
_objects = objects;
|
||||
_colors = colors;
|
||||
_config = config;
|
||||
_colorCombo = new DesignColorCombo(colors, true);
|
||||
_leftButtons =
|
||||
[
|
||||
new ExportToClipboardButton(this),
|
||||
new SaveAsDesignButton(this),
|
||||
];
|
||||
_rightButtons =
|
||||
[
|
||||
new FavoriteButton(this),
|
||||
];
|
||||
}
|
||||
public ReadOnlySpan<byte> Id
|
||||
=> "NpcPanel"u8;
|
||||
|
||||
public void Draw()
|
||||
{
|
||||
using var group = ImRaii.Group();
|
||||
|
||||
DrawHeader();
|
||||
DrawPanel();
|
||||
}
|
||||
|
||||
private void DrawHeader()
|
||||
{
|
||||
HeaderDrawer.Draw(_selector.HasSelection ? _selector.Selection.Name : "No Selection", ColorId.NormalDesign.Value().Color,
|
||||
ImGuiColor.FrameBackground.Get().Color, _leftButtons, _rightButtons);
|
||||
SaveDesignDrawPopup();
|
||||
}
|
||||
|
||||
private sealed class FavoriteButton(NpcPanel panel) : Button
|
||||
{
|
||||
protected override string Description
|
||||
=> panel._favorites.IsFavorite(panel._selector.Selection)
|
||||
? "Remove this NPC appearance from your favorites."
|
||||
: "Add this NPC Appearance to your favorites.";
|
||||
|
||||
protected override Rgba32 TextColor
|
||||
=> panel._favorites.IsFavorite(panel._selector.Selection)
|
||||
? ColorId.FavoriteStarOn.Value()
|
||||
: 0x80000000;
|
||||
|
||||
protected override FontAwesomeIcon Icon
|
||||
=> FontAwesomeIcon.Star;
|
||||
|
||||
public override bool Visible
|
||||
=> panel._selector.HasSelection;
|
||||
|
||||
protected override void OnClick()
|
||||
=> panel._favorites.ToggleFavorite(panel._selector.Selection);
|
||||
}
|
||||
|
||||
private void SaveDesignDrawPopup()
|
||||
{
|
||||
if (!ImGuiUtil.OpenNameField("Save as Design", ref _newName))
|
||||
using var table = Im.Table.Begin("##Panel"u8, 1, TableFlags.None, Im.ContentRegion.Available);
|
||||
if (!table || !selection.HasSelection)
|
||||
return;
|
||||
|
||||
if (_newDesign != null && _newName.Length > 0)
|
||||
_designManager.CreateClone(_newDesign, _newName, true);
|
||||
_newDesign = null;
|
||||
_newName = string.Empty;
|
||||
}
|
||||
|
||||
private void DrawPanel()
|
||||
{
|
||||
using var table = Im.Table.Begin("##Panel"u8, 1, TableFlags.BordersOuter | TableFlags.ScrollY, Im.ContentRegion.Available);
|
||||
if (!table || !_selector.HasSelection)
|
||||
return;
|
||||
|
||||
ImGui.TableSetupScrollFreeze(0, 1);
|
||||
ImGui.TableNextColumn();
|
||||
table.SetupScrollFreeze(0, 1);
|
||||
table.NextColumn();
|
||||
Im.Dummy(Vector2.Zero);
|
||||
DrawButtonRow();
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
table.NextColumn();
|
||||
DrawCustomization();
|
||||
DrawEquipment();
|
||||
DrawAppearanceInfo();
|
||||
|
|
@ -143,96 +52,82 @@ public class NpcPanel
|
|||
|
||||
private void DrawCustomization()
|
||||
{
|
||||
if (_config.HideDesignPanel.HasFlag(DesignPanelFlag.Customization))
|
||||
if (config.HideDesignPanel.HasFlag(DesignPanelFlag.Customization))
|
||||
return;
|
||||
|
||||
var expand = _config.AutoExpandDesignPanel.HasFlag(DesignPanelFlag.Customization);
|
||||
using var h = Im.Tree.HeaderId(_selector.Selection.ModelId is 0
|
||||
var expand = config.AutoExpandDesignPanel.HasFlag(DesignPanelFlag.Customization);
|
||||
using var h = Im.Tree.HeaderId(selection.Data.ModelId is 0
|
||||
? "Customization"u8
|
||||
: $"Customization (Model Id #{_selector.Selection.ModelId})###Customization",
|
||||
: $"Customization (Model Id #{selection.Data.ModelId})###Customization",
|
||||
expand ? TreeNodeFlags.DefaultOpen : TreeNodeFlags.None);
|
||||
if (!h)
|
||||
return;
|
||||
|
||||
_customizeDrawer.Draw(_selector.Selection.Customize, true, true);
|
||||
customizeDrawer.Draw(selection.Data.Customize, true, true);
|
||||
Im.Dummy(new Vector2(Im.Style.TextHeight / 2));
|
||||
}
|
||||
|
||||
private void DrawEquipment()
|
||||
{
|
||||
using var h = DesignPanelFlag.Equipment.Header(_config);
|
||||
using var h = DesignPanelFlag.Equipment.Header(config);
|
||||
if (!h)
|
||||
return;
|
||||
|
||||
_equipDrawer.Prepare();
|
||||
var designData = ToDesignData();
|
||||
equipmentDrawer.Prepare();
|
||||
var designData = selection.ToDesignData();
|
||||
|
||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||
{
|
||||
var data = new EquipDrawData(slot, designData) { Locked = true };
|
||||
_equipDrawer.DrawEquip(data);
|
||||
equipmentDrawer.DrawEquip(data);
|
||||
}
|
||||
|
||||
var mainhandData = new EquipDrawData(EquipSlot.MainHand, designData) { Locked = true };
|
||||
var offhandData = new EquipDrawData(EquipSlot.OffHand, designData) { Locked = true };
|
||||
_equipDrawer.DrawWeapons(mainhandData, offhandData, false);
|
||||
equipmentDrawer.DrawWeapons(mainhandData, offhandData, false);
|
||||
|
||||
Im.Dummy(new Vector2(Im.Style.TextHeight / 2));
|
||||
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromValue(MetaIndex.VisorState, _selector.Selection.VisorToggled));
|
||||
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromValue(MetaIndex.VisorState, selection.Data.VisorToggled));
|
||||
Im.Dummy(new Vector2(Im.Style.TextHeight / 2));
|
||||
}
|
||||
|
||||
private DesignData ToDesignData()
|
||||
{
|
||||
var selection = _selector.Selection;
|
||||
var items = _converter.FromDrawData(selection.Equip(), selection.Mainhand, selection.Offhand, true).ToArray();
|
||||
var designData = new DesignData { Customize = selection.Customize };
|
||||
foreach (var (slot, item, stain) in items)
|
||||
{
|
||||
designData.SetItem(slot, item);
|
||||
designData.SetStain(slot, stain);
|
||||
}
|
||||
|
||||
return designData;
|
||||
}
|
||||
|
||||
private void DrawApplyToSelf()
|
||||
{
|
||||
var (id, data) = _objects.PlayerData;
|
||||
if (!ImUtf8.ButtonEx("Apply to Yourself"u8,
|
||||
var (id, data) = objects.PlayerData;
|
||||
if (!ImEx.Button("Apply to Yourself"u8, Vector2.Zero,
|
||||
"Apply the current NPC appearance to your character.\nHold Control to only apply gear.\nHold Shift to only apply customizations."u8,
|
||||
Vector2.Zero, !data.Valid))
|
||||
!data.Valid))
|
||||
return;
|
||||
|
||||
if (_state.GetOrCreate(id, data.Objects[0], out var state))
|
||||
if (stateManager.GetOrCreate(id, data.Objects[0], out var state))
|
||||
{
|
||||
var design = _converter.Convert(ToDesignData(), new StateMaterialManager(), ApplicationRules.NpcFromModifiers());
|
||||
_state.ApplyDesign(state, design, ApplySettings.Manual with { IsFinal = true });
|
||||
var design = selection.ToDesignBase();
|
||||
stateManager.ApplyDesign(state, design, ApplySettings.Manual with { IsFinal = true });
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawApplyToTarget()
|
||||
{
|
||||
var (id, data) = _objects.TargetData;
|
||||
var (id, data) = objects.TargetData;
|
||||
var tt = id.IsValid
|
||||
? data.Valid
|
||||
? "Apply the current NPC appearance to your current target.\nHold Control to only apply gear.\nHold Shift to only apply customizations."u8
|
||||
: "The current target can not be manipulated."u8
|
||||
: "No valid target selected."u8;
|
||||
if (!ImUtf8.ButtonEx("Apply to Target"u8, tt, Vector2.Zero, !data.Valid))
|
||||
if (!ImEx.Button("Apply to Target"u8, Vector2.Zero, tt, !data.Valid))
|
||||
return;
|
||||
|
||||
if (_state.GetOrCreate(id, data.Objects[0], out var state))
|
||||
if (stateManager.GetOrCreate(id, data.Objects[0], out var state))
|
||||
{
|
||||
var design = _converter.Convert(ToDesignData(), new StateMaterialManager(), ApplicationRules.NpcFromModifiers());
|
||||
_state.ApplyDesign(state, design, ApplySettings.Manual with { IsFinal = true });
|
||||
var design = selection.ToDesignBase();
|
||||
stateManager.ApplyDesign(state, design, ApplySettings.Manual with { IsFinal = true });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void DrawAppearanceInfo()
|
||||
{
|
||||
using var h = DesignPanelFlag.AppearanceDetails.Header(_config);
|
||||
using var h = DesignPanelFlag.AppearanceDetails.Header(config);
|
||||
if (!h)
|
||||
return;
|
||||
|
||||
|
|
@ -240,112 +135,64 @@ public class NpcPanel
|
|||
if (!table)
|
||||
return;
|
||||
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ButtonTextAlign, new Vector2(0, 0.5f));
|
||||
table.SetupColumn("Type"u8, TableColumnFlags.WidthFixed, Im.Font.CalculateSize("Last Update Datem"u8).X);
|
||||
using var style = ImStyleDouble.ButtonTextAlign.Push(new Vector2(0, 0.5f));
|
||||
table.SetupColumn("Type"u8, TableColumnFlags.WidthFixed, Im.Font.CalculateButtonSize("Last Update Date"u8).X);
|
||||
table.SetupColumn("Data"u8, TableColumnFlags.WidthStretch);
|
||||
|
||||
var selection = _selector.Selection;
|
||||
CopyButton("NPC Name"u8, selection.Name);
|
||||
CopyButton("NPC ID"u8, selection.Id.Id.ToString());
|
||||
ImGuiUtil.DrawFrameColumn("NPC Type");
|
||||
ImGui.TableNextColumn();
|
||||
CopyButton(table, "NPC Name"u8, selection.Name);
|
||||
CopyButton(table, "NPC ID"u8, $"{selection.Data.Id.Id}");
|
||||
table.DrawFrameColumn("NPC Type"u8);
|
||||
table.NextColumn();
|
||||
var width = Im.ContentRegion.Available.X;
|
||||
ImEx.TextFramed(selection.Kind is ObjectKind.BattleNpc ? "Battle NPC"u8 : "Event NPC"u8, new Vector2(width, 0),
|
||||
ImEx.TextFramed(selection.Data.Kind is ObjectKind.BattleNpc ? "Battle NPC"u8 : "Event NPC"u8, new Vector2(width, 0),
|
||||
ImGuiColor.FrameBackground.Get());
|
||||
|
||||
ImUtf8.DrawFrameColumn("Color"u8);
|
||||
var color = _favorites.GetColor(selection);
|
||||
var colorName = color.Length == 0 ? DesignColors.AutomaticName : color;
|
||||
ImGui.TableNextColumn();
|
||||
if (_colorCombo.Draw("##colorCombo", colorName,
|
||||
"Associate a color with this NPC appearance.\n"
|
||||
+ "Right-Click to revert to automatic coloring.\n"
|
||||
+ "Hold Control and scroll the mousewheel to scroll.",
|
||||
width - Im.Style.ItemSpacing.X - Im.Style.FrameHeight, Im.Style.TextHeight)
|
||||
&& _colorCombo.CurrentSelection != null)
|
||||
{
|
||||
color = _colorCombo.CurrentSelection is DesignColors.AutomaticName ? string.Empty : _colorCombo.CurrentSelection;
|
||||
_favorites.SetColor(selection, color);
|
||||
}
|
||||
table.DrawFrameColumn("Color"u8);
|
||||
table.NextColumn();
|
||||
var color = selection.ColorText;
|
||||
if (_combo.Draw("##colorCombo"u8, selection.ColorTextU8,
|
||||
"Associate a color with this NPC appearance.\n"u8
|
||||
+ "Right-Click to revert to automatic coloring.\n"u8
|
||||
+ "Hold Control and scroll the mousewheel to scroll."u8,
|
||||
width - Im.Style.ItemInnerSpacing.X - Im.Style.FrameHeight, out var newColorText))
|
||||
favorites.SetColor(selection.Data, newColorText.Item == DesignColors.AutomaticName ? string.Empty : newColorText.Item);
|
||||
|
||||
if (Im.Item.RightClicked())
|
||||
{
|
||||
_favorites.SetColor(selection, string.Empty);
|
||||
favorites.SetColor(selection.Data, string.Empty);
|
||||
color = string.Empty;
|
||||
}
|
||||
|
||||
if (_colors.TryGetValue(color, out var currentColor))
|
||||
if (designColors.TryGetValue(color, out var currentColor))
|
||||
{
|
||||
Im.Line.Same();
|
||||
Im.Line.SameInner();
|
||||
if (DesignColorUi.DrawColorButton($"Color associated with {color}", currentColor, out var newColor))
|
||||
_colors.SetColor(color, newColor);
|
||||
designColors.SetColor(color, newColor);
|
||||
}
|
||||
else if (color.Length is not 0)
|
||||
{
|
||||
Im.Line.Same();
|
||||
var size = new Vector2(Im.Style.FrameHeight);
|
||||
using var font = ImRaii.PushFont(UiBuilder.IconFont);
|
||||
ImEx.TextFramed(LunaStyle.WarningIcon.Span, size, _colors.MissingColor);
|
||||
ImUtf8.HoverTooltip("The color associated with this design does not exist."u8);
|
||||
Im.Line.SameInner();
|
||||
var size = new Vector2(Im.Style.FrameHeight);
|
||||
using (AwesomeIcon.Font.Push())
|
||||
{
|
||||
ImEx.TextFramed(LunaStyle.WarningIcon.Span, size, designColors.MissingColor);
|
||||
}
|
||||
|
||||
Im.Tooltip.OnHover("The color associated with this design does not exist."u8);
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
static void CopyButton(ReadOnlySpan<byte> label, string text)
|
||||
static void CopyButton(in Im.TableDisposable table, ReadOnlySpan<byte> label, Utf8StringHandler<HintStringHandlerBuffer> text)
|
||||
{
|
||||
ImUtf8.DrawFrameColumn(label);
|
||||
ImGui.TableNextColumn();
|
||||
if (ImUtf8.Button(text, new Vector2(Im.ContentRegion.Available.X, 0)))
|
||||
ImUtf8.SetClipboardText(text);
|
||||
ImUtf8.HoverTooltip("Click to copy to clipboard."u8);
|
||||
}
|
||||
}
|
||||
table.DrawFrameColumn(label);
|
||||
table.NextColumn();
|
||||
if (!text.GetSpan(out var span))
|
||||
return;
|
||||
|
||||
private sealed class ExportToClipboardButton(NpcPanel panel) : Button
|
||||
{
|
||||
protected override string Description
|
||||
=> "Copy the current NPCs appearance to your clipboard.\nHold Control to disable applying of customizations for the copied design.\nHold Shift to disable applying of gear for the copied design.";
|
||||
|
||||
protected override FontAwesomeIcon Icon
|
||||
=> FontAwesomeIcon.Copy;
|
||||
|
||||
public override bool Visible
|
||||
=> panel._selector.HasSelection;
|
||||
|
||||
protected override void OnClick()
|
||||
{
|
||||
try
|
||||
{
|
||||
var data = panel.ToDesignData();
|
||||
var text = panel._converter.ShareBase64(data, new StateMaterialManager(), ApplicationRules.NpcFromModifiers());
|
||||
ImGui.SetClipboardText(text);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Glamourer.Messager.NotificationMessage(ex, $"Could not copy {panel._selector.Selection.Name}'s data to clipboard.",
|
||||
$"Could not copy data from NPC appearance {panel._selector.Selection.Kind} {panel._selector.Selection.Id.Id} to clipboard",
|
||||
NotificationType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class SaveAsDesignButton(NpcPanel panel) : Button
|
||||
{
|
||||
protected override string Description
|
||||
=> "Save this NPCs appearance as a design.\nHold Control to disable applying of customizations for the saved design.\nHold Shift to disable applying of gear for the saved design.";
|
||||
|
||||
protected override FontAwesomeIcon Icon
|
||||
=> FontAwesomeIcon.Save;
|
||||
|
||||
public override bool Visible
|
||||
=> panel._selector.HasSelection;
|
||||
|
||||
protected override void OnClick()
|
||||
{
|
||||
ImGui.OpenPopup("Save as Design");
|
||||
panel._newName = panel._selector.Selection.Name;
|
||||
var data = panel.ToDesignData();
|
||||
panel._newDesign = panel._converter.Convert(data, new StateMaterialManager(), ApplicationRules.NpcFromModifiers());
|
||||
if (Im.Button(span, Im.ContentRegion.Available with { Y = 0 }))
|
||||
Im.Clipboard.Set(span);
|
||||
Im.Tooltip.OnHover("Click to copy to clipboard."u8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
97
Glamourer/Gui/Tabs/NpcTab/NpcSelection.cs
Normal file
97
Glamourer/Gui/Tabs/NpcTab/NpcSelection.cs
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
using Glamourer.Designs;
|
||||
using Glamourer.GameData;
|
||||
using ImSharp;
|
||||
using Luna;
|
||||
|
||||
namespace Glamourer.Gui.Tabs.NpcTab;
|
||||
|
||||
public sealed class NpcSelection : IUiService, IDisposable
|
||||
{
|
||||
private readonly LocalNpcAppearanceData _data;
|
||||
private readonly DesignColors _colors;
|
||||
private readonly DesignConverter _converter;
|
||||
|
||||
public NpcSelection(LocalNpcAppearanceData data, DesignColors colors, DesignConverter converter)
|
||||
{
|
||||
_data = data;
|
||||
_colors = colors;
|
||||
_converter = converter;
|
||||
|
||||
_data.DataChanged += OnDataChanged;
|
||||
_colors.ColorChanged += OnColorChanged;
|
||||
}
|
||||
|
||||
private void OnColorChanged()
|
||||
{
|
||||
if (!HasSelection)
|
||||
return;
|
||||
|
||||
Color = _data.GetData(Data).Color.ToVector();
|
||||
}
|
||||
|
||||
private void OnDataChanged()
|
||||
{
|
||||
if (!HasSelection)
|
||||
return;
|
||||
|
||||
ColorText = _data.GetColor(Data);
|
||||
ColorTextU8 = ColorText.Length is 0 ? DesignColors.AutomaticNameU8 : new StringU8(ColorText);
|
||||
(var color, Favorite) = _data.GetData(Data);
|
||||
Color = color.ToVector();
|
||||
}
|
||||
|
||||
public NpcData Data { get; private set; }
|
||||
public StringU8 Name { get; private set; } = StringU8.Empty;
|
||||
|
||||
public bool Favorite { get; private set; } = false;
|
||||
public string ColorText { get; private set; } = string.Empty;
|
||||
public StringU8 ColorTextU8 { get; private set; } = DesignColors.AutomaticNameU8;
|
||||
public Vector4 Color { get; private set; }
|
||||
|
||||
public uint Id
|
||||
=> Data.Id;
|
||||
|
||||
public bool HasSelection
|
||||
=> Id is not 0;
|
||||
|
||||
public DesignData ToDesignData()
|
||||
{
|
||||
var items = _converter.FromDrawData(Data.Equip(), Data.Mainhand, Data.Offhand, true).ToArray();
|
||||
var designData = new DesignData { Customize = Data.Customize };
|
||||
foreach (var (slot, item, stain) in items)
|
||||
{
|
||||
designData.SetItem(slot, item);
|
||||
designData.SetStain(slot, stain);
|
||||
}
|
||||
|
||||
return designData;
|
||||
}
|
||||
|
||||
public DesignBase ToDesignBase()
|
||||
{
|
||||
var data = ToDesignData();
|
||||
return _converter.Convert(data, new StateMaterialManager(), ApplicationRules.NpcFromModifiers());
|
||||
}
|
||||
|
||||
public string ToBase64()
|
||||
{
|
||||
var data = ToDesignData();
|
||||
return _converter.ShareBase64(data, new StateMaterialManager(), ApplicationRules.NpcFromModifiers());
|
||||
}
|
||||
|
||||
public void Update(in NpcCacheItem item)
|
||||
{
|
||||
Data = item.Npc;
|
||||
Name = item.Name.Utf8;
|
||||
Favorite = item.Favorite;
|
||||
ColorText = item.ColorText;
|
||||
ColorTextU8 = ColorText.Length > 0 ? new StringU8(ColorText) : DesignColors.AutomaticNameU8;
|
||||
Color = item.Color;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_data.DataChanged -= OnDataChanged;
|
||||
_colors.ColorChanged -= OnColorChanged;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,92 +1,85 @@
|
|||
using Glamourer.GameData;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Glamourer.Designs;
|
||||
using ImSharp;
|
||||
using OtterGui.Extensions;
|
||||
using OtterGui.Raii;
|
||||
using ImGuiClip = OtterGui.ImGuiClip;
|
||||
using Luna;
|
||||
|
||||
namespace Glamourer.Gui.Tabs.NpcTab;
|
||||
|
||||
public class NpcSelector : IDisposable
|
||||
public sealed class NpcSelector(
|
||||
NpcCustomizeSet npcs,
|
||||
LocalNpcAppearanceData favorites,
|
||||
NpcFilter filter,
|
||||
DesignColors designColors,
|
||||
NpcSelection selection) : IPanel
|
||||
{
|
||||
private readonly NpcCustomizeSet _npcs;
|
||||
private readonly LocalNpcAppearanceData _favorites;
|
||||
private readonly NpcCustomizeSet _npcs = npcs;
|
||||
private readonly LocalNpcAppearanceData _favorites = favorites;
|
||||
private readonly NpcFilter _filter = filter;
|
||||
private readonly DesignColors _designColors = designColors;
|
||||
|
||||
private NpcFilter _filter;
|
||||
private readonly List<int> _visibleOrdered = [];
|
||||
private int _selectedGlobalIndex;
|
||||
private bool _listDirty = true;
|
||||
private Vector2 _defaultItemSpacing;
|
||||
private float _width;
|
||||
public ReadOnlySpan<byte> Id
|
||||
=> "NpcSelector"u8;
|
||||
|
||||
|
||||
public NpcSelector(NpcCustomizeSet npcs, LocalNpcAppearanceData favorites)
|
||||
public void Draw()
|
||||
{
|
||||
_npcs = npcs;
|
||||
_favorites = favorites;
|
||||
_filter = new NpcFilter(_favorites);
|
||||
_favorites.DataChanged += OnFavoriteChange;
|
||||
Im.Cursor.Y += Im.Style.FramePadding.Y;
|
||||
var cache = CacheManager.Instance.GetOrCreateCache(Im.Id.Current, () => new Cache(this));
|
||||
using var clipper = new Im.ListClipper(cache.Count, Im.Style.TextHeightWithSpacing);
|
||||
using var color = new Im.ColorDisposable();
|
||||
foreach (var item in clipper.Iterate(cache))
|
||||
{
|
||||
Im.Cursor.X += Im.Style.FramePadding.X;
|
||||
using var id = Im.Id.Push(item.Npc.Id.Id);
|
||||
color.Push(ImGuiColor.Text, item.Color);
|
||||
if (Im.Selectable(item.Name.Utf8, item.Npc.Id == selection.Id))
|
||||
selection.Update(item);
|
||||
color.Pop();
|
||||
var size = item.Id.CalculateSize();
|
||||
Im.Line.Same();
|
||||
if (Im.ContentRegion.Available.X >= size.X)
|
||||
{
|
||||
color.Push(ImGuiColor.Text, Im.Style[ImGuiColor.TextDisabled]);
|
||||
ImEx.TextRightAligned(item.Id, 0, size.X);
|
||||
color.Pop();
|
||||
}
|
||||
else
|
||||
{
|
||||
Im.Tooltip.OnHover(item.Id);
|
||||
Im.Line.New();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
private NpcCacheItem CreateItem(in NpcData data)
|
||||
{
|
||||
_favorites.DataChanged -= OnFavoriteChange;
|
||||
var colorText = _favorites.GetColor(data);
|
||||
var (color, favorite) = _favorites.GetData(data);
|
||||
return new NpcCacheItem(data, colorText, color, favorite);
|
||||
}
|
||||
|
||||
private void OnFavoriteChange()
|
||||
=> _listDirty = true;
|
||||
|
||||
public void UpdateList()
|
||||
private sealed class Cache : BasicFilterCache<NpcCacheItem>
|
||||
{
|
||||
if (!_listDirty)
|
||||
return;
|
||||
private readonly NpcSelector _parent;
|
||||
|
||||
_listDirty = false;
|
||||
_visibleOrdered.Clear();
|
||||
var enumerable = _npcs.WithIndex();
|
||||
if (!_filter.IsEmpty)
|
||||
enumerable = enumerable.Where(d => _filter.ApplyFilter(d.Value));
|
||||
var range = enumerable.OrderByDescending(d => _favorites.IsFavorite(d.Value))
|
||||
.ThenBy(d => d.Index)
|
||||
.Select(d => d.Index);
|
||||
_visibleOrdered.AddRange(range);
|
||||
}
|
||||
public Cache(NpcSelector parent)
|
||||
: base(parent._filter)
|
||||
{
|
||||
_parent = parent;
|
||||
_parent._favorites.DataChanged += OnDataChange;
|
||||
_parent._designColors.ColorChanged += OnDataChange;
|
||||
}
|
||||
|
||||
public bool HasSelection
|
||||
=> _selectedGlobalIndex >= 0 && _selectedGlobalIndex < _npcs.Count;
|
||||
private void OnDataChange()
|
||||
=> Dirty |= IManagedCache.DirtyFlags.Custom;
|
||||
|
||||
public NpcData Selection
|
||||
=> HasSelection ? _npcs[_selectedGlobalIndex] : default;
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
_parent._favorites.DataChanged -= OnDataChange;
|
||||
_parent._designColors.ColorChanged -= OnDataChange;
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
public void Draw(float width)
|
||||
{
|
||||
_width = width;
|
||||
using var group = ImRaii.Group();
|
||||
_defaultItemSpacing = Im.Style.ItemSpacing;
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
|
||||
.Push(ImGuiStyleVar.FrameRounding, 0);
|
||||
|
||||
if (_filter.Draw(width))
|
||||
_listDirty = true;
|
||||
UpdateList();
|
||||
DrawSelector();
|
||||
}
|
||||
|
||||
private void DrawSelector()
|
||||
{
|
||||
using var child = ImRaii.Child("##Selector", new Vector2(_width, Im.ContentRegion.Available.Y), true);
|
||||
if (!child)
|
||||
return;
|
||||
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, _defaultItemSpacing);
|
||||
ImGuiClip.ClippedDraw(_visibleOrdered, DrawSelectable, Im.Style.TextHeight);
|
||||
}
|
||||
|
||||
private void DrawSelectable(int globalIndex)
|
||||
{
|
||||
using var id = Im.Id.Push(globalIndex);
|
||||
using var color = ImGuiColor.Text.Push(_favorites.GetData(_npcs[globalIndex]).Color);
|
||||
if (ImGui.Selectable(_npcs[globalIndex].Name, _selectedGlobalIndex == globalIndex, ImGuiSelectableFlags.AllowItemOverlap))
|
||||
_selectedGlobalIndex = globalIndex;
|
||||
protected override IEnumerable<NpcCacheItem> GetItems()
|
||||
=> _parent._npcs.Select(n => _parent.CreateItem(n)).OrderByDescending(n => n.Favorite);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,18 +3,24 @@ using Luna;
|
|||
|
||||
namespace Glamourer.Gui.Tabs.NpcTab;
|
||||
|
||||
public sealed class NpcTab(NpcSelector selector, NpcPanel panel) : ITab<MainTabType>
|
||||
public sealed class NpcTab : TwoPanelLayout, ITab<MainTabType>
|
||||
{
|
||||
public ReadOnlySpan<byte> Label
|
||||
public NpcTab(NpcFilter filter, NpcSelector selector, NpcPanel panel, NpcHeader header)
|
||||
{
|
||||
LeftHeader = new FilterHeader<NpcCacheItem>(filter, new StringU8("Filter..."u8));
|
||||
LeftPanel = selector;
|
||||
LeftFooter = NopHeaderFooter.Instance;
|
||||
RightHeader = header;
|
||||
RightPanel = panel;
|
||||
RightFooter = NopHeaderFooter.Instance;
|
||||
}
|
||||
|
||||
public override ReadOnlySpan<byte> Label
|
||||
=> "NPCs"u8;
|
||||
|
||||
public MainTabType Identifier
|
||||
=> MainTabType.Npcs;
|
||||
|
||||
public void DrawContent()
|
||||
{
|
||||
selector.Draw(200 * Im.Style.GlobalScale);
|
||||
Im.Line.Same();
|
||||
panel.Draw();
|
||||
}
|
||||
=> Draw(TwoPanelWidth.IndeterminateRelative);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -299,6 +299,28 @@ public sealed class SettingsTab(
|
|||
Im.Line.New();
|
||||
}
|
||||
|
||||
private readonly (StringU8, QdbButtons)[] _columns =
|
||||
[
|
||||
(new StringU8("Apply Design"u8), QdbButtons.ApplyDesign),
|
||||
(new StringU8("Revert All"u8), QdbButtons.RevertAll),
|
||||
(new StringU8("Revert to Auto"u8), QdbButtons.RevertAutomation),
|
||||
(new StringU8("Reapply Auto"u8), QdbButtons.ReapplyAutomation),
|
||||
(new StringU8("Revert Equip"u8), QdbButtons.RevertEquip),
|
||||
(new StringU8("Revert Customize"u8), QdbButtons.RevertCustomize),
|
||||
(new StringU8("Revert Advanced Customization"u8), QdbButtons.RevertAdvancedCustomization),
|
||||
(new StringU8("Revert Advanced Dyes"u8), QdbButtons.RevertAdvancedDyes),
|
||||
(new StringU8("Reset Settings"u8), QdbButtons.ResetSettings),
|
||||
];
|
||||
|
||||
private static bool DisplayButton(QdbButtons button, bool showAuto, bool useTemporarySettings)
|
||||
=> button switch
|
||||
{
|
||||
QdbButtons.RevertAutomation => showAuto,
|
||||
QdbButtons.ReapplyAutomation => showAuto,
|
||||
QdbButtons.ResetSettings => useTemporarySettings,
|
||||
_ => true,
|
||||
};
|
||||
|
||||
private void DrawQuickDesignBoxes()
|
||||
{
|
||||
var showAuto = config.EnableAutoDesigns;
|
||||
|
|
@ -310,23 +332,11 @@ public sealed class SettingsTab(
|
|||
if (!table)
|
||||
return;
|
||||
|
||||
IEnumerable<RefTuple<ReadOnlySpan<byte>, bool, QdbButtons>> columns =
|
||||
[
|
||||
RefTuple.Create("Apply Design"u8, true, QdbButtons.ApplyDesign),
|
||||
RefTuple.Create("Revert All"u8, true, QdbButtons.RevertAll),
|
||||
RefTuple.Create("Revert to Auto"u8, showAuto, QdbButtons.RevertAutomation),
|
||||
RefTuple.Create("Reapply Auto"u8, showAuto, QdbButtons.ReapplyAutomation),
|
||||
RefTuple.Create("Revert Equip"u8, true, QdbButtons.RevertEquip),
|
||||
RefTuple.Create("Revert Customize"u8, true, QdbButtons.RevertCustomize),
|
||||
RefTuple.Create("Revert Advanced Customization"u8, true, QdbButtons.RevertAdvancedCustomization),
|
||||
RefTuple.Create("Revert Advanced Dyes"u8, true, QdbButtons.RevertAdvancedDyes),
|
||||
RefTuple.Create("Reset Settings"u8, config.UseTemporarySettings, QdbButtons.ResetSettings),
|
||||
];
|
||||
|
||||
// ReSharper disable once PossibleMultipleEnumeration
|
||||
foreach (var (text, display, _) in columns)
|
||||
foreach (var (text, flag) in _columns)
|
||||
{
|
||||
if (!display)
|
||||
if (!DisplayButton(flag, showAuto, config.UseTemporarySettings))
|
||||
continue;
|
||||
|
||||
table.NextColumn();
|
||||
|
|
@ -334,9 +344,9 @@ public sealed class SettingsTab(
|
|||
}
|
||||
|
||||
// ReSharper disable once PossibleMultipleEnumeration
|
||||
foreach (var (_, display, flag) in columns)
|
||||
foreach (var (_, flag) in _columns)
|
||||
{
|
||||
if (!display)
|
||||
if (!DisplayButton(flag, showAuto, config.UseTemporarySettings))
|
||||
continue;
|
||||
|
||||
using var id = Im.Id.Push((int)flag);
|
||||
|
|
|
|||
2
Luna
2
Luna
|
|
@ -1 +1 @@
|
|||
Subproject commit c52743f736892dde62f39e6a2b06fde4096cdff7
|
||||
Subproject commit 042b829099c34608782c2a70da3cf189ac5f53be
|
||||
Loading…
Add table
Add a link
Reference in a new issue