Improve Category editor
This commit is contained in:
@@ -5,68 +5,54 @@ using KamiToolKit.Premade.Nodes;
|
|||||||
|
|
||||||
namespace AetherBags.Nodes.Configuration.Category;
|
namespace AetherBags.Nodes.Configuration.Category;
|
||||||
|
|
||||||
public class CategoryConfigurationNode : ConfigNode<CategoryWrapper>
|
public class CategoryConfigurationNode : ConfigNode<CategoryWrapper>
|
||||||
{
|
{
|
||||||
private readonly ScrollingListNode _categoryList;
|
|
||||||
private CategoryDefinitionConfigurationNode? _activeNode;
|
private CategoryDefinitionConfigurationNode? _activeNode;
|
||||||
|
|
||||||
public Action? OnCategoryChanged { get; set; }
|
public Action? OnCategoryChanged { get; set; }
|
||||||
|
|
||||||
public CategoryConfigurationNode()
|
public CategoryConfigurationNode()
|
||||||
{
|
{
|
||||||
_categoryList = new ScrollingListNode
|
|
||||||
{
|
|
||||||
AutoHideScrollBar = true,
|
|
||||||
};
|
|
||||||
_categoryList.FitContents = true;
|
|
||||||
_categoryList.AttachNode(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OptionChanged(CategoryWrapper? option)
|
protected override void OptionChanged(CategoryWrapper? option)
|
||||||
{
|
{
|
||||||
if (option?.CategoryDefinition is null)
|
if (option?.CategoryDefinition is null)
|
||||||
{
|
{
|
||||||
_categoryList.IsVisible = false;
|
if (_activeNode is not null)
|
||||||
|
{
|
||||||
|
_activeNode.IsVisible = false;
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_categoryList.IsVisible = true;
|
|
||||||
|
|
||||||
if (_activeNode is null)
|
if (_activeNode is null)
|
||||||
{
|
{
|
||||||
_activeNode = new CategoryDefinitionConfigurationNode(option.CategoryDefinition)
|
_activeNode = new CategoryDefinitionConfigurationNode
|
||||||
{
|
{
|
||||||
Size = _categoryList.Size,
|
OnLayoutChanged = RecalculateLayout,
|
||||||
OnLayoutChanged = UpdateScrollHeight,
|
|
||||||
OnCategoryPropertyChanged = OnCategoryChanged,
|
OnCategoryPropertyChanged = OnCategoryChanged,
|
||||||
};
|
};
|
||||||
_categoryList.AddNode(_activeNode);
|
_activeNode.AttachNode(this);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_activeNode.SetCategory(option.CategoryDefinition);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateScrollHeight();
|
_activeNode.IsVisible = true;
|
||||||
|
_activeNode.Size = Size;
|
||||||
|
_activeNode.SetCategory(option.CategoryDefinition);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateScrollHeight()
|
private void RecalculateLayout()
|
||||||
{
|
{
|
||||||
_categoryList.RecalculateLayout();
|
// Trigger parent layout update if needed
|
||||||
//_categoryList.ContentHeight = _categoryList.Height;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnSizeChanged()
|
protected override void OnSizeChanged()
|
||||||
{
|
{
|
||||||
base.OnSizeChanged();
|
base.OnSizeChanged();
|
||||||
_categoryList.Size = Size;
|
|
||||||
_categoryList.Width = Width;
|
|
||||||
|
|
||||||
foreach (var node in _categoryList.GetNodes<CategoryDefinitionConfigurationNode>())
|
if (_activeNode is not null)
|
||||||
{
|
{
|
||||||
node.Width = Width;
|
_activeNode.Size = Size;
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateScrollHeight();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using AetherBags.Configuration;
|
using AetherBags.Configuration;
|
||||||
using AetherBags.Inventory;
|
using AetherBags.Inventory;
|
||||||
@@ -15,520 +16,500 @@ using Action = System.Action;
|
|||||||
|
|
||||||
namespace AetherBags.Nodes.Configuration.Category;
|
namespace AetherBags.Nodes.Configuration.Category;
|
||||||
|
|
||||||
public sealed class CategoryDefinitionConfigurationNode : VerticalListNode
|
public sealed class CategoryDefinitionConfigurationNode : SimpleComponentNode
|
||||||
{
|
{
|
||||||
private readonly CheckboxNode _enabledCheckbox;
|
private static ExcelSheet<Item>? ItemSheet => Services.DataManager.GetExcelSheet<Item>();
|
||||||
private readonly CheckboxNode _pinnedCheckbox;
|
private static ExcelSheet<ItemUICategory>? UICategorySheet => Services.DataManager.GetExcelSheet<ItemUICategory>();
|
||||||
private readonly TextInputNode _nameInputNode;
|
|
||||||
private readonly TextInputNode _descriptionInputNode;
|
|
||||||
private readonly ColorInputRow _colorInputNode;
|
|
||||||
private readonly NumericInputNode _priorityInputNode;
|
|
||||||
private readonly NumericInputNode _orderInputNode;
|
|
||||||
|
|
||||||
private readonly CheckboxNode _levelEnabledCheckbox;
|
public Action? OnLayoutChanged { get; init; }
|
||||||
private readonly NumericInputNode _levelMinNode;
|
public Action? OnCategoryPropertyChanged { get; init; }
|
||||||
private readonly NumericInputNode _levelMaxNode;
|
|
||||||
|
|
||||||
private readonly CheckboxNode _itemLevelEnabledCheckbox;
|
private UserCategoryDefinition _categoryDefinition = new();
|
||||||
private readonly NumericInputNode _itemLevelMinNode;
|
|
||||||
private readonly NumericInputNode _itemLevelMaxNode;
|
|
||||||
|
|
||||||
private readonly CheckboxNode _vendorPriceEnabledCheckbox;
|
private readonly ScrollingAreaNode<TreeListNode> _scrollingArea;
|
||||||
private readonly NumericInputNode _vendorPriceMinNode;
|
private readonly BasicSettingsSection _basicSettings;
|
||||||
private readonly NumericInputNode _vendorPriceMaxNode;
|
private readonly RangeFiltersSection _rangeFilters;
|
||||||
|
private readonly StateFiltersSection _stateFilters;
|
||||||
|
private readonly ListFiltersSection _listFilters;
|
||||||
|
|
||||||
private readonly StateFilterRowNode _untradableFilter;
|
public CategoryDefinitionConfigurationNode()
|
||||||
private readonly StateFilterRowNode _uniqueFilter;
|
|
||||||
private readonly StateFilterRowNode _collectableFilter;
|
|
||||||
private readonly StateFilterRowNode _dyeableFilter;
|
|
||||||
private readonly StateFilterRowNode _repairableFilter;
|
|
||||||
private readonly StateFilterRowNode _hqFilter;
|
|
||||||
private readonly StateFilterRowNode _desynthFilter;
|
|
||||||
private readonly StateFilterRowNode _glamourFilter;
|
|
||||||
private readonly StateFilterRowNode _spiritbondFilter;
|
|
||||||
|
|
||||||
private readonly UintListEditorNode _allowedItemIdsEditor;
|
|
||||||
private readonly StringListEditorNode _allowedNamePatternsEditor;
|
|
||||||
private readonly UintListEditorNode _allowedUiCategoriesEditor;
|
|
||||||
private readonly RarityEditorNode _allowedRaritiesEditor;
|
|
||||||
|
|
||||||
private bool _isInitialized;
|
|
||||||
|
|
||||||
private static ExcelSheet<Item>? _sItemSheet;
|
|
||||||
private static ExcelSheet<ItemUICategory>? _sUICategorySheet;
|
|
||||||
|
|
||||||
public Action? OnLayoutChanged { get; set; }
|
|
||||||
|
|
||||||
public Action? OnCategoryPropertyChanged { get; set; }
|
|
||||||
|
|
||||||
private UserCategoryDefinition CategoryDefinition { get; set; }
|
|
||||||
|
|
||||||
public CategoryDefinitionConfigurationNode(UserCategoryDefinition categoryDefinition)
|
|
||||||
{
|
{
|
||||||
CategoryDefinition = categoryDefinition;
|
_scrollingArea = new ScrollingAreaNode<TreeListNode>
|
||||||
|
|
||||||
_sItemSheet ??= Services.DataManager.GetExcelSheet<Item>();
|
|
||||||
_sUICategorySheet ??= Services.DataManager.GetExcelSheet<ItemUICategory>();
|
|
||||||
|
|
||||||
FitContents = true;
|
|
||||||
ItemSpacing = 4.0f;
|
|
||||||
|
|
||||||
var catchAllWarningNode = new TextNode
|
|
||||||
{
|
{
|
||||||
Size = new Vector2(300, 40),
|
ContentHeight = 100.0f,
|
||||||
TextFlags = TextFlags.MultiLine | TextFlags.AutoAdjustNodeSize,
|
AutoHideScrollBar = true,
|
||||||
SeString = new SeStringBuilder().Append(" Warning: No rules configured\nThis category won't match anything!").ToReadOnlySeString(),
|
|
||||||
TextColor = ColorHelper.GetColor(17),
|
|
||||||
LineSpacing = 20,
|
|
||||||
IsVisible = UserCategoryMatcher.IsCatchAll(CategoryDefinition),
|
|
||||||
};
|
};
|
||||||
AddNode(catchAllWarningNode);
|
_scrollingArea.AttachNode(this);
|
||||||
|
|
||||||
AddNode(CreateSectionHeader("Basic Settings"));
|
_scrollingArea.ContentNode.OnLayoutUpdate = newHeight =>
|
||||||
|
{
|
||||||
|
_scrollingArea.ContentHeight = newHeight;
|
||||||
|
};
|
||||||
|
|
||||||
|
_scrollingArea.ContentNode.CategoryVerticalSpacing = 4.0f;
|
||||||
|
|
||||||
|
var treeListNode = _scrollingArea.ContentAreaNode;
|
||||||
|
|
||||||
|
_basicSettings = new BasicSettingsSection(() => _categoryDefinition)
|
||||||
|
{
|
||||||
|
String = "Basic Settings",
|
||||||
|
IsCollapsed = false,
|
||||||
|
OnPropertyChanged = () =>
|
||||||
|
{
|
||||||
|
NotifyChanged();
|
||||||
|
NotifyCategoryPropertyChanged();
|
||||||
|
},
|
||||||
|
OnValueChanged = NotifyChanged,
|
||||||
|
};
|
||||||
|
_basicSettings.OnToggle = _ => HandleLayoutChange();
|
||||||
|
treeListNode.AddCategoryNode(_basicSettings);
|
||||||
|
|
||||||
|
_rangeFilters = new RangeFiltersSection(() => _categoryDefinition)
|
||||||
|
{
|
||||||
|
String = "Range Filters",
|
||||||
|
IsCollapsed = true,
|
||||||
|
OnValueChanged = NotifyChanged,
|
||||||
|
};
|
||||||
|
_rangeFilters.OnToggle = _ => HandleLayoutChange();
|
||||||
|
treeListNode.AddCategoryNode(_rangeFilters);
|
||||||
|
|
||||||
|
_stateFilters = new StateFiltersSection(() => _categoryDefinition)
|
||||||
|
{
|
||||||
|
String = "State Filters",
|
||||||
|
IsCollapsed = true,
|
||||||
|
OnValueChanged = NotifyChanged,
|
||||||
|
};
|
||||||
|
_stateFilters.OnToggle = _ => HandleLayoutChange();
|
||||||
|
treeListNode.AddCategoryNode(_stateFilters);
|
||||||
|
|
||||||
|
_listFilters = new ListFiltersSection(() => _categoryDefinition)
|
||||||
|
{
|
||||||
|
String = "List Filters",
|
||||||
|
IsCollapsed = true,
|
||||||
|
OnValueChanged = NotifyChanged,
|
||||||
|
OnListChanged = HandleListChanged,
|
||||||
|
};
|
||||||
|
_listFilters.OnToggle = _ => HandleLayoutChange();
|
||||||
|
treeListNode.AddCategoryNode(_listFilters);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnSizeChanged()
|
||||||
|
{
|
||||||
|
base.OnSizeChanged();
|
||||||
|
|
||||||
|
_scrollingArea.Size = Size;
|
||||||
|
|
||||||
|
foreach (var categoryNode in _scrollingArea.ContentNode.CategoryNodes)
|
||||||
|
{
|
||||||
|
categoryNode.Width = Width - 16.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
_scrollingArea.ContentNode.RefreshLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetCategory(UserCategoryDefinition newCategory)
|
||||||
|
{
|
||||||
|
_categoryDefinition = newCategory;
|
||||||
|
RefreshAllValues();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshAllValues()
|
||||||
|
{
|
||||||
|
_basicSettings.Refresh();
|
||||||
|
_rangeFilters.Refresh();
|
||||||
|
_stateFilters.Refresh();
|
||||||
|
_listFilters.Refresh();
|
||||||
|
|
||||||
|
HandleLayoutChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleListChanged()
|
||||||
|
{
|
||||||
|
NotifyChanged();
|
||||||
|
HandleLayoutChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleLayoutChange()
|
||||||
|
{
|
||||||
|
_scrollingArea.ContentNode.RefreshLayout();
|
||||||
|
OnLayoutChanged?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void NotifyChanged() => InventoryOrchestrator.RefreshAll(updateMaps: true);
|
||||||
|
|
||||||
|
private void NotifyCategoryPropertyChanged() => OnCategoryPropertyChanged?.Invoke();
|
||||||
|
|
||||||
|
public static string ResolveItemName(uint itemId) => ItemSheet?.GetRow(itemId).Name.ToString() ?? "Unknown";
|
||||||
|
|
||||||
|
public static string ResolveUiCategoryName(uint categoryId) => UICategorySheet?.GetRow(categoryId).Name.ToString() ?? "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class ConfigurationSection : TreeListCategoryNode
|
||||||
|
{
|
||||||
|
private readonly Func<UserCategoryDefinition> _getCategoryDefinition;
|
||||||
|
|
||||||
|
public Action? OnValueChanged { get; init; }
|
||||||
|
|
||||||
|
protected UserCategoryDefinition CategoryDefinition => _getCategoryDefinition();
|
||||||
|
|
||||||
|
protected ConfigurationSection(Func<UserCategoryDefinition> getCategoryDefinition)
|
||||||
|
{
|
||||||
|
_getCategoryDefinition = getCategoryDefinition;
|
||||||
|
VerticalPadding = 4.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static LabelTextNode CreateLabel(string text) => new()
|
||||||
|
{
|
||||||
|
TextFlags = TextFlags.AutoAdjustNodeSize,
|
||||||
|
Size = new Vector2(80, 20),
|
||||||
|
String = text,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class BasicSettingsSection : ConfigurationSection
|
||||||
|
{
|
||||||
|
public Action? OnPropertyChanged { get; init; }
|
||||||
|
|
||||||
|
private CheckboxNode? _enabledCheckbox;
|
||||||
|
private CheckboxNode? _pinnedCheckbox;
|
||||||
|
private TextInputNode? _nameInput;
|
||||||
|
private TextInputNode? _descriptionInput;
|
||||||
|
private ColorInputRow? _colorInput;
|
||||||
|
private NumericInputNode? _priorityInput;
|
||||||
|
private NumericInputNode? _orderInput;
|
||||||
|
|
||||||
|
private bool _initialized;
|
||||||
|
|
||||||
|
public BasicSettingsSection(Func<UserCategoryDefinition> getCategoryDefinition)
|
||||||
|
: base(getCategoryDefinition)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureInitialized()
|
||||||
|
{
|
||||||
|
if (_initialized) return;
|
||||||
|
_initialized = true;
|
||||||
|
|
||||||
_enabledCheckbox = new CheckboxNode
|
_enabledCheckbox = new CheckboxNode
|
||||||
{
|
{
|
||||||
Size = new Vector2(200, 20),
|
Size = new Vector2(Width, 20),
|
||||||
String = "Enabled",
|
String = "Enabled",
|
||||||
IsChecked = CategoryDefinition.Enabled,
|
|
||||||
OnClick = isChecked =>
|
OnClick = isChecked =>
|
||||||
{
|
{
|
||||||
CategoryDefinition.Enabled = isChecked;
|
CategoryDefinition.Enabled = isChecked;
|
||||||
NotifyChanged();
|
OnPropertyChanged?.Invoke();
|
||||||
NotifyCategoryPropertyChanged();
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
AddNode(_enabledCheckbox);
|
AddNode(_enabledCheckbox);
|
||||||
|
|
||||||
_pinnedCheckbox = new CheckboxNode
|
_pinnedCheckbox = new CheckboxNode
|
||||||
{
|
{
|
||||||
Size = new Vector2(200, 20),
|
Size = new Vector2(Width, 20),
|
||||||
String = "Pinned",
|
String = "Pinned",
|
||||||
IsChecked = CategoryDefinition.Pinned,
|
|
||||||
OnClick = isChecked =>
|
OnClick = isChecked =>
|
||||||
{
|
{
|
||||||
CategoryDefinition.Pinned = isChecked;
|
CategoryDefinition.Pinned = isChecked;
|
||||||
NotifyChanged();
|
OnPropertyChanged?.Invoke();
|
||||||
NotifyCategoryPropertyChanged();
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
AddNode(_pinnedCheckbox);
|
AddNode(_pinnedCheckbox);
|
||||||
|
|
||||||
AddNode(new LabelTextNode
|
AddNode(CreateLabel("Name: "));
|
||||||
{
|
_nameInput = new TextInputNode
|
||||||
TextFlags = TextFlags.AutoAdjustNodeSize,
|
|
||||||
Size = new Vector2(80, 20),
|
|
||||||
String = "Name:"
|
|
||||||
});
|
|
||||||
_nameInputNode = new TextInputNode
|
|
||||||
{
|
{
|
||||||
Size = new Vector2(250, 28),
|
Size = new Vector2(250, 28),
|
||||||
String = CategoryDefinition.Name,
|
PlaceholderString = "Category Name",
|
||||||
PlaceholderString = CategoryDefinition.Name.IsNullOrWhitespace() ? "Category Name" : "",
|
OnInputReceived = input =>
|
||||||
OnInputReceived = name =>
|
|
||||||
{
|
{
|
||||||
CategoryDefinition.Name = name.ExtractText();
|
CategoryDefinition.Name = input.ExtractText();
|
||||||
NotifyChanged();
|
OnPropertyChanged?.Invoke();
|
||||||
NotifyCategoryPropertyChanged();
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
AddNode(_nameInputNode);
|
AddNode(_nameInput);
|
||||||
|
|
||||||
AddNode(new LabelTextNode
|
AddNode(CreateLabel("Description:"));
|
||||||
{
|
_descriptionInput = new TextInputNode
|
||||||
TextFlags = TextFlags.AutoAdjustNodeSize,
|
|
||||||
Size = new Vector2(80, 20),
|
|
||||||
String = "Description:"
|
|
||||||
});
|
|
||||||
_descriptionInputNode = new TextInputNode
|
|
||||||
{
|
{
|
||||||
Size = new Vector2(250, 28),
|
Size = new Vector2(250, 28),
|
||||||
String = CategoryDefinition.Description,
|
PlaceholderString = "Optional description",
|
||||||
PlaceholderString = CategoryDefinition.Description.IsNullOrWhitespace() ? "Optional description" : "",
|
OnInputReceived = input =>
|
||||||
OnInputReceived = desc =>
|
|
||||||
{
|
{
|
||||||
CategoryDefinition.Description = desc.ExtractText();
|
CategoryDefinition.Description = input.ExtractText();
|
||||||
NotifyChanged();
|
OnValueChanged?.Invoke();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
AddNode(_descriptionInputNode);
|
AddNode(_descriptionInput);
|
||||||
|
|
||||||
_colorInputNode = new ColorInputRow
|
_colorInput = new ColorInputRow
|
||||||
{
|
{
|
||||||
Label = "Color",
|
Label = "Color",
|
||||||
Size = new Vector2(300, 28),
|
Size = new Vector2(300, 28),
|
||||||
CurrentColor = CategoryDefinition.Color,
|
CurrentColor = new UserCategoryDefinition().Color,
|
||||||
DefaultColor = new UserCategoryDefinition().Color,
|
DefaultColor = new UserCategoryDefinition().Color,
|
||||||
OnColorConfirmed = color =>
|
OnColorConfirmed = c => { CategoryDefinition.Color = c; OnValueChanged?.Invoke(); },
|
||||||
{
|
OnColorCanceled = c => { CategoryDefinition.Color = c; OnValueChanged?.Invoke(); },
|
||||||
CategoryDefinition.Color = color;
|
OnColorPreviewed = c => { CategoryDefinition.Color = c; OnValueChanged?.Invoke(); },
|
||||||
NotifyChanged();
|
|
||||||
},
|
|
||||||
OnColorCanceled = color =>
|
|
||||||
{
|
|
||||||
CategoryDefinition.Color = color;
|
|
||||||
NotifyChanged();
|
|
||||||
},
|
|
||||||
OnColorPreviewed = color =>
|
|
||||||
{
|
|
||||||
CategoryDefinition.Color = color;
|
|
||||||
NotifyChanged();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
AddNode(_colorInputNode);
|
AddNode(_colorInput);
|
||||||
|
|
||||||
AddNode(new LabelTextNode
|
AddNode(CreateLabel("Priority:"));
|
||||||
{
|
_priorityInput = new NumericInputNode
|
||||||
TextFlags = TextFlags.AutoAdjustNodeSize,
|
|
||||||
Size = new Vector2(80, 20),
|
|
||||||
String = "Priority:"
|
|
||||||
});
|
|
||||||
_priorityInputNode = new NumericInputNode
|
|
||||||
{
|
{
|
||||||
Size = new Vector2(120, 28),
|
Size = new Vector2(120, 28),
|
||||||
Min = 0,
|
Min = 0,
|
||||||
Max = 1000,
|
Max = 1000,
|
||||||
Step = 1,
|
Step = 1,
|
||||||
Value = CategoryDefinition.Priority,
|
|
||||||
OnValueUpdate = val =>
|
OnValueUpdate = val =>
|
||||||
{
|
{
|
||||||
CategoryDefinition.Priority = val;
|
CategoryDefinition.Priority = val;
|
||||||
NotifyChanged();
|
OnValueChanged?.Invoke();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
AddNode(_priorityInputNode);
|
AddNode(_priorityInput);
|
||||||
|
|
||||||
AddNode(new LabelTextNode { TextFlags = TextFlags.AutoAdjustNodeSize, Size = new Vector2(80, 20), String = "Order:" });
|
AddNode(CreateLabel("Order: "));
|
||||||
_orderInputNode = new NumericInputNode
|
_orderInput = new NumericInputNode
|
||||||
{
|
{
|
||||||
Size = new Vector2(120, 28),
|
Size = new Vector2(120, 28),
|
||||||
Min = 0,
|
Min = 0,
|
||||||
Max = 9999,
|
Max = 9999,
|
||||||
Step = 1,
|
Step = 1,
|
||||||
Value = CategoryDefinition.Order,
|
|
||||||
OnValueUpdate = val =>
|
OnValueUpdate = val =>
|
||||||
{
|
{
|
||||||
CategoryDefinition.Order = val;
|
CategoryDefinition.Order = val;
|
||||||
NotifyChanged();
|
OnPropertyChanged?.Invoke();
|
||||||
NotifyCategoryPropertyChanged();
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
AddNode(_orderInputNode);
|
AddNode(_orderInput);
|
||||||
|
|
||||||
AddNode(CreateSectionHeader("Range Filters"));
|
RecalculateLayout();
|
||||||
|
}
|
||||||
|
|
||||||
(_levelEnabledCheckbox, _levelMinNode, _levelMaxNode) = CreateRangeFilter(
|
public void Refresh()
|
||||||
"Level",
|
{
|
||||||
CategoryDefinition.Rules.Level,
|
EnsureInitialized();
|
||||||
0, 200,
|
|
||||||
(enabled, min, max) =>
|
_enabledCheckbox!.IsChecked = CategoryDefinition.Enabled;
|
||||||
|
_pinnedCheckbox!.IsChecked = CategoryDefinition.Pinned;
|
||||||
|
_nameInput!.String = CategoryDefinition.Name;
|
||||||
|
_nameInput.PlaceholderString = CategoryDefinition.Name.IsNullOrWhitespace() ? "Category Name" : "";
|
||||||
|
_descriptionInput!.String = CategoryDefinition.Description;
|
||||||
|
_descriptionInput.PlaceholderString = CategoryDefinition.Description.IsNullOrWhitespace() ? "Optional description" : "";
|
||||||
|
_colorInput!.CurrentColor = CategoryDefinition.Color;
|
||||||
|
_priorityInput!.Value = CategoryDefinition.Priority;
|
||||||
|
_orderInput!.Value = CategoryDefinition.Order;
|
||||||
|
|
||||||
|
RecalculateLayout();
|
||||||
|
ParentTreeListNode?.RefreshLayout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class RangeFiltersSection : ConfigurationSection
|
||||||
|
{
|
||||||
|
private RangeFilterRow? _levelFilter;
|
||||||
|
private RangeFilterRow? _itemLevelFilter;
|
||||||
|
private RangeFilterRowUint? _vendorPriceFilter;
|
||||||
|
|
||||||
|
private bool _initialized;
|
||||||
|
|
||||||
|
public RangeFiltersSection(Func<UserCategoryDefinition> getCategoryDefinition)
|
||||||
|
: base(getCategoryDefinition)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureInitialized()
|
||||||
|
{
|
||||||
|
if (_initialized) return;
|
||||||
|
_initialized = true;
|
||||||
|
|
||||||
|
_levelFilter = new RangeFilterRow
|
||||||
|
{
|
||||||
|
Label = "Level",
|
||||||
|
MinBound = 0,
|
||||||
|
MaxBound = 200,
|
||||||
|
OnFilterChanged = (enabled, min, max) =>
|
||||||
{
|
{
|
||||||
CategoryDefinition.Rules.Level.Enabled = enabled;
|
CategoryDefinition.Rules.Level.Enabled = enabled;
|
||||||
CategoryDefinition.Rules.Level.Min = min;
|
CategoryDefinition.Rules.Level.Min = min;
|
||||||
CategoryDefinition.Rules.Level.Max = max;
|
CategoryDefinition.Rules.Level.Max = max;
|
||||||
NotifyChanged();
|
OnValueChanged?.Invoke();
|
||||||
}
|
},
|
||||||
);
|
};
|
||||||
|
AddNode(_levelFilter);
|
||||||
|
|
||||||
(_itemLevelEnabledCheckbox, _itemLevelMinNode, _itemLevelMaxNode) = CreateRangeFilter(
|
_itemLevelFilter = new RangeFilterRow
|
||||||
"Item Level",
|
{
|
||||||
CategoryDefinition.Rules.ItemLevel,
|
Label = "Item Level",
|
||||||
0, 2000,
|
MinBound = 0,
|
||||||
(enabled, min, max) =>
|
MaxBound = 2000,
|
||||||
|
OnFilterChanged = (enabled, min, max) =>
|
||||||
{
|
{
|
||||||
CategoryDefinition.Rules.ItemLevel.Enabled = enabled;
|
CategoryDefinition.Rules.ItemLevel.Enabled = enabled;
|
||||||
CategoryDefinition.Rules.ItemLevel.Min = min;
|
CategoryDefinition.Rules.ItemLevel.Min = min;
|
||||||
CategoryDefinition.Rules.ItemLevel.Max = max;
|
CategoryDefinition.Rules.ItemLevel.Max = max;
|
||||||
NotifyChanged();
|
OnValueChanged?.Invoke();
|
||||||
}
|
},
|
||||||
);
|
|
||||||
|
|
||||||
(_vendorPriceEnabledCheckbox, _vendorPriceMinNode, _vendorPriceMaxNode) = CreateRangeFilterUint(
|
|
||||||
"Vendor Price",
|
|
||||||
CategoryDefinition.Rules.VendorPrice,
|
|
||||||
0, 9_999_999
|
|
||||||
);
|
|
||||||
|
|
||||||
AddNode(CreateSectionHeader("State Filters"));
|
|
||||||
|
|
||||||
_untradableFilter = new StateFilterRowNode("Untradable", CategoryDefinition.Rules.Untradable, NotifyChanged);
|
|
||||||
AddNode(_untradableFilter);
|
|
||||||
|
|
||||||
_uniqueFilter = new StateFilterRowNode("Unique", CategoryDefinition.Rules.Unique, NotifyChanged);
|
|
||||||
AddNode(_uniqueFilter);
|
|
||||||
|
|
||||||
_collectableFilter = new StateFilterRowNode("Collectable", CategoryDefinition.Rules.Collectable, NotifyChanged);
|
|
||||||
AddNode(_collectableFilter);
|
|
||||||
|
|
||||||
_dyeableFilter = new StateFilterRowNode("Dyeable", CategoryDefinition.Rules.Dyeable, NotifyChanged);
|
|
||||||
AddNode(_dyeableFilter);
|
|
||||||
|
|
||||||
_repairableFilter = new StateFilterRowNode("Repairable", CategoryDefinition.Rules.Repairable, NotifyChanged);
|
|
||||||
AddNode(_repairableFilter);
|
|
||||||
|
|
||||||
_hqFilter = new StateFilterRowNode("High Quality", CategoryDefinition.Rules.HighQuality, NotifyChanged);
|
|
||||||
AddNode(_hqFilter);
|
|
||||||
|
|
||||||
_desynthFilter = new StateFilterRowNode("Desynthesizable", CategoryDefinition.Rules.Desynthesizable, NotifyChanged);
|
|
||||||
AddNode(_desynthFilter);
|
|
||||||
|
|
||||||
_glamourFilter = new StateFilterRowNode("Glamourable", CategoryDefinition.Rules.Glamourable, NotifyChanged);
|
|
||||||
AddNode(_glamourFilter);
|
|
||||||
|
|
||||||
_spiritbondFilter = new StateFilterRowNode("Spiritbonded", CategoryDefinition.Rules.FullySpiritbonded, NotifyChanged);
|
|
||||||
AddNode(_spiritbondFilter);
|
|
||||||
|
|
||||||
AddNode(CreateSectionHeader("List Filters"));
|
|
||||||
|
|
||||||
_allowedItemIdsEditor = new UintListEditorNode(
|
|
||||||
"Allowed Item IDs:",
|
|
||||||
CategoryDefinition.Rules.AllowedItemIds,
|
|
||||||
OnListChanged,
|
|
||||||
ResolveItemName
|
|
||||||
);
|
|
||||||
AddNode(_allowedItemIdsEditor);
|
|
||||||
|
|
||||||
_allowedNamePatternsEditor = new StringListEditorNode(
|
|
||||||
"Name Patterns (Regex):",
|
|
||||||
CategoryDefinition.Rules.AllowedItemNamePatterns,
|
|
||||||
OnListChanged
|
|
||||||
);
|
|
||||||
AddNode(_allowedNamePatternsEditor);
|
|
||||||
|
|
||||||
_allowedUiCategoriesEditor = new UintListEditorNode(
|
|
||||||
"UI Categories:",
|
|
||||||
CategoryDefinition.Rules.AllowedUiCategoryIds,
|
|
||||||
OnListChanged,
|
|
||||||
ResolveUiCategoryName
|
|
||||||
);
|
|
||||||
AddNode(_allowedUiCategoriesEditor);
|
|
||||||
|
|
||||||
_allowedRaritiesEditor = new RarityEditorNode(
|
|
||||||
CategoryDefinition.Rules.AllowedRarities,
|
|
||||||
NotifyChanged
|
|
||||||
);
|
|
||||||
AddNode(_allowedRaritiesEditor);
|
|
||||||
|
|
||||||
_isInitialized = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnListChanged()
|
|
||||||
{
|
|
||||||
NotifyChanged();
|
|
||||||
RecalculateLayout();
|
|
||||||
OnLayoutChanged?.Invoke();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string ResolveItemName(uint itemId)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var item = _sItemSheet?.GetRow(itemId);
|
|
||||||
return item?.Name.ToString() ?? "Unknown";
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return "Unknown";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string ResolveUiCategoryName(uint categoryId)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var category = _sUICategorySheet?.GetRow(categoryId);
|
|
||||||
return category?.Name.ToString() ?? "Unknown";
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return "Unknown";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void NotifyChanged()
|
|
||||||
{
|
|
||||||
InventoryOrchestrator.RefreshAll(updateMaps: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void NotifyCategoryPropertyChanged()
|
|
||||||
{
|
|
||||||
OnCategoryPropertyChanged?.Invoke();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static LabelTextNode CreateSectionHeader(string text)
|
|
||||||
{
|
|
||||||
return new LabelTextNode
|
|
||||||
{
|
|
||||||
Size = new Vector2(300, 22),
|
|
||||||
String = text,
|
|
||||||
TextColor = ColorHelper.GetColor(2),
|
|
||||||
TextOutlineColor = ColorHelper.GetColor(0),
|
|
||||||
};
|
};
|
||||||
}
|
AddNode(_itemLevelFilter);
|
||||||
|
|
||||||
private (CheckboxNode enabled, NumericInputNode min, NumericInputNode max) CreateRangeFilter(
|
_vendorPriceFilter = new RangeFilterRowUint
|
||||||
string label,
|
|
||||||
RangeFilter<int> filter,
|
|
||||||
int minBound,
|
|
||||||
int maxBound,
|
|
||||||
Action<bool, int, int> onUpdate)
|
|
||||||
{
|
|
||||||
var enabledCheckbox = new CheckboxNode
|
|
||||||
{
|
{
|
||||||
Size = new Vector2(200, 20),
|
Label = "Vendor Price",
|
||||||
String = $"{label} Filter",
|
MinBound = 0,
|
||||||
IsChecked = filter.Enabled,
|
MaxBound = 9_999_999,
|
||||||
|
OnFilterChanged = (enabled, min, max) =>
|
||||||
|
{
|
||||||
|
CategoryDefinition.Rules.VendorPrice.Enabled = enabled;
|
||||||
|
CategoryDefinition.Rules.VendorPrice.Min = min;
|
||||||
|
CategoryDefinition.Rules.VendorPrice.Max = max;
|
||||||
|
OnValueChanged?.Invoke();
|
||||||
|
},
|
||||||
};
|
};
|
||||||
AddNode(enabledCheckbox);
|
AddNode(_vendorPriceFilter);
|
||||||
|
|
||||||
var minNode = new NumericInputNode
|
|
||||||
{
|
|
||||||
Size = new Vector2(120, 28),
|
|
||||||
Min = minBound,
|
|
||||||
Max = maxBound,
|
|
||||||
Value = filter.Min,
|
|
||||||
IsEnabled = filter.Enabled,
|
|
||||||
};
|
|
||||||
|
|
||||||
var maxNode = new NumericInputNode
|
|
||||||
{
|
|
||||||
Size = new Vector2(120, 28),
|
|
||||||
Min = minBound,
|
|
||||||
Max = maxBound,
|
|
||||||
Value = filter.Max,
|
|
||||||
IsEnabled = filter.Enabled,
|
|
||||||
};
|
|
||||||
|
|
||||||
var rangeRow = new HorizontalListNode { Size = new Vector2(300, 28), ItemSpacing = 8.0f };
|
|
||||||
rangeRow.AddNode(new LabelTextNode { TextFlags = TextFlags.AutoAdjustNodeSize, Size = new Vector2(30, 28), String = "Min:" });
|
|
||||||
rangeRow.AddNode(minNode);
|
|
||||||
rangeRow.AddNode(new LabelTextNode { TextFlags = TextFlags.AutoAdjustNodeSize, Size = new Vector2(30, 28), String = "Max:" });
|
|
||||||
rangeRow.AddNode(maxNode);
|
|
||||||
AddNode(rangeRow);
|
|
||||||
|
|
||||||
enabledCheckbox.OnClick = isChecked =>
|
|
||||||
{
|
|
||||||
minNode.IsEnabled = isChecked;
|
|
||||||
maxNode.IsEnabled = isChecked;
|
|
||||||
onUpdate(isChecked, minNode.Value, maxNode.Value);
|
|
||||||
};
|
|
||||||
|
|
||||||
minNode.OnValueUpdate = val => onUpdate(enabledCheckbox.IsChecked, val, maxNode.Value);
|
|
||||||
maxNode.OnValueUpdate = val => onUpdate(enabledCheckbox.IsChecked, minNode.Value, val);
|
|
||||||
|
|
||||||
return (enabledCheckbox, minNode, maxNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
private (CheckboxNode enabled, NumericInputNode min, NumericInputNode max) CreateRangeFilterUint(
|
|
||||||
string label,
|
|
||||||
RangeFilter<uint> filter,
|
|
||||||
int minBound,
|
|
||||||
int maxBound)
|
|
||||||
{
|
|
||||||
var enabledCheckbox = new CheckboxNode
|
|
||||||
{
|
|
||||||
Size = new Vector2(200, 20),
|
|
||||||
String = $"{label} Filter",
|
|
||||||
IsChecked = filter.Enabled,
|
|
||||||
};
|
|
||||||
AddNode(enabledCheckbox);
|
|
||||||
|
|
||||||
var minNode = new NumericInputNode
|
|
||||||
{
|
|
||||||
Size = new Vector2(120, 28),
|
|
||||||
Min = minBound,
|
|
||||||
Max = maxBound,
|
|
||||||
Value = (int)filter.Min,
|
|
||||||
IsEnabled = filter.Enabled,
|
|
||||||
};
|
|
||||||
|
|
||||||
var maxNode = new NumericInputNode
|
|
||||||
{
|
|
||||||
Size = new Vector2(120, 28),
|
|
||||||
Min = minBound,
|
|
||||||
Max = maxBound,
|
|
||||||
Value = (int)Math.Min(filter.Max, maxBound),
|
|
||||||
IsEnabled = filter.Enabled,
|
|
||||||
};
|
|
||||||
|
|
||||||
var rangeRow = new HorizontalListNode { Size = new Vector2(300, 28), ItemSpacing = 8.0f };
|
|
||||||
rangeRow.AddNode(new LabelTextNode { TextFlags = TextFlags.AutoAdjustNodeSize, Size = new Vector2(30, 28), String = "Min:" });
|
|
||||||
rangeRow.AddNode(minNode);
|
|
||||||
rangeRow.AddNode(new LabelTextNode { TextFlags = TextFlags.AutoAdjustNodeSize, Size = new Vector2(30, 28), String = "Max:" });
|
|
||||||
rangeRow.AddNode(maxNode);
|
|
||||||
AddNode(rangeRow);
|
|
||||||
|
|
||||||
enabledCheckbox.OnClick = isChecked =>
|
|
||||||
{
|
|
||||||
minNode.IsEnabled = isChecked;
|
|
||||||
maxNode.IsEnabled = isChecked;
|
|
||||||
CategoryDefinition.Rules.VendorPrice.Enabled = isChecked;
|
|
||||||
NotifyChanged();
|
|
||||||
};
|
|
||||||
|
|
||||||
minNode.OnValueUpdate = value =>
|
|
||||||
{
|
|
||||||
CategoryDefinition.Rules.VendorPrice.Min = (uint)value;
|
|
||||||
NotifyChanged();
|
|
||||||
};
|
|
||||||
|
|
||||||
maxNode.OnValueUpdate = value =>
|
|
||||||
{
|
|
||||||
CategoryDefinition.Rules.VendorPrice.Max = (uint)value;
|
|
||||||
NotifyChanged();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (enabledCheckbox, minNode, maxNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetCategory(UserCategoryDefinition newCategory)
|
|
||||||
{
|
|
||||||
CategoryDefinition = newCategory;
|
|
||||||
RefreshValues();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RefreshValues()
|
|
||||||
{
|
|
||||||
if (! _isInitialized) return;
|
|
||||||
|
|
||||||
_enabledCheckbox.IsChecked = CategoryDefinition.Enabled;
|
|
||||||
_pinnedCheckbox.IsChecked = CategoryDefinition.Pinned;
|
|
||||||
_colorInputNode.CurrentColor = CategoryDefinition.Color;
|
|
||||||
_nameInputNode.String = CategoryDefinition.Name;
|
|
||||||
_descriptionInputNode.String = CategoryDefinition.Description;
|
|
||||||
_priorityInputNode.Value = CategoryDefinition.Priority;
|
|
||||||
_orderInputNode.Value = CategoryDefinition.Order;
|
|
||||||
|
|
||||||
RefreshRangeFilter(_levelEnabledCheckbox, _levelMinNode, _levelMaxNode, CategoryDefinition.Rules.Level);
|
|
||||||
RefreshRangeFilter(_itemLevelEnabledCheckbox, _itemLevelMinNode, _itemLevelMaxNode, CategoryDefinition.Rules.ItemLevel);
|
|
||||||
|
|
||||||
_vendorPriceEnabledCheckbox.IsChecked = CategoryDefinition.Rules.VendorPrice.Enabled;
|
|
||||||
_vendorPriceMinNode.Value = (int)CategoryDefinition.Rules.VendorPrice.Min;
|
|
||||||
_vendorPriceMaxNode.Value = (int)Math.Min(CategoryDefinition.Rules.VendorPrice.Max, int.MaxValue);
|
|
||||||
_vendorPriceMinNode.IsEnabled = CategoryDefinition.Rules.VendorPrice.Enabled;
|
|
||||||
_vendorPriceMaxNode.IsEnabled = CategoryDefinition.Rules.VendorPrice.Enabled;
|
|
||||||
|
|
||||||
_untradableFilter.SetState(CategoryDefinition.Rules.Untradable);
|
|
||||||
_uniqueFilter.SetState(CategoryDefinition.Rules.Unique);
|
|
||||||
_collectableFilter.SetState(CategoryDefinition.Rules.Collectable);
|
|
||||||
_dyeableFilter.SetState(CategoryDefinition.Rules.Dyeable);
|
|
||||||
_repairableFilter.SetState(CategoryDefinition.Rules.Repairable);
|
|
||||||
|
|
||||||
_allowedItemIdsEditor.SetList(CategoryDefinition.Rules.AllowedItemIds);
|
|
||||||
_allowedNamePatternsEditor.SetList(CategoryDefinition.Rules.AllowedItemNamePatterns);
|
|
||||||
_allowedUiCategoriesEditor.SetList(CategoryDefinition.Rules.AllowedUiCategoryIds);
|
|
||||||
_allowedRaritiesEditor.SetList(CategoryDefinition.Rules.AllowedRarities);
|
|
||||||
|
|
||||||
RecalculateLayout();
|
RecalculateLayout();
|
||||||
OnLayoutChanged?.Invoke();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void RefreshRangeFilter(CheckboxNode enabled, NumericInputNode min, NumericInputNode max, RangeFilter<int> filter)
|
public void Refresh()
|
||||||
{
|
{
|
||||||
enabled.IsChecked = filter.Enabled;
|
EnsureInitialized();
|
||||||
min.Value = filter.Min;
|
|
||||||
max.Value = filter.Max;
|
_levelFilter!.SetFilter(CategoryDefinition.Rules.Level);
|
||||||
min.IsEnabled = filter.Enabled;
|
_itemLevelFilter!.SetFilter(CategoryDefinition.Rules.ItemLevel);
|
||||||
max.IsEnabled = filter.Enabled;
|
_vendorPriceFilter!.SetFilter(CategoryDefinition.Rules.VendorPrice);
|
||||||
|
|
||||||
|
RecalculateLayout();
|
||||||
|
ParentTreeListNode?.RefreshLayout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class StateFiltersSection : ConfigurationSection
|
||||||
|
{
|
||||||
|
private readonly List<(StateFilterRowNode Node, Func<UserCategoryDefinition, StateFilter> GetFilter)> _filters = [];
|
||||||
|
private bool _initialized;
|
||||||
|
|
||||||
|
public StateFiltersSection(Func<UserCategoryDefinition> getCategoryDefinition)
|
||||||
|
: base(getCategoryDefinition)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureInitialized()
|
||||||
|
{
|
||||||
|
if (_initialized) return;
|
||||||
|
_initialized = true;
|
||||||
|
|
||||||
|
AddFilter("Untradable", def => def.Rules.Untradable);
|
||||||
|
AddFilter("Unique", def => def.Rules.Unique);
|
||||||
|
AddFilter("Collectable", def => def.Rules.Collectable);
|
||||||
|
AddFilter("Dyeable", def => def.Rules.Dyeable);
|
||||||
|
AddFilter("Repairable", def => def.Rules.Repairable);
|
||||||
|
AddFilter("High Quality", def => def.Rules.HighQuality);
|
||||||
|
AddFilter("Desynthesizable", def => def.Rules.Desynthesizable);
|
||||||
|
AddFilter("Glamourable", def => def.Rules.Glamourable);
|
||||||
|
AddFilter("Spiritbonded", def => def.Rules.FullySpiritbonded);
|
||||||
|
|
||||||
|
RecalculateLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddFilter(string label, Func<UserCategoryDefinition, StateFilter> getFilter)
|
||||||
|
{
|
||||||
|
var node = new StateFilterRowNode(label, new StateFilter(), () => OnValueChanged?.Invoke());
|
||||||
|
_filters.Add((node, getFilter));
|
||||||
|
AddNode(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Refresh()
|
||||||
|
{
|
||||||
|
EnsureInitialized();
|
||||||
|
|
||||||
|
foreach (var (node, getFilter) in _filters)
|
||||||
|
{
|
||||||
|
node.SetState(getFilter(CategoryDefinition));
|
||||||
|
}
|
||||||
|
|
||||||
|
RecalculateLayout();
|
||||||
|
ParentTreeListNode?.RefreshLayout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class ListFiltersSection : ConfigurationSection
|
||||||
|
{
|
||||||
|
public Action? OnListChanged { get; init; }
|
||||||
|
|
||||||
|
private UintListEditorNode? _itemIdsEditor;
|
||||||
|
private StringListEditorNode? _namePatternsEditor;
|
||||||
|
private UintListEditorNode? _uiCategoriesEditor;
|
||||||
|
private RarityEditorNode? _raritiesEditor;
|
||||||
|
|
||||||
|
private bool _initialized;
|
||||||
|
|
||||||
|
public ListFiltersSection(Func<UserCategoryDefinition> getCategoryDefinition)
|
||||||
|
: base(getCategoryDefinition)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureInitialized()
|
||||||
|
{
|
||||||
|
if (_initialized) return;
|
||||||
|
_initialized = true;
|
||||||
|
|
||||||
|
_itemIdsEditor = new UintListEditorNode
|
||||||
|
{
|
||||||
|
Label = "Allowed Item IDs:",
|
||||||
|
LabelResolver = CategoryDefinitionConfigurationNode.ResolveItemName,
|
||||||
|
OnChanged = () =>
|
||||||
|
{
|
||||||
|
OnListChanged?.Invoke();
|
||||||
|
RecalculateLayout();
|
||||||
|
ParentTreeListNode?.RefreshLayout();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
AddNode(_itemIdsEditor);
|
||||||
|
|
||||||
|
_namePatternsEditor = new StringListEditorNode
|
||||||
|
{
|
||||||
|
Label = "Name Patterns (Regex):",
|
||||||
|
OnChanged = () =>
|
||||||
|
{
|
||||||
|
OnListChanged?.Invoke();
|
||||||
|
RecalculateLayout();
|
||||||
|
ParentTreeListNode?.RefreshLayout();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
AddNode(_namePatternsEditor);
|
||||||
|
|
||||||
|
_uiCategoriesEditor = new UintListEditorNode
|
||||||
|
{
|
||||||
|
Label = "UI Categories:",
|
||||||
|
LabelResolver = CategoryDefinitionConfigurationNode.ResolveUiCategoryName,
|
||||||
|
OnChanged = () =>
|
||||||
|
{
|
||||||
|
OnListChanged?.Invoke();
|
||||||
|
RecalculateLayout();
|
||||||
|
ParentTreeListNode?.RefreshLayout();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
AddNode(_uiCategoriesEditor);
|
||||||
|
|
||||||
|
_raritiesEditor = new RarityEditorNode
|
||||||
|
{
|
||||||
|
OnChanged = () => OnValueChanged?.Invoke(),
|
||||||
|
};
|
||||||
|
AddNode(_raritiesEditor);
|
||||||
|
|
||||||
|
RecalculateLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Refresh()
|
||||||
|
{
|
||||||
|
EnsureInitialized();
|
||||||
|
|
||||||
|
_itemIdsEditor!.SetList(CategoryDefinition.Rules.AllowedItemIds);
|
||||||
|
_namePatternsEditor!.SetList(CategoryDefinition.Rules.AllowedItemNamePatterns);
|
||||||
|
_uiCategoriesEditor!.SetList(CategoryDefinition.Rules.AllowedUiCategoryIds);
|
||||||
|
_raritiesEditor!.SetList(CategoryDefinition.Rules.AllowedRarities);
|
||||||
|
|
||||||
|
RecalculateLayout();
|
||||||
|
ParentTreeListNode?.RefreshLayout();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,206 @@
|
|||||||
|
using System;
|
||||||
|
using System.Numerics;
|
||||||
|
using AetherBags.Configuration;
|
||||||
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
using KamiToolKit.Nodes;
|
||||||
|
|
||||||
|
namespace AetherBags.Nodes.Configuration.Category;
|
||||||
|
|
||||||
|
public sealed class RangeFilterRow : VerticalListNode
|
||||||
|
{
|
||||||
|
private readonly CheckboxNode _enabledCheckbox;
|
||||||
|
private readonly NumericInputNode _minNode;
|
||||||
|
private readonly NumericInputNode _maxNode;
|
||||||
|
|
||||||
|
public Action<bool, int, int>? OnFilterChanged { get; set; }
|
||||||
|
|
||||||
|
public required string Label
|
||||||
|
{
|
||||||
|
get => _enabledCheckbox.String.Replace(" Filter", "");
|
||||||
|
init => _enabledCheckbox.String = $"{value} Filter";
|
||||||
|
}
|
||||||
|
|
||||||
|
public int MinBound
|
||||||
|
{
|
||||||
|
get => _minNode.Min;
|
||||||
|
init
|
||||||
|
{
|
||||||
|
_minNode.Min = value;
|
||||||
|
_maxNode.Min = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int MaxBound
|
||||||
|
{
|
||||||
|
get => _minNode.Max;
|
||||||
|
init
|
||||||
|
{
|
||||||
|
_minNode.Max = value;
|
||||||
|
_maxNode.Max = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public RangeFilterRow()
|
||||||
|
{
|
||||||
|
FitContents = true;
|
||||||
|
ItemSpacing = 2.0f;
|
||||||
|
|
||||||
|
_enabledCheckbox = new CheckboxNode
|
||||||
|
{
|
||||||
|
Size = new Vector2(200, 20),
|
||||||
|
OnClick = isChecked =>
|
||||||
|
{
|
||||||
|
if (_minNode == null || _maxNode == null) return;
|
||||||
|
_minNode.IsEnabled = isChecked;
|
||||||
|
_maxNode.IsEnabled = isChecked;
|
||||||
|
OnFilterChanged?.Invoke(isChecked, _minNode.Value, _maxNode.Value);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
AddNode(_enabledCheckbox);
|
||||||
|
|
||||||
|
var rangeRow = new HorizontalListNode { Size = new Vector2(300, 28), ItemSpacing = 8.0f };
|
||||||
|
|
||||||
|
rangeRow.AddNode(new LabelTextNode
|
||||||
|
{
|
||||||
|
TextFlags = TextFlags.AutoAdjustNodeSize,
|
||||||
|
Size = new Vector2(30, 28),
|
||||||
|
String = "Min:",
|
||||||
|
});
|
||||||
|
|
||||||
|
_minNode = new NumericInputNode
|
||||||
|
{
|
||||||
|
Size = new Vector2(100, 28),
|
||||||
|
OnValueUpdate = val =>
|
||||||
|
{
|
||||||
|
if (_maxNode != null) OnFilterChanged?.Invoke(_enabledCheckbox.IsChecked, val, _maxNode.Value);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
rangeRow.AddNode(_minNode);
|
||||||
|
|
||||||
|
rangeRow.AddNode(new LabelTextNode
|
||||||
|
{
|
||||||
|
TextFlags = TextFlags.AutoAdjustNodeSize,
|
||||||
|
Size = new Vector2(30, 28),
|
||||||
|
String = "Max:",
|
||||||
|
});
|
||||||
|
|
||||||
|
_maxNode = new NumericInputNode
|
||||||
|
{
|
||||||
|
Size = new Vector2(100, 28),
|
||||||
|
OnValueUpdate = val => OnFilterChanged?.Invoke(_enabledCheckbox.IsChecked, _minNode.Value, val),
|
||||||
|
};
|
||||||
|
rangeRow.AddNode(_maxNode);
|
||||||
|
|
||||||
|
AddNode(rangeRow);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetFilter(RangeFilter<int> filter)
|
||||||
|
{
|
||||||
|
_enabledCheckbox.IsChecked = filter.Enabled;
|
||||||
|
_minNode.Value = filter.Min;
|
||||||
|
_maxNode.Value = filter.Max;
|
||||||
|
_minNode.IsEnabled = filter.Enabled;
|
||||||
|
_maxNode.IsEnabled = filter.Enabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class RangeFilterRowUint : VerticalListNode
|
||||||
|
{
|
||||||
|
private readonly CheckboxNode _enabledCheckbox;
|
||||||
|
private readonly NumericInputNode _minNode;
|
||||||
|
private readonly NumericInputNode _maxNode;
|
||||||
|
private int _maxBound = int.MaxValue;
|
||||||
|
|
||||||
|
public Action<bool, uint, uint>? OnFilterChanged { get; set; }
|
||||||
|
|
||||||
|
public required string Label
|
||||||
|
{
|
||||||
|
get => _enabledCheckbox.String.Replace(" Filter", "");
|
||||||
|
init => _enabledCheckbox.String = $"{value} Filter";
|
||||||
|
}
|
||||||
|
|
||||||
|
public int MinBound
|
||||||
|
{
|
||||||
|
get => _minNode.Min;
|
||||||
|
init
|
||||||
|
{
|
||||||
|
_minNode.Min = value;
|
||||||
|
_maxNode.Min = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int MaxBound
|
||||||
|
{
|
||||||
|
get => _maxBound;
|
||||||
|
init
|
||||||
|
{
|
||||||
|
_maxBound = value;
|
||||||
|
_minNode.Max = value;
|
||||||
|
_maxNode.Max = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public RangeFilterRowUint()
|
||||||
|
{
|
||||||
|
FitContents = true;
|
||||||
|
ItemSpacing = 2.0f;
|
||||||
|
|
||||||
|
_enabledCheckbox = new CheckboxNode
|
||||||
|
{
|
||||||
|
Size = new Vector2(200, 20),
|
||||||
|
OnClick = isChecked =>
|
||||||
|
{
|
||||||
|
if (_minNode == null || _maxNode == null) return;
|
||||||
|
_minNode.IsEnabled = isChecked;
|
||||||
|
_maxNode.IsEnabled = isChecked;
|
||||||
|
OnFilterChanged?.Invoke(isChecked, (uint)_minNode.Value, (uint)_maxNode.Value);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
AddNode(_enabledCheckbox);
|
||||||
|
|
||||||
|
var rangeRow = new HorizontalListNode { Size = new Vector2(300, 28), ItemSpacing = 8.0f };
|
||||||
|
|
||||||
|
rangeRow.AddNode(new LabelTextNode
|
||||||
|
{
|
||||||
|
TextFlags = TextFlags.AutoAdjustNodeSize,
|
||||||
|
Size = new Vector2(30, 28),
|
||||||
|
String = "Min:",
|
||||||
|
});
|
||||||
|
|
||||||
|
_minNode = new NumericInputNode
|
||||||
|
{
|
||||||
|
Size = new Vector2(100, 28),
|
||||||
|
OnValueUpdate = val =>
|
||||||
|
{
|
||||||
|
if (_maxNode != null)
|
||||||
|
OnFilterChanged?.Invoke(_enabledCheckbox.IsChecked, (uint)val, (uint)_maxNode.Value);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
rangeRow.AddNode(_minNode);
|
||||||
|
|
||||||
|
rangeRow.AddNode(new LabelTextNode
|
||||||
|
{
|
||||||
|
TextFlags = TextFlags.AutoAdjustNodeSize,
|
||||||
|
Size = new Vector2(30, 28),
|
||||||
|
String = "Max:",
|
||||||
|
});
|
||||||
|
|
||||||
|
_maxNode = new NumericInputNode
|
||||||
|
{
|
||||||
|
Size = new Vector2(100, 28),
|
||||||
|
OnValueUpdate = val => OnFilterChanged?.Invoke(_enabledCheckbox.IsChecked, (uint)_minNode.Value, (uint)val),
|
||||||
|
};
|
||||||
|
rangeRow.AddNode(_maxNode);
|
||||||
|
|
||||||
|
AddNode(rangeRow);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetFilter(RangeFilter<uint> filter)
|
||||||
|
{
|
||||||
|
_enabledCheckbox.IsChecked = filter.Enabled;
|
||||||
|
_minNode.Value = (int)filter.Min;
|
||||||
|
_maxNode.Value = (int)Math.Min(filter.Max, _maxBound);
|
||||||
|
_minNode.IsEnabled = filter.Enabled;
|
||||||
|
_maxNode.IsEnabled = filter.Enabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,25 +1,33 @@
|
|||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
|
||||||
using KamiToolKit.Classes;
|
|
||||||
using KamiToolKit.Nodes;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
using KamiToolKit.Classes;
|
||||||
|
using KamiToolKit.Nodes;
|
||||||
|
|
||||||
namespace AetherBags.Nodes.Configuration.Category;
|
namespace AetherBags.Nodes.Configuration.Category;
|
||||||
|
|
||||||
public sealed class RarityEditorNode : VerticalListNode
|
public sealed class RarityEditorNode :VerticalListNode
|
||||||
{
|
{
|
||||||
private static readonly string[] RarityNames = { "Common (White)", "Uncommon (Green)", "Rare (Blue)", "Relic (Purple)", "Aetherial (Pink)" };
|
private const float LabelWidth = 120f;
|
||||||
|
private const float CheckboxWidth = 150f;
|
||||||
|
|
||||||
private List<int> _list;
|
private static readonly string[] RarityNames =
|
||||||
private readonly List<CheckboxNode> _checkboxes = new();
|
[
|
||||||
private readonly Action? _onChanged;
|
"Common (White)",
|
||||||
|
"Uncommon (Green)",
|
||||||
|
"Rare (Blue)",
|
||||||
|
"Relic (Purple)",
|
||||||
|
"Aetherial (Pink)"
|
||||||
|
];
|
||||||
|
|
||||||
public RarityEditorNode(List<int> list, Action? onChanged = null)
|
public Action? OnChanged { get; set; }
|
||||||
|
|
||||||
|
private List<int> _list = [];
|
||||||
|
private readonly List<CheckboxNode> _checkboxes = [];
|
||||||
|
|
||||||
|
public RarityEditorNode()
|
||||||
{
|
{
|
||||||
_list = list;
|
|
||||||
_onChanged = onChanged;
|
|
||||||
|
|
||||||
FitContents = true;
|
FitContents = true;
|
||||||
ItemSpacing = 2.0f;
|
ItemSpacing = 2.0f;
|
||||||
|
|
||||||
@@ -32,33 +40,35 @@ public sealed class RarityEditorNode : VerticalListNode
|
|||||||
};
|
};
|
||||||
AddNode(headerLabel);
|
AddNode(headerLabel);
|
||||||
|
|
||||||
for (int i = 0; i < RarityNames.Length; i++)
|
for (var i = 0; i < RarityNames.Length; i++)
|
||||||
{
|
{
|
||||||
var rarity = i;
|
var rarity = i;
|
||||||
var checkbox = new CheckboxNode
|
var checkbox = new CheckboxNode
|
||||||
{
|
{
|
||||||
Size = new Vector2(200, 20),
|
Size = new Vector2(LabelWidth + CheckboxWidth, 22),
|
||||||
String = RarityNames[i],
|
String = RarityNames[i],
|
||||||
IsChecked = _list.Contains(i),
|
OnClick = isChecked => ToggleRarity(rarity, isChecked),
|
||||||
OnClick = isChecked =>
|
|
||||||
{
|
|
||||||
if (isChecked && !_list.Contains(rarity))
|
|
||||||
{
|
|
||||||
_list.Add(rarity);
|
|
||||||
_list.Sort();
|
|
||||||
}
|
|
||||||
else if (!isChecked && _list.Contains(rarity))
|
|
||||||
{
|
|
||||||
_list.Remove(rarity);
|
|
||||||
}
|
|
||||||
_onChanged?.Invoke();
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
_checkboxes.Add(checkbox);
|
_checkboxes.Add(checkbox);
|
||||||
AddNode(checkbox);
|
AddNode(checkbox);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ToggleRarity(int rarity, bool isChecked)
|
||||||
|
{
|
||||||
|
if (isChecked && !_list.Contains(rarity))
|
||||||
|
{
|
||||||
|
_list.Add(rarity);
|
||||||
|
_list.Sort();
|
||||||
|
}
|
||||||
|
else if (!isChecked && _list.Contains(rarity))
|
||||||
|
{
|
||||||
|
_list.Remove(rarity);
|
||||||
|
}
|
||||||
|
|
||||||
|
OnChanged?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
public void SetList(List<int> newList)
|
public void SetList(List<int> newList)
|
||||||
{
|
{
|
||||||
_list = newList;
|
_list = newList;
|
||||||
@@ -67,7 +77,7 @@ public sealed class RarityEditorNode : VerticalListNode
|
|||||||
|
|
||||||
public void Refresh()
|
public void Refresh()
|
||||||
{
|
{
|
||||||
for (int i = 0; i < _checkboxes.Count; i++)
|
for (var i = 0; i < _checkboxes.Count; i++)
|
||||||
{
|
{
|
||||||
_checkboxes[i].IsChecked = _list.Contains(i);
|
_checkboxes[i].IsChecked = _list.Contains(i);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using AetherBags.Configuration;
|
|||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
using KamiToolKit.Classes;
|
using KamiToolKit.Classes;
|
||||||
using KamiToolKit.Nodes;
|
using KamiToolKit.Nodes;
|
||||||
|
using KamiToolKit.Premade.Nodes;
|
||||||
using System;
|
using System;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
|
||||||
@@ -9,48 +10,54 @@ namespace AetherBags.Nodes.Configuration.Category;
|
|||||||
|
|
||||||
public sealed class StateFilterRowNode : HorizontalListNode
|
public sealed class StateFilterRowNode : HorizontalListNode
|
||||||
{
|
{
|
||||||
private readonly LabelTextNode _labelNode;
|
private const float LabelWidth = 120f;
|
||||||
private readonly TextButtonNode _stateButton;
|
private const float ButtonWidth = 100f;
|
||||||
|
|
||||||
|
private readonly StateFilterButton _stateButton;
|
||||||
private readonly Action? _onChanged;
|
private readonly Action? _onChanged;
|
||||||
private StateFilter _filter;
|
private StateFilter _filter;
|
||||||
|
|
||||||
private static readonly string[] StateLabels = { "Ignored", "Allow", "Disallow" };
|
public StateFilterRowNode(string label, StateFilter filter, Action?onChanged = null)
|
||||||
|
|
||||||
public StateFilterRowNode(string label, StateFilter filter, Action? onChanged = null)
|
|
||||||
{
|
{
|
||||||
_filter = filter;
|
_filter = filter;
|
||||||
_onChanged = onChanged;
|
_onChanged = onChanged;
|
||||||
Size = new Vector2(280, 24);
|
Size = new Vector2(LabelWidth + ButtonWidth + 8f, 24);
|
||||||
ItemSpacing = 8.0f;
|
ItemSpacing = 8.0f;
|
||||||
|
|
||||||
_labelNode = new LabelTextNode
|
var labelNode = new LabelTextNode
|
||||||
{
|
{
|
||||||
TextFlags = TextFlags.AutoAdjustNodeSize,
|
Size = new Vector2(LabelWidth, 24),
|
||||||
Size = new Vector2(100, 24),
|
|
||||||
String = $"{label}:",
|
String = $"{label}:",
|
||||||
TextColor = ColorHelper.GetColor(8),
|
TextColor = ColorHelper.GetColor(8),
|
||||||
|
AlignmentType = AlignmentType.Right,
|
||||||
};
|
};
|
||||||
AddNode(_labelNode);
|
AddNode(labelNode);
|
||||||
|
|
||||||
_stateButton = new TextButtonNode
|
_stateButton = new StateFilterButton
|
||||||
{
|
{
|
||||||
Size = new Vector2(100, 24),
|
Size = new Vector2(ButtonWidth, 24),
|
||||||
String = StateLabels[_filter.State],
|
States = [0, 1, 2],
|
||||||
OnClick = CycleState,
|
SelectedState = _filter.State,
|
||||||
|
OnStateChanged = newState =>
|
||||||
|
{
|
||||||
|
_filter.State = newState;
|
||||||
|
_onChanged?.Invoke();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
AddNode(_stateButton);
|
AddNode(_stateButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CycleState()
|
|
||||||
{
|
|
||||||
_filter.State = (_filter.State + 1) % 3;
|
|
||||||
_stateButton.String = StateLabels[_filter.State];
|
|
||||||
_onChanged?.Invoke();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetState(StateFilter newFilter)
|
public void SetState(StateFilter newFilter)
|
||||||
{
|
{
|
||||||
_filter = newFilter;
|
_filter = newFilter;
|
||||||
_stateButton.String = StateLabels[_filter.State];
|
_stateButton.SelectedState = _filter.State;
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class StateFilterButton : MultiStateButtonNode<int>
|
||||||
|
{
|
||||||
|
private static readonly string[] StateLabels = ["Ignored", "Required", "Excluded"];
|
||||||
|
|
||||||
|
protected override string GetStateText(int state)
|
||||||
|
=> state >= 0 && state < StateLabels.Length ?StateLabels[state] : "Unknown";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,84 +1,77 @@
|
|||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
|
||||||
using KamiToolKit.Classes;
|
|
||||||
using KamiToolKit.Nodes;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
using KamiToolKit.Classes;
|
||||||
|
using KamiToolKit.Nodes;
|
||||||
|
|
||||||
namespace AetherBags.Nodes.Configuration.Category;
|
namespace AetherBags.Nodes.Configuration.Category;
|
||||||
|
|
||||||
public sealed class StringListEditorNode : VerticalListNode
|
public sealed class StringListEditorNode : VerticalListNode
|
||||||
{
|
{
|
||||||
private List<string> _list;
|
private const float LabelWidth = 300f;
|
||||||
private readonly TextInputNode _addInput;
|
private const float RowHeight = 28f;
|
||||||
|
|
||||||
|
private List<string> _list = [];
|
||||||
|
|
||||||
|
private readonly LabelTextNode _headerLabel;
|
||||||
private readonly VerticalListNode _itemsContainer;
|
private readonly VerticalListNode _itemsContainer;
|
||||||
private readonly Action? _onChanged;
|
private readonly HorizontalListNode _addRow;
|
||||||
|
private readonly TextInputNode _addInput;
|
||||||
|
|
||||||
public StringListEditorNode(string label, List<string> list, Action? onChanged = null)
|
public Action? OnChanged { get; set; }
|
||||||
|
|
||||||
|
public required string Label
|
||||||
{
|
{
|
||||||
_list = list;
|
get => _headerLabel.String;
|
||||||
_onChanged = onChanged;
|
init => _headerLabel.String = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StringListEditorNode()
|
||||||
|
{
|
||||||
FitContents = true;
|
FitContents = true;
|
||||||
ItemSpacing = 4.0f;
|
ItemSpacing = 4.0f;
|
||||||
|
|
||||||
var headerLabel = new LabelTextNode
|
_headerLabel = new LabelTextNode
|
||||||
{
|
{
|
||||||
TextFlags = TextFlags.AutoAdjustNodeSize,
|
TextFlags = TextFlags.AutoAdjustNodeSize,
|
||||||
Size = new Vector2(280, 18),
|
Size = new Vector2(280, 18),
|
||||||
String = label,
|
|
||||||
TextColor = ColorHelper.GetColor(8),
|
TextColor = ColorHelper.GetColor(8),
|
||||||
};
|
};
|
||||||
AddNode(headerLabel);
|
AddNode(_headerLabel);
|
||||||
|
|
||||||
_itemsContainer = new VerticalListNode
|
_itemsContainer = new VerticalListNode
|
||||||
{
|
{
|
||||||
FitContents = true,
|
Size = new Vector2(LabelWidth + 40f, 0),
|
||||||
ItemSpacing = 2.0f,
|
ItemSpacing = 2.0f,
|
||||||
|
FitContents = true,
|
||||||
|
FirstItemSpacing = 2,
|
||||||
};
|
};
|
||||||
AddNode(_itemsContainer);
|
AddNode(_itemsContainer);
|
||||||
|
|
||||||
var addRow = new HorizontalListNode { Size = new Vector2(300, 28), ItemSpacing = 4.0f };
|
_addRow = new HorizontalListNode
|
||||||
|
{
|
||||||
|
Size = new Vector2(LabelWidth + 40f, RowHeight),
|
||||||
|
ItemSpacing = 4.0f,
|
||||||
|
};
|
||||||
|
|
||||||
_addInput = new TextInputNode
|
_addInput = new TextInputNode
|
||||||
{
|
{
|
||||||
Size = new Vector2(200, 28),
|
Size = new Vector2(200, RowHeight),
|
||||||
PlaceholderString = "Add new...",
|
PlaceholderString = "Add new...",
|
||||||
OnInputComplete = text =>
|
OnInputComplete = _ => AddCurrentValue(),
|
||||||
{
|
|
||||||
var value = text.ExtractText();
|
|
||||||
if (!string.IsNullOrWhiteSpace(value) && ! _list.Contains(value))
|
|
||||||
{
|
|
||||||
_list.Add(value);
|
|
||||||
_addInput?.String = "";
|
|
||||||
RefreshItems();
|
|
||||||
_onChanged?.Invoke();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
addRow.AddNode(_addInput);
|
_addRow.AddNode(_addInput);
|
||||||
|
|
||||||
var addButton = new TextButtonNode
|
var addButton = new TextButtonNode
|
||||||
{
|
{
|
||||||
Size = new Vector2(60, 28),
|
Size = new Vector2(60, RowHeight),
|
||||||
String = "Add",
|
String = "Add",
|
||||||
OnClick = () =>
|
OnClick = AddCurrentValue,
|
||||||
{
|
|
||||||
var value = _addInput.String;
|
|
||||||
if (!string.IsNullOrWhiteSpace(value) && !_list.Contains(value))
|
|
||||||
{
|
|
||||||
_list.Add(value);
|
|
||||||
_addInput.String = "";
|
|
||||||
RefreshItems();
|
|
||||||
_onChanged?.Invoke();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
addRow.AddNode(addButton);
|
_addRow.AddNode(addButton);
|
||||||
|
|
||||||
AddNode(addRow);
|
AddNode(_addRow);
|
||||||
|
|
||||||
RefreshItems();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetList(List<string> newList)
|
public void SetList(List<string> newList)
|
||||||
@@ -87,35 +80,54 @@ public sealed class StringListEditorNode : VerticalListNode
|
|||||||
RefreshItems();
|
RefreshItems();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void AddCurrentValue()
|
||||||
|
{
|
||||||
|
var value = _addInput.String;
|
||||||
|
if (!string.IsNullOrWhiteSpace(value) && !_list.Contains(value))
|
||||||
|
{
|
||||||
|
_list.Add(value);
|
||||||
|
_addInput.String = "";
|
||||||
|
RefreshItems();
|
||||||
|
OnChanged?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void RefreshItems()
|
private void RefreshItems()
|
||||||
{
|
{
|
||||||
_itemsContainer.SyncWithListData(
|
_itemsContainer.Clear();
|
||||||
_list,
|
|
||||||
node => node.Value,
|
foreach (var value in _list)
|
||||||
value => new StringListItemNode(value)
|
{
|
||||||
{
|
_itemsContainer.AddNode(CreateItemNode(value));
|
||||||
Size = new Vector2(280, 22),
|
}
|
||||||
OnRemove = () =>
|
|
||||||
{
|
if (_list.Count == 0)
|
||||||
_list.Remove(value);
|
{
|
||||||
RefreshItems();
|
_itemsContainer.Height = 0;
|
||||||
_onChanged?.Invoke();
|
}
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
_itemsContainer.RecalculateLayout();
|
_itemsContainer.RecalculateLayout();
|
||||||
RecalculateLayout();
|
RecalculateLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Refresh()
|
private StringListItemNode CreateItemNode(string value) => new(value)
|
||||||
{
|
{
|
||||||
|
Size = new Vector2(LabelWidth + 40f, RowHeight),
|
||||||
|
OnRemove = () => RemoveValue(value),
|
||||||
|
};
|
||||||
|
|
||||||
|
private void RemoveValue(string value)
|
||||||
|
{
|
||||||
|
_list.Remove(value);
|
||||||
RefreshItems();
|
RefreshItems();
|
||||||
|
OnChanged?.Invoke();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class StringListItemNode : HorizontalListNode
|
public sealed class StringListItemNode : HorizontalListNode
|
||||||
{
|
{
|
||||||
|
private const float LabelWidth = 300f;
|
||||||
|
|
||||||
public string Value { get; }
|
public string Value { get; }
|
||||||
public Action? OnRemove { get; init; }
|
public Action? OnRemove { get; init; }
|
||||||
|
|
||||||
@@ -124,20 +136,18 @@ public sealed class StringListItemNode : HorizontalListNode
|
|||||||
Value = value;
|
Value = value;
|
||||||
ItemSpacing = 4.0f;
|
ItemSpacing = 4.0f;
|
||||||
|
|
||||||
var itemLabel = new LabelTextNode
|
AddNode(new LabelTextNode
|
||||||
{
|
{
|
||||||
Size = new Vector2(220, 22),
|
Size = new Vector2(LabelWidth, 24),
|
||||||
String = value,
|
String = value,
|
||||||
TextColor = ColorHelper.GetColor(3),
|
TextColor = ColorHelper.GetColor(3),
|
||||||
};
|
});
|
||||||
AddNode(itemLabel);
|
|
||||||
|
|
||||||
var removeButton = new TextButtonNode
|
AddNode(new CircleButtonNode
|
||||||
{
|
{
|
||||||
Size = new Vector2(50, 22),
|
Size = new Vector2(28, 28),
|
||||||
String = "X",
|
Icon = ButtonIcon.Cross,
|
||||||
OnClick = () => OnRemove?.Invoke(),
|
OnClick = () => OnRemove?.Invoke(),
|
||||||
};
|
});
|
||||||
AddNode(removeButton);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,76 +1,79 @@
|
|||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
|
||||||
using KamiToolKit.Classes;
|
|
||||||
using KamiToolKit.Nodes;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
using KamiToolKit.Classes;
|
||||||
|
using KamiToolKit.Nodes;
|
||||||
|
|
||||||
namespace AetherBags.Nodes.Configuration.Category;
|
namespace AetherBags.Nodes.Configuration.Category;
|
||||||
|
|
||||||
public sealed class UintListEditorNode : VerticalListNode
|
public sealed class UintListEditorNode : VerticalListNode
|
||||||
{
|
{
|
||||||
private List<uint> _list;
|
private const float LabelWidth = 300f;
|
||||||
private readonly NumericInputNode _addInput;
|
private const float RowHeight = 28f;
|
||||||
|
|
||||||
|
private List<uint> _list = [];
|
||||||
|
|
||||||
|
private readonly LabelTextNode _headerLabel;
|
||||||
private readonly VerticalListNode _itemsContainer;
|
private readonly VerticalListNode _itemsContainer;
|
||||||
private readonly Action? _onChanged;
|
private readonly HorizontalListNode _addRow;
|
||||||
private readonly Func<uint, string>? _labelResolver;
|
private readonly NumericInputNode _addInput;
|
||||||
|
|
||||||
public UintListEditorNode(string label, List<uint> list, Action? onChanged = null, Func<uint, string>? labelResolver = null)
|
public Func<uint, string>? LabelResolver { get; init; }
|
||||||
|
public Action? OnChanged { get; set; }
|
||||||
|
|
||||||
|
public required string Label
|
||||||
{
|
{
|
||||||
_list = list;
|
get => _headerLabel.String;
|
||||||
_onChanged = onChanged;
|
init => _headerLabel.String = value;
|
||||||
_labelResolver = labelResolver;
|
}
|
||||||
|
|
||||||
|
public UintListEditorNode()
|
||||||
|
{
|
||||||
FitContents = true;
|
FitContents = true;
|
||||||
ItemSpacing = 4.0f;
|
ItemSpacing = 4.0f;
|
||||||
|
|
||||||
var headerLabel = new LabelTextNode
|
_headerLabel = new LabelTextNode
|
||||||
{
|
{
|
||||||
TextFlags = TextFlags.AutoAdjustNodeSize,
|
TextFlags = TextFlags.AutoAdjustNodeSize,
|
||||||
Size = new Vector2(280, 18),
|
Size = new Vector2(280, 18),
|
||||||
String = label,
|
|
||||||
TextColor = ColorHelper.GetColor(8),
|
TextColor = ColorHelper.GetColor(8),
|
||||||
};
|
};
|
||||||
AddNode(headerLabel);
|
AddNode(_headerLabel);
|
||||||
|
|
||||||
_itemsContainer = new VerticalListNode
|
_itemsContainer = new VerticalListNode
|
||||||
{
|
{
|
||||||
FitContents = true,
|
Size = new Vector2(LabelWidth + 40f, 0),
|
||||||
ItemSpacing = 2.0f,
|
ItemSpacing = 2.0f,
|
||||||
|
FitContents = true,
|
||||||
|
FirstItemSpacing = 2,
|
||||||
};
|
};
|
||||||
AddNode(_itemsContainer);
|
AddNode(_itemsContainer);
|
||||||
|
|
||||||
var addRow = new HorizontalListNode { Size = new Vector2(300, 28), ItemSpacing = 4.0f };
|
_addRow = new HorizontalListNode
|
||||||
|
{
|
||||||
|
Size = new Vector2(LabelWidth + 40f, RowHeight),
|
||||||
|
ItemSpacing = 4.0f,
|
||||||
|
};
|
||||||
|
|
||||||
_addInput = new NumericInputNode
|
_addInput = new NumericInputNode
|
||||||
{
|
{
|
||||||
Size = new Vector2(120, 28),
|
Size = new Vector2(120, RowHeight),
|
||||||
Min = 0,
|
Min = 0,
|
||||||
Max = int.MaxValue,
|
Max = int.MaxValue,
|
||||||
Value = 0,
|
Value = 0,
|
||||||
};
|
};
|
||||||
addRow.AddNode(_addInput);
|
_addRow.AddNode(_addInput);
|
||||||
|
|
||||||
var addButton = new TextButtonNode
|
var addButton = new TextButtonNode
|
||||||
{
|
{
|
||||||
Size = new Vector2(60, 28),
|
Size = new Vector2(60, RowHeight),
|
||||||
String = "Add",
|
String = "Add",
|
||||||
OnClick = () =>
|
OnClick = AddCurrentValue,
|
||||||
{
|
|
||||||
var value = (uint)_addInput.Value;
|
|
||||||
if (! _list.Contains(value))
|
|
||||||
{
|
|
||||||
_list.Add(value);
|
|
||||||
RefreshItems();
|
|
||||||
_onChanged?.Invoke();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
addRow.AddNode(addButton);
|
_addRow.AddNode(addButton);
|
||||||
|
|
||||||
AddNode(addRow);
|
AddNode(_addRow);
|
||||||
|
|
||||||
RefreshItems();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetList(List<uint> newList)
|
public void SetList(List<uint> newList)
|
||||||
@@ -79,35 +82,53 @@ public sealed class UintListEditorNode : VerticalListNode
|
|||||||
RefreshItems();
|
RefreshItems();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void AddCurrentValue()
|
||||||
|
{
|
||||||
|
var value = (uint)_addInput.Value;
|
||||||
|
if (!_list.Contains(value))
|
||||||
|
{
|
||||||
|
_list.Add(value);
|
||||||
|
RefreshItems();
|
||||||
|
OnChanged?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void RefreshItems()
|
private void RefreshItems()
|
||||||
{
|
{
|
||||||
_itemsContainer.SyncWithListData(
|
_itemsContainer.Clear();
|
||||||
_list,
|
|
||||||
node => node.Value,
|
foreach (var value in _list)
|
||||||
value => new UintListItemNode(value, _labelResolver)
|
{
|
||||||
{
|
_itemsContainer.AddNode(CreateItemNode(value));
|
||||||
Size = new Vector2(280, 22),
|
}
|
||||||
OnRemove = () =>
|
|
||||||
{
|
if (_list.Count == 0)
|
||||||
_list.Remove(value);
|
{
|
||||||
RefreshItems();
|
_itemsContainer.Height = 0;
|
||||||
_onChanged?.Invoke();
|
}
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
_itemsContainer.RecalculateLayout();
|
_itemsContainer.RecalculateLayout();
|
||||||
RecalculateLayout();
|
RecalculateLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Refresh()
|
private UintListItemNode CreateItemNode(uint value) => new(value, LabelResolver)
|
||||||
{
|
{
|
||||||
|
Size = new Vector2(LabelWidth + 40f, RowHeight),
|
||||||
|
OnRemove = () => RemoveValue(value),
|
||||||
|
};
|
||||||
|
|
||||||
|
private void RemoveValue(uint value)
|
||||||
|
{
|
||||||
|
_list.Remove(value);
|
||||||
RefreshItems();
|
RefreshItems();
|
||||||
|
OnChanged?.Invoke();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class UintListItemNode : HorizontalListNode
|
public sealed class UintListItemNode : HorizontalListNode
|
||||||
{
|
{
|
||||||
|
private const float LabelWidth = 300f;
|
||||||
|
|
||||||
public uint Value { get; }
|
public uint Value { get; }
|
||||||
public Action? OnRemove { get; init; }
|
public Action? OnRemove { get; init; }
|
||||||
|
|
||||||
@@ -116,22 +137,22 @@ public sealed class UintListItemNode : HorizontalListNode
|
|||||||
Value = value;
|
Value = value;
|
||||||
ItemSpacing = 4.0f;
|
ItemSpacing = 4.0f;
|
||||||
|
|
||||||
var displayText = labelResolver != null ? $"{value} - {labelResolver(value)}" : value.ToString();
|
var displayText = labelResolver is not null
|
||||||
var itemLabel = new LabelTextNode
|
? $"{value} - {labelResolver(value)}"
|
||||||
|
: value.ToString();
|
||||||
|
|
||||||
|
AddNode(new LabelTextNode
|
||||||
{
|
{
|
||||||
TextFlags = TextFlags.AutoAdjustNodeSize,
|
Size = new Vector2(LabelWidth, 24),
|
||||||
Size = new Vector2(220, 22),
|
|
||||||
String = displayText,
|
String = displayText,
|
||||||
TextColor = ColorHelper.GetColor(3),
|
TextColor = ColorHelper.GetColor(3),
|
||||||
};
|
});
|
||||||
AddNode(itemLabel);
|
|
||||||
|
|
||||||
var removeButton = new TextButtonNode
|
AddNode(new CircleButtonNode
|
||||||
{
|
{
|
||||||
Size = new Vector2(50, 22),
|
Size = new Vector2(28, 28),
|
||||||
String = "X",
|
Icon = ButtonIcon.Cross,
|
||||||
OnClick = () => OnRemove?.Invoke(),
|
OnClick = () => OnRemove?.Invoke(),
|
||||||
};
|
});
|
||||||
AddNode(removeButton);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -45,6 +45,7 @@ public sealed class ImportExportResetNode : HorizontalListNode
|
|||||||
Height = 32,
|
Height = 32,
|
||||||
Width = 100,
|
Width = 100,
|
||||||
String = "Reset",
|
String = "Reset",
|
||||||
|
TextNode = { TextColor = ColorHelper.GetColor(50) },
|
||||||
TextTooltip = " Reset configuration\n(hold button to confirm)",
|
TextTooltip = " Reset configuration\n(hold button to confirm)",
|
||||||
OnClick = ResetConfig
|
OnClick = ResetConfig
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user