Add UI stuff to random designs.

This commit is contained in:
Ottermandias 2024-02-29 14:37:23 +01:00
parent 2a01b328e1
commit 139508917b
18 changed files with 744 additions and 117 deletions

View file

@ -1,4 +1,5 @@
using Glamourer.Designs;
using Glamourer.Designs.Special;
using Glamourer.GameData;
using Glamourer.Interop.Structs;
using Newtonsoft.Json.Linq;
@ -9,8 +10,6 @@ namespace Glamourer.Automation;
public class AutoDesign
{
public const string RevertName = "Revert";
public IDesignStandIn Design = new RevertDesign();
public JobGroup Jobs;
public ApplicationType Type;

View file

@ -131,6 +131,7 @@ public sealed class AutoDesignApplier : IDisposable
case AutomationChanged.Type.ChangedDesign:
case AutomationChanged.Type.ChangedConditions:
case AutomationChanged.Type.ChangedType:
case AutomationChanged.Type.ChangedData:
ApplyNew(set);
break;
}

View file

@ -1,6 +1,7 @@
using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Interface.Internal.Notifications;
using Glamourer.Designs;
using Glamourer.Designs.Special;
using Glamourer.Events;
using Glamourer.Interop;
using Glamourer.Services;
@ -347,6 +348,20 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
_event.Invoke(AutomationChanged.Type.ChangedType, set, (which, old, applicationType));
}
public void ChangeData(AutoDesignSet set, int which, object data)
{
if (which >= set.Designs.Count || which < 0)
return;
var design = set.Designs[which];
if (!design.Design.ChangeData(data))
return;
Save();
Glamourer.Log.Debug($"Changed additional design data for associated design {which + 1} in design set.");
_event.Invoke(AutomationChanged.Type.ChangedData, set, (which, data));
}
public string ToFilename(FilenameService fileNames)
=> fileNames.AutomationFile;
@ -499,6 +514,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
design = d;
}
design.ParseData(jObj);
// ApplicationType is a migration from an older property name.

View file

@ -81,6 +81,9 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn
public void ParseData(JObject _)
{ }
public bool ChangeData(object data)
=> false;
#endregion
#region Serialization

View file

@ -20,4 +20,6 @@ public interface IDesignStandIn : IEquatable<IDesignStandIn>
public void AddData(JObject jObj);
public void ParseData(JObject jObj);
public bool ChangeData(object data);
}

View file

@ -1,91 +0,0 @@
using OtterGui;
using OtterGui.Services;
using System;
namespace Glamourer.Designs;
public class RandomDesignGenerator(DesignStorage designs, DesignFileSystem fileSystem) : IService
{
private readonly Random _rng = new();
public Design? Design(IReadOnlyList<Design> localDesigns)
{
if (localDesigns.Count == 0)
return null;
var idx = _rng.Next(0, localDesigns.Count - 1);
Glamourer.Log.Verbose($"[Random Design] Chose design {idx} out of {localDesigns.Count}: {localDesigns[idx].Incognito}.");
return localDesigns[idx];
}
public Design? Design()
=> Design(designs);
public Design? Design(string restrictions)
{
if (restrictions.Length == 0)
return Design(designs);
List<Func<string, string, string, bool>> predicates = [];
switch (restrictions[0])
{
case '{':
var end = restrictions.IndexOf('}');
if (end == -1)
throw new ArgumentException($"The restriction group '{restrictions}' is not properly terminated.");
restrictions = restrictions[1..end];
var split = restrictions.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
foreach (var item in split.Distinct())
predicates.Add(item[0] == '/' ? CreatePredicateSlash(item) : CreatePredicate(item));
break;
case '/':
predicates.Add(CreatePredicateSlash(restrictions));
break;
default:
predicates.Add(CreatePredicate(restrictions));
break;
}
if (predicates.Count == 1)
{
var p = predicates[0];
return Design(designs.Select(Transform).Where(t => p(t.NameLower, t.Identifier, t.PathLower)).Select(t => t.Design).ToList());
}
return Design(designs.Select(Transform).Where(t => predicates.Any(p => p(t.NameLower, t.Identifier, t.PathLower))).Select(t => t.Design)
.ToList());
(Design Design, string NameLower, string Identifier, string PathLower) Transform(Design design)
{
var name = design.Name.Lower;
var identifier = design.Identifier.ToString();
var path = fileSystem.FindLeaf(design, out var leaf) ? leaf.FullName().ToLowerInvariant() : string.Empty;
return (design, name, identifier, path);
}
Func<string, string, string, bool> CreatePredicate(string input)
{
var value = input.ToLowerInvariant();
return (string nameLower, string identifier, string pathLower) =>
{
if (nameLower.Contains(value))
return true;
if (identifier.Contains(value))
return true;
if (pathLower.Contains(value))
return true;
return false;
};
}
Func<string, string, string, bool> CreatePredicateSlash(string input)
{
var value = input[1..].ToLowerInvariant();
return (string nameLower, string identifier, string pathLower)
=> pathLower.StartsWith(value);
}
}
}

View file

@ -3,7 +3,7 @@ using Glamourer.Interop.Material;
using Glamourer.State;
using Newtonsoft.Json.Linq;
namespace Glamourer.Designs;
namespace Glamourer.Designs.Special;
public class RandomDesign(RandomDesignGenerator rng) : IDesignStandIn
{
@ -11,14 +11,14 @@ public class RandomDesign(RandomDesignGenerator rng) : IDesignStandIn
public const string ResolvedName = "Random";
private Design? _currentDesign;
public string Restrictions { get; internal set; } = string.Empty;
public IReadOnlyList<IDesignPredicate> Predicates { get; private set; } = [];
public string ResolveName(bool _)
=> ResolvedName;
public ref readonly DesignData GetDesignData(in DesignData baseRef)
{
_currentDesign ??= rng.Design(Restrictions);
_currentDesign ??= rng.Design(Predicates);
if (_currentDesign == null)
return ref baseRef;
@ -27,7 +27,7 @@ public class RandomDesign(RandomDesignGenerator rng) : IDesignStandIn
public IReadOnlyList<(uint, MaterialValueDesign)> GetMaterialData()
{
_currentDesign ??= rng.Design(Restrictions);
_currentDesign ??= rng.Design(Predicates);
if (_currentDesign == null)
return [];
@ -38,7 +38,9 @@ public class RandomDesign(RandomDesignGenerator rng) : IDesignStandIn
=> SerializedName;
public bool Equals(IDesignStandIn? other)
=> other is RandomDesign r && string.Equals(r.Restrictions, Restrictions, StringComparison.OrdinalIgnoreCase);
=> other is RandomDesign r
&& string.Equals(RandomPredicate.GeneratePredicateString(r.Predicates), RandomPredicate.GeneratePredicateString(Predicates),
StringComparison.OrdinalIgnoreCase);
public StateSource AssociatedSource()
=> StateSource.Manual;
@ -47,7 +49,7 @@ public class RandomDesign(RandomDesignGenerator rng) : IDesignStandIn
{
get
{
_currentDesign = rng.Design(Restrictions);
_currentDesign = rng.Design(Predicates);
if (_currentDesign == null)
yield break;
@ -58,12 +60,21 @@ public class RandomDesign(RandomDesignGenerator rng) : IDesignStandIn
public void AddData(JObject jObj)
{
jObj["Restrictions"] = Restrictions;
jObj["Restrictions"] = RandomPredicate.GeneratePredicateString(Predicates);
}
public void ParseData(JObject jObj)
{
var restrictions = jObj["Restrictions"]?.ToObject<string>() ?? string.Empty;
Restrictions = restrictions;
Predicates = RandomPredicate.GeneratePredicates(restrictions);
}
public bool ChangeData(object data)
{
if (data is not List<IDesignPredicate> predicates)
return false;
Predicates = predicates;
return true;
}
}

View file

@ -0,0 +1,37 @@
using OtterGui.Services;
namespace Glamourer.Designs.Special;
public class RandomDesignGenerator(DesignStorage designs, DesignFileSystem fileSystem) : IService
{
private readonly Random _rng = new();
public Design? Design(IReadOnlyList<Design> localDesigns)
{
if (localDesigns.Count == 0)
return null;
var idx = _rng.Next(0, localDesigns.Count - 1);
Glamourer.Log.Verbose($"[Random Design] Chose design {idx} out of {localDesigns.Count}: {localDesigns[idx].Incognito}.");
return localDesigns[idx];
}
public Design? Design()
=> Design(designs);
public Design? Design(IDesignPredicate predicate)
=> Design(predicate.Get(designs, fileSystem).ToList());
public Design? Design(IReadOnlyList<IDesignPredicate> predicates)
{
if (predicates.Count == 0)
return Design();
if (predicates.Count == 1)
return Design(predicates[0]);
return Design(IDesignPredicate.Get(predicates, designs, fileSystem).ToList());
}
public Design? Design(string restrictions)
=> Design(RandomPredicate.GeneratePredicates(restrictions));
}

View file

@ -0,0 +1,163 @@
using OtterGui.Classes;
namespace Glamourer.Designs.Special;
public interface IDesignPredicate
{
public bool Invoke(Design design, string lowerName, string identifier, string lowerPath);
public bool Invoke((Design Design, string LowerName, string Identifier, string LowerPath) args)
=> Invoke(args.Design, args.LowerName, args.Identifier, args.LowerPath);
public IEnumerable<Design> Get(IEnumerable<Design> designs, DesignFileSystem fileSystem)
=> designs.Select(d => Transform(d, fileSystem))
.Where(Invoke)
.Select(t => t.Design);
public static IEnumerable<Design> Get(IReadOnlyList<IDesignPredicate> predicates, IEnumerable<Design> designs, DesignFileSystem fileSystem)
=> predicates.Count > 0
? designs.Select(d => Transform(d, fileSystem))
.Where(t => predicates.Any(p => p.Invoke(t)))
.Select(t => t.Design)
: designs;
private static (Design Design, string LowerName, string Identifier, string LowerPath) Transform(Design d, DesignFileSystem fs)
=> (d, d.Name.Lower, d.Identifier.ToString(), fs.FindLeaf(d, out var l) ? l.FullName().ToLowerInvariant() : string.Empty);
}
public static class RandomPredicate
{
public readonly struct StartsWith(string value) : IDesignPredicate
{
public LowerString Value { get; } = value;
public bool Invoke(Design design, string lowerName, string identifier, string lowerPath)
=> lowerPath.StartsWith(Value.Lower);
public override string ToString()
=> $"/{Value.Text}";
}
public readonly struct Contains(string value) : IDesignPredicate
{
public LowerString Value { get; } = value;
public bool Invoke(Design design, string lowerName, string identifier, string lowerPath)
{
if (lowerName.Contains(Value.Lower))
return true;
if (identifier.Contains(Value.Lower))
return true;
if (lowerPath.Contains(Value.Lower))
return true;
return false;
}
public override string ToString()
=> Value.Text;
}
public readonly struct Exact(Exact.Type type, string value) : IDesignPredicate
{
public enum Type : byte
{
Name,
Path,
Identifier,
Tag,
Color,
}
public Type Which { get; } = type;
public LowerString Value { get; } = value;
public bool Invoke(Design design, string lowerName, string identifier, string lowerPath)
=> Which switch
{
Type.Name => lowerName == Value.Lower,
Type.Path => lowerPath == Value.Lower,
Type.Identifier => identifier == Value.Lower,
Type.Tag => IsContained(Value, design.Tags),
Type.Color => design.Color == Value,
_ => false,
};
private static bool IsContained(LowerString value, IEnumerable<string> data)
=> data.Any(t => t == value);
public override string ToString()
=> $"\"{Which switch { Type.Name => 'n', Type.Identifier => 'i', Type.Path => 'p', Type.Tag => 't', Type.Color => 'c', _ => '?' }}?{Value.Text}\"";
}
public static IDesignPredicate CreateSinglePredicate(string restriction)
{
switch (restriction[0])
{
case '/': return new StartsWith(restriction[1..]);
case '"':
var end = restriction.IndexOf('"', 1);
if (end < 3)
return new Contains(restriction);
switch (restriction[1], restriction[2])
{
case ('n', '?'):
case ('N', '?'):
return new Exact(Exact.Type.Name, restriction[3..end]);
case ('p', '?'):
case ('P', '?'):
return new Exact(Exact.Type.Path, restriction[3..end]);
case ('i', '?'):
case ('I', '?'):
return new Exact(Exact.Type.Identifier, restriction[3..end]);
case ('t', '?'):
case ('T', '?'):
return new Exact(Exact.Type.Tag, restriction[3..end]);
case ('c', '?'):
case ('C', '?'):
return new Exact(Exact.Type.Color, restriction[3..end]);
default: return new Contains(restriction);
}
default: return new Contains(restriction);
}
}
public static List<IDesignPredicate> GeneratePredicates(string restrictions)
{
if (restrictions.Length == 0)
return [];
List<IDesignPredicate> predicates = new(1);
if (restrictions[0] is '{')
{
var end = restrictions.IndexOf('}');
if (end == -1)
{
predicates.Add(CreateSinglePredicate(restrictions));
}
else
{
restrictions = restrictions[1..end];
var split = restrictions.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
predicates.AddRange(split.Distinct().Select(CreateSinglePredicate));
}
}
else
{
predicates.Add(CreateSinglePredicate(restrictions));
}
return predicates;
}
public static string GeneratePredicateString(IReadOnlyCollection<IDesignPredicate> predicates)
{
if (predicates.Count == 0)
return string.Empty;
if (predicates.Count == 1)
return predicates.First()!.ToString()!;
return $"{{{string.Join("; ", predicates)}}}";
}
}

View file

@ -3,7 +3,7 @@ using Glamourer.Interop.Material;
using Glamourer.State;
using Newtonsoft.Json.Linq;
namespace Glamourer.Designs;
namespace Glamourer.Designs.Special;
public class RevertDesign : IDesignStandIn
{
@ -38,4 +38,7 @@ public class RevertDesign : IDesignStandIn
public void ParseData(JObject jObj)
{ }
public bool ChangeData(object data)
=> false;
}

View file

@ -46,7 +46,7 @@ public sealed class AutomationChanged()
/// <summary> Move a given associated design in the list of a given set. Additional data is the index that got moved and the index it got moved to [(int, int)]. </summary>
MovedDesign,
/// <summary> Change the linked design in an associated design for a given set. Additional data is the index of the changed associated design, the old linked design and the new linked design [(int, Design, Design)]. </summary>
/// <summary> Change the linked design in an associated design for a given set. Additional data is the index of the changed associated design, the old linked design and the new linked design [(int, IDesignStandIn, IDesignStandIn)]. </summary>
ChangedDesign,
/// <summary> Change the job condition in an associated design for a given set. Additional data is the index of the changed associated design, the old job group and the new job group [(int, JobGroup, JobGroup)]. </summary>
@ -54,6 +54,9 @@ public sealed class AutomationChanged()
/// <summary> Change the application type in an associated design for a given set. Additional data is the index of the changed associated design, the old type and the new type. [(int, AutoDesign.Type, AutoDesign.Type)]. </summary>
ChangedType,
/// <summary> Change the additional data for a specific design type. Additional data is the index of the changed associated design and the new data. [(int, object)] </summary>
ChangedData,
}
public enum Priority
@ -62,6 +65,9 @@ public sealed class AutomationChanged()
SetSelector = 0,
/// <seealso cref="AutoDesignApplier.OnAutomationChange"/>
AutoDesignApplier,
AutoDesignApplier = 0,
/// <seealso cref="Gui.Tabs.AutomationTab.RandomRestrictionDrawer.OnAutomationChange"/>
RandomRestrictionDrawer = -1,
}
}

View file

@ -2,6 +2,7 @@
using Dalamud.Interface.Utility.Raii;
using Glamourer.Automation;
using Glamourer.Designs;
using Glamourer.Designs.Special;
using Glamourer.Events;
using ImGuiNET;
using OtterGui;
@ -207,6 +208,42 @@ public sealed class LinkDesignCombo(
.OrderBy(d => d.Item2),
]);
public sealed class RandomDesignCombo(
DesignManager designs,
DesignFileSystem fileSystem,
Logger log,
DesignChanged designChanged,
TabSelected tabSelected,
EphemeralConfig config,
DesignColors designColors)
: DesignCombo(log, designChanged, tabSelected, config, designColors, () =>
[
.. designs.Designs
.Select(d => new Tuple<IDesignStandIn, string>(d, fileSystem.FindLeaf(d, out var l) ? l.FullName() : string.Empty))
.OrderBy(d => d.Item2),
])
{
private Design? GetDesign(RandomPredicate.Exact exact)
{
return exact.Which switch
{
RandomPredicate.Exact.Type.Name => designs.Designs.FirstOrDefault(d => d.Name == exact.Value),
RandomPredicate.Exact.Type.Path => fileSystem.Find(exact.Value.Text, out var c) && c is DesignFileSystem.Leaf l ? l.Value : null,
RandomPredicate.Exact.Type.Identifier => designs.Designs.ByIdentifier(Guid.TryParse(exact.Value.Text, out var g) ? g : Guid.Empty),
_ => null,
};
}
public bool Draw(RandomPredicate.Exact exact, float width)
{
var design = GetDesign(exact);
return Draw(design, design?.ResolveName(Incognito) ?? $"Not Found [{exact.Value.Text}]", width);
}
public bool Draw(IDesignStandIn? design, float width)
=> Draw(design, design?.ResolveName(Incognito) ?? string.Empty, width);
}
public sealed class SpecialDesignCombo(
DesignManager designs,
DesignFileSystem fileSystem,

View file

@ -158,16 +158,14 @@ public sealed unsafe class AdvancedDyePopup(
if (config.KeepAdvancedDyesAttached)
{
var position = ImGui.GetWindowPos();
position.X += ImGui.GetWindowSize().X;
position.X += ImGui.GetWindowSize().X + ImGui.GetStyle().WindowPadding.X;
ImGui.SetNextWindowPos(position);
flags |= ImGuiWindowFlags.NoMove;
}
var size = new Vector2(7 * ImGui.GetFrameHeight() + 3 * ImGui.GetStyle().ItemInnerSpacing.X + 300 * ImGuiHelpers.GlobalScale,
18 * ImGui.GetFrameHeightWithSpacing() + ImGui.GetStyle().WindowPadding.Y + ImGui.GetStyle().ItemSpacing.Y);
18 * ImGui.GetFrameHeightWithSpacing() + ImGui.GetStyle().WindowPadding.Y + 2 * ImGui.GetStyle().ItemSpacing.Y);
ImGui.SetNextWindowSize(size);
var window = ImGui.Begin("###Glamourer Advanced Dyes", flags);
try
{

View file

@ -0,0 +1,432 @@
using Dalamud.Interface;
using Dalamud.Interface.Utility;
using Glamourer.Automation;
using Glamourer.Designs;
using Glamourer.Designs.Special;
using Glamourer.Events;
using ImGuiNET;
using OtterGui;
using OtterGui.Raii;
using OtterGui.Services;
namespace Glamourer.Gui.Tabs.AutomationTab;
public sealed class RandomRestrictionDrawer : IService, IDisposable
{
private AutoDesignSet? _set;
private int _designIndex = -1;
private readonly AutomationChanged _automationChanged;
private readonly Configuration _config;
private readonly AutoDesignManager _autoDesignManager;
private readonly RandomDesignCombo _randomDesignCombo;
private readonly SetSelector _selector;
private readonly DesignStorage _designs;
private readonly DesignFileSystem _designFileSystem;
private string _newText = string.Empty;
private string? _newDefinition;
private Design? _newDesign = null;
public RandomRestrictionDrawer(AutomationChanged automationChanged, Configuration config, AutoDesignManager autoDesignManager,
RandomDesignCombo randomDesignCombo, SetSelector selector, DesignFileSystem designFileSystem, DesignStorage designs)
{
_automationChanged = automationChanged;
_config = config;
_autoDesignManager = autoDesignManager;
_randomDesignCombo = randomDesignCombo;
_selector = selector;
_designFileSystem = designFileSystem;
_designs = designs;
_automationChanged.Subscribe(OnAutomationChange, AutomationChanged.Priority.RandomRestrictionDrawer);
}
public void Dispose()
{
_automationChanged.Unsubscribe(OnAutomationChange);
}
public void DrawButton(AutoDesignSet set, int designIndex)
{
var isOpen = set == _set && designIndex == _designIndex;
using (var color = ImRaii.PushColor(ImGuiCol.Button, ImGui.GetColorU32(ImGuiCol.ButtonActive), isOpen)
.Push(ImGuiCol.Text, ColorId.HeaderButtons.Value(), isOpen)
.Push(ImGuiCol.Border, ColorId.HeaderButtons.Value(), isOpen))
{
using var frame = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, 2 * ImGuiHelpers.GlobalScale, isOpen);
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Edit.ToIconString(), new Vector2(ImGui.GetFrameHeight()),
string.Empty, false, true))
{
if (isOpen)
Close();
else
Open(set, designIndex);
}
}
ImGuiUtil.HoverTooltip("Edit restrictions for this random design.");
}
private void Open(AutoDesignSet set, int designIndex)
{
if (designIndex < 0 || designIndex >= set.Designs.Count)
return;
var design = set.Designs[designIndex];
if (design.Design is not RandomDesign)
return;
_set = set;
_designIndex = designIndex;
}
private void Close()
{
_set = null;
_designIndex = -1;
}
public void Draw()
{
if (_set == null || _designIndex < 0 || _designIndex >= _set.Designs.Count)
return;
if (_set != _selector.Selection)
{
Close();
return;
}
var design = _set.Designs[_designIndex];
if (design.Design is not RandomDesign random)
return;
DrawWindow(random);
}
private void DrawWindow(RandomDesign random)
{
var flags = ImGuiWindowFlags.NoFocusOnAppearing
| ImGuiWindowFlags.NoCollapse
| ImGuiWindowFlags.NoResize;
// Set position to the right of the main window when attached
// The downwards offset is implicit through child position.
if (_config.KeepAdvancedDyesAttached)
{
var position = ImGui.GetWindowPos();
position.X += ImGui.GetWindowSize().X + ImGui.GetStyle().WindowPadding.X;
ImGui.SetNextWindowPos(position);
flags |= ImGuiWindowFlags.NoMove;
}
using var color = ImRaii.PushColor(ImGuiCol.TitleBgActive, ImGui.GetColorU32(ImGuiCol.TitleBg));
var size = new Vector2(7 * ImGui.GetFrameHeight() + 3 * ImGui.GetStyle().ItemInnerSpacing.X + 300 * ImGuiHelpers.GlobalScale,
18 * ImGui.GetFrameHeightWithSpacing() + ImGui.GetStyle().WindowPadding.Y + ImGui.GetStyle().ItemSpacing.Y);
ImGui.SetNextWindowSize(size);
var open = true;
var window = ImGui.Begin($"{_set!.Name} #{_designIndex + 1:D2}###Glamourer Random Design", ref open, flags);
try
{
if (window)
DrawContent(random);
}
finally
{
ImGui.End();
}
if (!open)
Close();
}
private void DrawTable(RandomDesign random, List<IDesignPredicate> list)
{
using var table = ImRaii.Table("##table", 3);
if (!table)
return;
using var spacing = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemInnerSpacing);
var buttonSize = new Vector2(ImGui.GetFrameHeight());
var descWidth = ImGui.CalcTextSize("or that are set to the color").X;
ImGui.TableSetupColumn("desc", ImGuiTableColumnFlags.WidthFixed, descWidth);
ImGui.TableSetupColumn("input", ImGuiTableColumnFlags.WidthStretch);
ImGui.TableSetupColumn("del", ImGuiTableColumnFlags.WidthFixed, buttonSize.X * 2 + ImGui.GetStyle().ItemInnerSpacing.X);
var orSize = ImGui.CalcTextSize("or ");
for (var i = 0; i < random.Predicates.Count; ++i)
{
using var id = ImRaii.PushId(i);
var predicate = random.Predicates[i];
ImGui.TableNextColumn();
ImGui.AlignTextToFramePadding();
if (i != 0)
ImGui.TextUnformatted("or ");
else
ImGui.Dummy(orSize);
ImGui.SameLine(0, 0);
ImGui.AlignTextToFramePadding();
switch (predicate)
{
case RandomPredicate.Contains contains:
{
ImGui.TextUnformatted("that contain");
ImGui.TableNextColumn();
var data = contains.Value.Text;
ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X);
if (ImGui.InputTextWithHint("##match", "Name, Path, or Identifier Contains...", ref data, 128))
{
if (data.Length == 0)
list.RemoveAt(i);
else
list[i] = new RandomPredicate.Contains(data);
_autoDesignManager.ChangeData(_set!, _designIndex, list);
}
break;
}
case RandomPredicate.StartsWith startsWith:
{
ImGui.TextUnformatted("whose path starts with");
ImGui.TableNextColumn();
var data = startsWith.Value.Text;
ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X);
if (ImGui.InputTextWithHint("##startsWith", "Path Starts With...", ref data, 128))
{
if (data.Length == 0)
list.RemoveAt(i);
else
list[i] = new RandomPredicate.StartsWith(data);
_autoDesignManager.ChangeData(_set!, _designIndex, list);
}
break;
}
case RandomPredicate.Exact { Which: RandomPredicate.Exact.Type.Tag } exact:
{
ImGui.TextUnformatted("that contain the tag");
ImGui.TableNextColumn();
ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X);
var data = exact.Value.Text;
if (ImGui.InputTextWithHint("##color", "Contained tag...", ref data, 128))
{
if (data.Length == 0)
list.RemoveAt(i);
else
list[i] = new RandomPredicate.Exact(RandomPredicate.Exact.Type.Tag, data);
_autoDesignManager.ChangeData(_set!, _designIndex, list);
}
break;
}
case RandomPredicate.Exact { Which: RandomPredicate.Exact.Type.Color } exact:
{
ImGui.TextUnformatted("that are set to the color");
ImGui.TableNextColumn();
ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X);
var data = exact.Value.Text;
if (ImGui.InputTextWithHint("##color", "Assigned Color is...", ref data, 128))
{
if (data.Length == 0)
list.RemoveAt(i);
else
list[i] = new RandomPredicate.Exact(RandomPredicate.Exact.Type.Color, data);
_autoDesignManager.ChangeData(_set!, _designIndex, list);
}
break;
}
case RandomPredicate.Exact exact:
{
ImGui.TextUnformatted("that are exactly");
ImGui.TableNextColumn();
if (_randomDesignCombo.Draw(exact, ImGui.GetContentRegionAvail().X) && _randomDesignCombo.Design is Design d)
{
list[i] = new RandomPredicate.Exact(RandomPredicate.Exact.Type.Identifier, d.Identifier.ToString());
_autoDesignManager.ChangeData(_set!, _designIndex, list);
}
break;
}
}
ImGui.TableNextColumn();
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), buttonSize, "Delete this restriction.", false, true))
{
list.RemoveAt(i);
_autoDesignManager.ChangeData(_set!, _designIndex, list);
}
ImGui.SameLine();
DrawLookup(predicate, buttonSize);
}
}
private void DrawLookup(IDesignPredicate predicate, Vector2 buttonSize)
{
ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.MagnifyingGlassChart.ToIconString(), buttonSize, string.Empty, false, true);
if (!ImGui.IsItemHovered())
return;
var designs = predicate.Get(_designs, _designFileSystem);
LookupTooltip(designs);
}
private void LookupTooltip(IEnumerable<Design> designs)
{
using var _ = ImRaii.Tooltip();
var tt = string.Join('\n', designs.Select(d => _designFileSystem.FindLeaf(d, out var l) ? l.FullName() : d.Name.Text).OrderBy(t => t));
ImGui.TextUnformatted(tt.Length == 0
? "Matches no currently existing designs."
: "Matches the following designs:");
ImGui.Separator();
ImGui.TextUnformatted(tt);
}
private void DrawNewButtons(List<IDesignPredicate> list)
{
ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X);
ImGui.InputTextWithHint("##newText", "Add New Restriction...", ref _newText, 128);
var spacing = ImGui.GetStyle().ItemInnerSpacing.X;
var invalid = _newText.Length == 0;
var buttonSize = new Vector2((ImGui.GetContentRegionAvail().X - 3 * spacing) / 4, 0);
var changed = ImGuiUtil.DrawDisabledButton("Starts With", buttonSize,
"Add a new condition that design paths must start with the given text.", invalid)
&& Add(new RandomPredicate.StartsWith(_newText));
ImGui.SameLine(0, spacing);
changed |= ImGuiUtil.DrawDisabledButton("Contains", buttonSize,
"Add a new condition that design paths, names or identifiers must contain the given text.", invalid)
&& Add(new RandomPredicate.Contains(_newText));
ImGui.SameLine(0, spacing);
changed |= ImGuiUtil.DrawDisabledButton("Has Tag", buttonSize,
"Add a new condition that the design must contain the given tag.", invalid)
&& Add(new RandomPredicate.Exact(RandomPredicate.Exact.Type.Tag, _newText));
ImGui.SameLine(0, spacing);
changed |= ImGuiUtil.DrawDisabledButton("Assigned Color", buttonSize,
"Add a new condition that the design must be assigned to the given color.", invalid)
&& Add(new RandomPredicate.Exact(RandomPredicate.Exact.Type.Color, _newText));
if (_randomDesignCombo.Draw(_newDesign, ImGui.GetContentRegionAvail().X - spacing - buttonSize.X))
_newDesign = _randomDesignCombo.CurrentSelection?.Item1 as Design;
ImGui.SameLine(0, spacing);
if (ImGuiUtil.DrawDisabledButton("Exact Design", buttonSize, "Add a single, specific design.", _newDesign == null))
{
Add(new RandomPredicate.Exact(RandomPredicate.Exact.Type.Identifier, _newDesign!.Identifier.ToString()));
changed = true;
_newDesign = null;
}
if (changed)
_autoDesignManager.ChangeData(_set!, _designIndex, list);
return;
bool Add(IDesignPredicate predicate)
{
list.Add(predicate);
return true;
}
}
private void DrawManualInput(IReadOnlyList<IDesignPredicate> list)
{
ImGui.Dummy(Vector2.Zero);
ImGui.Separator();
ImGui.Dummy(Vector2.Zero);
DrawTotalPreview(list);
var currentDefinition = RandomPredicate.GeneratePredicateString(list);
var definition = _newDefinition ?? currentDefinition;
definition = definition.Replace(";", ";\n\t").Replace("{", "{\n\t").Replace("}", "\n}");
var lines = definition.Count(c => c is '\n');
if (ImGui.InputTextMultiline("##definition", ref definition, 2000,
new Vector2(ImGui.GetContentRegionAvail().X, (lines + 1) * ImGui.GetTextLineHeight() + ImGui.GetFrameHeight()),
ImGuiInputTextFlags.CtrlEnterForNewLine))
_newDefinition = definition;
if (ImGui.IsItemDeactivatedAfterEdit() && _newDefinition != null && _newDefinition != currentDefinition)
{
var predicates = RandomPredicate.GeneratePredicates(_newDefinition.Replace("\n", string.Empty).Replace("\t", string.Empty));
_autoDesignManager.ChangeData(_set!, _designIndex, predicates);
_newDefinition = null;
}
if (ImGui.Button("Copy to Clipboard Without Line Breaks", new Vector2(ImGui.GetContentRegionAvail().X, 0)))
{
try
{
ImGui.SetClipboardText(currentDefinition);
}
catch
{
// ignored
}
}
}
private void DrawTotalPreview(IReadOnlyList<IDesignPredicate> list)
{
var designs = IDesignPredicate.Get(list, _designs, _designFileSystem).ToList();
var button = designs.Count > 0
? $"All Restrictions Combined Match {designs.Count} Designs"
: "None of the Restrictions Matches Any Designs";
ImGuiUtil.DrawDisabledButton(button, new Vector2(ImGui.GetContentRegionAvail().X, 0),
string.Empty, false, false);
if (ImGui.IsItemHovered())
LookupTooltip(designs);
}
private void DrawContent(RandomDesign random)
{
ImGui.SetCursorPosY(ImGui.GetCursorPosY() - ImGui.GetStyle().WindowPadding.Y + ImGuiHelpers.GlobalScale);
ImGui.Separator();
ImGui.Dummy(Vector2.Zero);
var list = random.Predicates.ToList();
if (list.Count == 0)
{
ImGui.TextUnformatted("No Restrictions Set. Selects among all existing Designs.");
}
else
{
ImGui.TextUnformatted("Select among designs...");
DrawTable(random, list);
}
ImGui.Dummy(Vector2.Zero);
ImGui.Separator();
ImGui.Dummy(Vector2.Zero);
DrawNewButtons(list);
DrawManualInput(list);
}
private void OnAutomationChange(AutomationChanged.Type type, AutoDesignSet? set, object? data)
{
if (set != _set || _set == null)
return;
switch (type)
{
case AutomationChanged.Type.DeletedSet:
case AutomationChanged.Type.DeletedDesign when data is int index && _designIndex == index:
Close();
break;
case AutomationChanged.Type.MovedDesign when data is (int from, int to):
if (_designIndex == from)
_designIndex = to;
else if (_designIndex < from && _designIndex > to)
_designIndex++;
else if (_designIndex > to && _designIndex < from)
_designIndex--;
break;
case AutomationChanged.Type.ChangedDesign when data is (int index, IDesignStandIn _, IDesignStandIn _) && index == _designIndex:
Close();
break;
}
}
}

View file

@ -2,6 +2,7 @@
using Dalamud.Interface.Utility;
using Glamourer.Automation;
using Glamourer.Designs;
using Glamourer.Designs.Special;
using Glamourer.Interop;
using Glamourer.Services;
using Glamourer.Unlocks;
@ -25,7 +26,8 @@ public class SetPanel(
CustomizeUnlockManager _customizeUnlocks,
CustomizeService _customizations,
IdentifierDrawer _identifierDrawer,
Configuration _config)
Configuration _config,
RandomRestrictionDrawer _randomDrawer)
{
private readonly JobGroupCombo _jobGroupCombo = new(_manager, _jobs, Glamourer.Log);
@ -115,6 +117,7 @@ public class SetPanel(
ImGui.Separator();
ImGui.Dummy(Vector2.Zero);
DrawDesignTable();
_randomDrawer.Draw();
}
@ -190,6 +193,7 @@ public class SetPanel(
ImGui.Selectable($"#{idx + 1:D2}");
DrawDragDrop(Selection, idx);
ImGui.TableNextColumn();
DrawRandomEditing(Selection, design, idx);
_designCombo.Draw(Selection, design, idx);
DrawDragDrop(Selection, idx);
if (singleRow)
@ -257,6 +261,15 @@ public class SetPanel(
}
}
private void DrawRandomEditing(AutoDesignSet set, AutoDesign design, int designIdx)
{
if (design.Design is not RandomDesign)
return;
_randomDrawer.DrawButton(set, designIdx);
ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X);
}
private void DrawWarnings(AutoDesign design)
{
if (design.Design is not DesignBase)
@ -266,7 +279,7 @@ public class SetPanel(
size.X += ImGuiHelpers.GlobalScale;
var (equipFlags, customizeFlags, _, _, _) = design.ApplyWhat();
var sb = new StringBuilder();
var sb = new StringBuilder();
var designData = design.Design.GetDesignData(default);
foreach (var slot in EquipSlotExtensions.EqdpSlots.Append(EquipSlot.MainHand).Append(EquipSlot.OffHand))
{

View file

@ -290,7 +290,7 @@ public class SetSelector : IDisposable
id = _actors.CreatePlayer(ByteString.FromSpanUnsafe("New Design"u8, true, false, true), ushort.MaxValue);
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), size,
$"Create a new Automatic Design Set for {id}. The associated player can be changed later.", !id.IsValid, true))
_manager.AddDesignSet("New Design", id);
_manager.AddDesignSet("New Automation Set", id);
}
private void DuplicateSetButton(Vector2 size)

View file

@ -3,6 +3,7 @@ using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Plugin.Services;
using Glamourer.Automation;
using Glamourer.Designs;
using Glamourer.Designs.Special;
using Glamourer.Gui;
using Glamourer.Interop;
using Glamourer.Interop.Penumbra;
@ -473,12 +474,7 @@ public class CommandService : IDisposable
_chat.Print(new SeStringBuilder()
.AddText(" 》 Clipboard as a single word will try to apply a design string currently in your clipboard.").BuiltString);
_chat.Print(new SeStringBuilder()
.AddText(" 》 ").AddYellow("Random").AddText(" supports the following restrictions:").BuiltString);
_chat.Print(new SeStringBuilder()
.AddText(" 》》》 ").AddYellow("Random").AddText(", choosing a random design out of all your designs.").BuiltString);
_chat.Print(new SeStringBuilder().AddText(" 》》》 ").AddYellow("Random:{List of [text] or /[text]}").AddText(", containing a list of restrictions within swirly braces, separated by semicolons.").BuiltString);
_chat.Print(new SeStringBuilder().AddText(" 》》》 ").AddYellow("Random:[text]").AddText(", choosing a random design where the path, name or identifier contains 'text' (no brackets).").BuiltString);
_chat.Print(new SeStringBuilder().AddText(" 》》》 ").AddYellow("Random:/[text]").AddText(", choosing a random design where the path starts with 'text' (no brackets).").BuiltString);
.AddText(" 》 ").AddYellow("Random").AddText(" supports many restrictions, see the Restriction Builder when adding a Random design to Automations for valid strings.").BuiltString);
_chat.Print(new SeStringBuilder()
.AddText(" 》 ").AddBlue("<Enable Mods>").AddText(" is optional and can be omitted (together with the ;), ").AddBlue("true")
.AddText(" or ").AddBlue("false").AddText(".").BuiltString);

View file

@ -149,6 +149,7 @@ public static class ServiceManagerA
.AddSingleton<DesignTab>()
.AddSingleton<QuickDesignCombo>()
.AddSingleton<LinkDesignCombo>()
.AddSingleton<RandomDesignCombo>()
.AddSingleton<SpecialDesignCombo>()
.AddSingleton<ModAssociationsTab>()
.AddSingleton<DesignDetailTab>()