Improve Category editor
This commit is contained in:
@@ -7,66 +7,52 @@ namespace AetherBags.Nodes.Configuration.Category;
|
||||
|
||||
public class CategoryConfigurationNode : ConfigNode<CategoryWrapper>
|
||||
{
|
||||
private readonly ScrollingListNode _categoryList;
|
||||
private CategoryDefinitionConfigurationNode? _activeNode;
|
||||
|
||||
public Action? OnCategoryChanged { get; set; }
|
||||
|
||||
public CategoryConfigurationNode()
|
||||
{
|
||||
_categoryList = new ScrollingListNode
|
||||
{
|
||||
AutoHideScrollBar = true,
|
||||
};
|
||||
_categoryList.FitContents = true;
|
||||
_categoryList.AttachNode(this);
|
||||
}
|
||||
|
||||
protected override void OptionChanged(CategoryWrapper? option)
|
||||
{
|
||||
if (option?.CategoryDefinition is null)
|
||||
{
|
||||
_categoryList.IsVisible = false;
|
||||
if (_activeNode is not null)
|
||||
{
|
||||
_activeNode.IsVisible = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
_categoryList.IsVisible = true;
|
||||
|
||||
if (_activeNode is null)
|
||||
{
|
||||
_activeNode = new CategoryDefinitionConfigurationNode(option.CategoryDefinition)
|
||||
_activeNode = new CategoryDefinitionConfigurationNode
|
||||
{
|
||||
Size = _categoryList.Size,
|
||||
OnLayoutChanged = UpdateScrollHeight,
|
||||
OnLayoutChanged = RecalculateLayout,
|
||||
OnCategoryPropertyChanged = OnCategoryChanged,
|
||||
};
|
||||
_categoryList.AddNode(_activeNode);
|
||||
_activeNode.AttachNode(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
_activeNode.IsVisible = true;
|
||||
_activeNode.Size = Size;
|
||||
_activeNode.SetCategory(option.CategoryDefinition);
|
||||
}
|
||||
|
||||
UpdateScrollHeight();
|
||||
}
|
||||
|
||||
private void UpdateScrollHeight()
|
||||
private void RecalculateLayout()
|
||||
{
|
||||
_categoryList.RecalculateLayout();
|
||||
//_categoryList.ContentHeight = _categoryList.Height;
|
||||
// Trigger parent layout update if needed
|
||||
}
|
||||
|
||||
protected override void OnSizeChanged()
|
||||
{
|
||||
base.OnSizeChanged();
|
||||
_categoryList.Size = Size;
|
||||
_categoryList.Width = Width;
|
||||
|
||||
foreach (var node in _categoryList.GetNodes<CategoryDefinitionConfigurationNode>())
|
||||
if (_activeNode is not null)
|
||||
{
|
||||
node.Width = Width;
|
||||
}
|
||||
|
||||
UpdateScrollHeight();
|
||||
_activeNode.Size = Size;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using AetherBags.Configuration;
|
||||
using AetherBags.Inventory;
|
||||
@@ -15,520 +16,500 @@ using Action = System.Action;
|
||||
|
||||
namespace AetherBags.Nodes.Configuration.Category;
|
||||
|
||||
public sealed class CategoryDefinitionConfigurationNode : VerticalListNode
|
||||
public sealed class CategoryDefinitionConfigurationNode : SimpleComponentNode
|
||||
{
|
||||
private readonly CheckboxNode _enabledCheckbox;
|
||||
private readonly CheckboxNode _pinnedCheckbox;
|
||||
private readonly TextInputNode _nameInputNode;
|
||||
private readonly TextInputNode _descriptionInputNode;
|
||||
private readonly ColorInputRow _colorInputNode;
|
||||
private readonly NumericInputNode _priorityInputNode;
|
||||
private readonly NumericInputNode _orderInputNode;
|
||||
private static ExcelSheet<Item>? ItemSheet => Services.DataManager.GetExcelSheet<Item>();
|
||||
private static ExcelSheet<ItemUICategory>? UICategorySheet => Services.DataManager.GetExcelSheet<ItemUICategory>();
|
||||
|
||||
private readonly CheckboxNode _levelEnabledCheckbox;
|
||||
private readonly NumericInputNode _levelMinNode;
|
||||
private readonly NumericInputNode _levelMaxNode;
|
||||
public Action? OnLayoutChanged { get; init; }
|
||||
public Action? OnCategoryPropertyChanged { get; init; }
|
||||
|
||||
private readonly CheckboxNode _itemLevelEnabledCheckbox;
|
||||
private readonly NumericInputNode _itemLevelMinNode;
|
||||
private readonly NumericInputNode _itemLevelMaxNode;
|
||||
private UserCategoryDefinition _categoryDefinition = new();
|
||||
|
||||
private readonly CheckboxNode _vendorPriceEnabledCheckbox;
|
||||
private readonly NumericInputNode _vendorPriceMinNode;
|
||||
private readonly NumericInputNode _vendorPriceMaxNode;
|
||||
private readonly ScrollingAreaNode<TreeListNode> _scrollingArea;
|
||||
private readonly BasicSettingsSection _basicSettings;
|
||||
private readonly RangeFiltersSection _rangeFilters;
|
||||
private readonly StateFiltersSection _stateFilters;
|
||||
private readonly ListFiltersSection _listFilters;
|
||||
|
||||
private readonly StateFilterRowNode _untradableFilter;
|
||||
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)
|
||||
public CategoryDefinitionConfigurationNode()
|
||||
{
|
||||
CategoryDefinition = categoryDefinition;
|
||||
|
||||
_sItemSheet ??= Services.DataManager.GetExcelSheet<Item>();
|
||||
_sUICategorySheet ??= Services.DataManager.GetExcelSheet<ItemUICategory>();
|
||||
|
||||
FitContents = true;
|
||||
ItemSpacing = 4.0f;
|
||||
|
||||
var catchAllWarningNode = new TextNode
|
||||
_scrollingArea = new ScrollingAreaNode<TreeListNode>
|
||||
{
|
||||
Size = new Vector2(300, 40),
|
||||
TextFlags = TextFlags.MultiLine | TextFlags.AutoAdjustNodeSize,
|
||||
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),
|
||||
ContentHeight = 100.0f,
|
||||
AutoHideScrollBar = true,
|
||||
};
|
||||
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
|
||||
{
|
||||
Size = new Vector2(200, 20),
|
||||
Size = new Vector2(Width, 20),
|
||||
String = "Enabled",
|
||||
IsChecked = CategoryDefinition.Enabled,
|
||||
OnClick = isChecked =>
|
||||
{
|
||||
CategoryDefinition.Enabled = isChecked;
|
||||
NotifyChanged();
|
||||
NotifyCategoryPropertyChanged();
|
||||
OnPropertyChanged?.Invoke();
|
||||
},
|
||||
};
|
||||
AddNode(_enabledCheckbox);
|
||||
|
||||
_pinnedCheckbox = new CheckboxNode
|
||||
{
|
||||
Size = new Vector2(200, 20),
|
||||
Size = new Vector2(Width, 20),
|
||||
String = "Pinned",
|
||||
IsChecked = CategoryDefinition.Pinned,
|
||||
OnClick = isChecked =>
|
||||
{
|
||||
CategoryDefinition.Pinned = isChecked;
|
||||
NotifyChanged();
|
||||
NotifyCategoryPropertyChanged();
|
||||
OnPropertyChanged?.Invoke();
|
||||
},
|
||||
};
|
||||
AddNode(_pinnedCheckbox);
|
||||
|
||||
AddNode(new LabelTextNode
|
||||
{
|
||||
TextFlags = TextFlags.AutoAdjustNodeSize,
|
||||
Size = new Vector2(80, 20),
|
||||
String = "Name:"
|
||||
});
|
||||
_nameInputNode = new TextInputNode
|
||||
AddNode(CreateLabel("Name: "));
|
||||
_nameInput = new TextInputNode
|
||||
{
|
||||
Size = new Vector2(250, 28),
|
||||
String = CategoryDefinition.Name,
|
||||
PlaceholderString = CategoryDefinition.Name.IsNullOrWhitespace() ? "Category Name" : "",
|
||||
OnInputReceived = name =>
|
||||
PlaceholderString = "Category Name",
|
||||
OnInputReceived = input =>
|
||||
{
|
||||
CategoryDefinition.Name = name.ExtractText();
|
||||
NotifyChanged();
|
||||
NotifyCategoryPropertyChanged();
|
||||
CategoryDefinition.Name = input.ExtractText();
|
||||
OnPropertyChanged?.Invoke();
|
||||
},
|
||||
};
|
||||
AddNode(_nameInputNode);
|
||||
AddNode(_nameInput);
|
||||
|
||||
AddNode(new LabelTextNode
|
||||
{
|
||||
TextFlags = TextFlags.AutoAdjustNodeSize,
|
||||
Size = new Vector2(80, 20),
|
||||
String = "Description:"
|
||||
});
|
||||
_descriptionInputNode = new TextInputNode
|
||||
AddNode(CreateLabel("Description:"));
|
||||
_descriptionInput = new TextInputNode
|
||||
{
|
||||
Size = new Vector2(250, 28),
|
||||
String = CategoryDefinition.Description,
|
||||
PlaceholderString = CategoryDefinition.Description.IsNullOrWhitespace() ? "Optional description" : "",
|
||||
OnInputReceived = desc =>
|
||||
PlaceholderString = "Optional description",
|
||||
OnInputReceived = input =>
|
||||
{
|
||||
CategoryDefinition.Description = desc.ExtractText();
|
||||
NotifyChanged();
|
||||
CategoryDefinition.Description = input.ExtractText();
|
||||
OnValueChanged?.Invoke();
|
||||
},
|
||||
};
|
||||
AddNode(_descriptionInputNode);
|
||||
AddNode(_descriptionInput);
|
||||
|
||||
_colorInputNode = new ColorInputRow
|
||||
_colorInput = new ColorInputRow
|
||||
{
|
||||
Label = "Color",
|
||||
Size = new Vector2(300, 28),
|
||||
CurrentColor = CategoryDefinition.Color,
|
||||
CurrentColor = new UserCategoryDefinition().Color,
|
||||
DefaultColor = new UserCategoryDefinition().Color,
|
||||
OnColorConfirmed = color =>
|
||||
{
|
||||
CategoryDefinition.Color = color;
|
||||
NotifyChanged();
|
||||
},
|
||||
OnColorCanceled = color =>
|
||||
{
|
||||
CategoryDefinition.Color = color;
|
||||
NotifyChanged();
|
||||
},
|
||||
OnColorPreviewed = color =>
|
||||
{
|
||||
CategoryDefinition.Color = color;
|
||||
NotifyChanged();
|
||||
}
|
||||
OnColorConfirmed = c => { CategoryDefinition.Color = c; OnValueChanged?.Invoke(); },
|
||||
OnColorCanceled = c => { CategoryDefinition.Color = c; OnValueChanged?.Invoke(); },
|
||||
OnColorPreviewed = c => { CategoryDefinition.Color = c; OnValueChanged?.Invoke(); },
|
||||
};
|
||||
AddNode(_colorInputNode);
|
||||
AddNode(_colorInput);
|
||||
|
||||
AddNode(new LabelTextNode
|
||||
{
|
||||
TextFlags = TextFlags.AutoAdjustNodeSize,
|
||||
Size = new Vector2(80, 20),
|
||||
String = "Priority:"
|
||||
});
|
||||
_priorityInputNode = new NumericInputNode
|
||||
AddNode(CreateLabel("Priority:"));
|
||||
_priorityInput = new NumericInputNode
|
||||
{
|
||||
Size = new Vector2(120, 28),
|
||||
Min = 0,
|
||||
Max = 1000,
|
||||
Step = 1,
|
||||
Value = CategoryDefinition.Priority,
|
||||
OnValueUpdate = val =>
|
||||
{
|
||||
CategoryDefinition.Priority = val;
|
||||
NotifyChanged();
|
||||
OnValueChanged?.Invoke();
|
||||
},
|
||||
};
|
||||
AddNode(_priorityInputNode);
|
||||
AddNode(_priorityInput);
|
||||
|
||||
AddNode(new LabelTextNode { TextFlags = TextFlags.AutoAdjustNodeSize, Size = new Vector2(80, 20), String = "Order:" });
|
||||
_orderInputNode = new NumericInputNode
|
||||
AddNode(CreateLabel("Order: "));
|
||||
_orderInput = new NumericInputNode
|
||||
{
|
||||
Size = new Vector2(120, 28),
|
||||
Min = 0,
|
||||
Max = 9999,
|
||||
Step = 1,
|
||||
Value = CategoryDefinition.Order,
|
||||
OnValueUpdate = val =>
|
||||
{
|
||||
CategoryDefinition.Order = val;
|
||||
NotifyChanged();
|
||||
NotifyCategoryPropertyChanged();
|
||||
OnPropertyChanged?.Invoke();
|
||||
},
|
||||
};
|
||||
AddNode(_orderInputNode);
|
||||
AddNode(_orderInput);
|
||||
|
||||
AddNode(CreateSectionHeader("Range Filters"));
|
||||
RecalculateLayout();
|
||||
}
|
||||
|
||||
(_levelEnabledCheckbox, _levelMinNode, _levelMaxNode) = CreateRangeFilter(
|
||||
"Level",
|
||||
CategoryDefinition.Rules.Level,
|
||||
0, 200,
|
||||
(enabled, min, max) =>
|
||||
public void Refresh()
|
||||
{
|
||||
EnsureInitialized();
|
||||
|
||||
_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.Min = min;
|
||||
CategoryDefinition.Rules.Level.Max = max;
|
||||
NotifyChanged();
|
||||
}
|
||||
);
|
||||
OnValueChanged?.Invoke();
|
||||
},
|
||||
};
|
||||
AddNode(_levelFilter);
|
||||
|
||||
(_itemLevelEnabledCheckbox, _itemLevelMinNode, _itemLevelMaxNode) = CreateRangeFilter(
|
||||
"Item Level",
|
||||
CategoryDefinition.Rules.ItemLevel,
|
||||
0, 2000,
|
||||
(enabled, min, max) =>
|
||||
_itemLevelFilter = new RangeFilterRow
|
||||
{
|
||||
Label = "Item Level",
|
||||
MinBound = 0,
|
||||
MaxBound = 2000,
|
||||
OnFilterChanged = (enabled, min, max) =>
|
||||
{
|
||||
CategoryDefinition.Rules.ItemLevel.Enabled = enabled;
|
||||
CategoryDefinition.Rules.ItemLevel.Min = min;
|
||||
CategoryDefinition.Rules.ItemLevel.Max = max;
|
||||
NotifyChanged();
|
||||
}
|
||||
);
|
||||
|
||||
(_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),
|
||||
OnValueChanged?.Invoke();
|
||||
},
|
||||
};
|
||||
}
|
||||
AddNode(_itemLevelFilter);
|
||||
|
||||
private (CheckboxNode enabled, NumericInputNode min, NumericInputNode max) CreateRangeFilter(
|
||||
string label,
|
||||
RangeFilter<int> filter,
|
||||
int minBound,
|
||||
int maxBound,
|
||||
Action<bool, int, int> onUpdate)
|
||||
_vendorPriceFilter = new RangeFilterRowUint
|
||||
{
|
||||
var enabledCheckbox = new CheckboxNode
|
||||
Label = "Vendor Price",
|
||||
MinBound = 0,
|
||||
MaxBound = 9_999_999,
|
||||
OnFilterChanged = (enabled, min, max) =>
|
||||
{
|
||||
Size = new Vector2(200, 20),
|
||||
String = $"{label} Filter",
|
||||
IsChecked = filter.Enabled,
|
||||
CategoryDefinition.Rules.VendorPrice.Enabled = enabled;
|
||||
CategoryDefinition.Rules.VendorPrice.Min = min;
|
||||
CategoryDefinition.Rules.VendorPrice.Max = max;
|
||||
OnValueChanged?.Invoke();
|
||||
},
|
||||
};
|
||||
AddNode(enabledCheckbox);
|
||||
|
||||
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);
|
||||
AddNode(_vendorPriceFilter);
|
||||
|
||||
RecalculateLayout();
|
||||
OnLayoutChanged?.Invoke();
|
||||
}
|
||||
|
||||
private static void RefreshRangeFilter(CheckboxNode enabled, NumericInputNode min, NumericInputNode max, RangeFilter<int> filter)
|
||||
public void Refresh()
|
||||
{
|
||||
enabled.IsChecked = filter.Enabled;
|
||||
min.Value = filter.Min;
|
||||
max.Value = filter.Max;
|
||||
min.IsEnabled = filter.Enabled;
|
||||
max.IsEnabled = filter.Enabled;
|
||||
EnsureInitialized();
|
||||
|
||||
_levelFilter!.SetFilter(CategoryDefinition.Rules.Level);
|
||||
_itemLevelFilter!.SetFilter(CategoryDefinition.Rules.ItemLevel);
|
||||
_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.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using KamiToolKit.Classes;
|
||||
using KamiToolKit.Nodes;
|
||||
|
||||
namespace AetherBags.Nodes.Configuration.Category;
|
||||
|
||||
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 readonly List<CheckboxNode> _checkboxes = new();
|
||||
private readonly Action? _onChanged;
|
||||
private static readonly string[] RarityNames =
|
||||
[
|
||||
"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;
|
||||
ItemSpacing = 2.0f;
|
||||
|
||||
@@ -32,15 +40,21 @@ public sealed class RarityEditorNode : VerticalListNode
|
||||
};
|
||||
AddNode(headerLabel);
|
||||
|
||||
for (int i = 0; i < RarityNames.Length; i++)
|
||||
for (var i = 0; i < RarityNames.Length; i++)
|
||||
{
|
||||
var rarity = i;
|
||||
var checkbox = new CheckboxNode
|
||||
{
|
||||
Size = new Vector2(200, 20),
|
||||
Size = new Vector2(LabelWidth + CheckboxWidth, 22),
|
||||
String = RarityNames[i],
|
||||
IsChecked = _list.Contains(i),
|
||||
OnClick = isChecked =>
|
||||
OnClick = isChecked => ToggleRarity(rarity, isChecked),
|
||||
};
|
||||
_checkboxes.Add(checkbox);
|
||||
AddNode(checkbox);
|
||||
}
|
||||
}
|
||||
|
||||
private void ToggleRarity(int rarity, bool isChecked)
|
||||
{
|
||||
if (isChecked && !_list.Contains(rarity))
|
||||
{
|
||||
@@ -51,12 +65,8 @@ public sealed class RarityEditorNode : VerticalListNode
|
||||
{
|
||||
_list.Remove(rarity);
|
||||
}
|
||||
_onChanged?.Invoke();
|
||||
},
|
||||
};
|
||||
_checkboxes.Add(checkbox);
|
||||
AddNode(checkbox);
|
||||
}
|
||||
|
||||
OnChanged?.Invoke();
|
||||
}
|
||||
|
||||
public void SetList(List<int> newList)
|
||||
@@ -67,7 +77,7 @@ public sealed class RarityEditorNode : VerticalListNode
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ using AetherBags.Configuration;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using KamiToolKit.Classes;
|
||||
using KamiToolKit.Nodes;
|
||||
using KamiToolKit.Premade.Nodes;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
|
||||
@@ -9,48 +10,54 @@ namespace AetherBags.Nodes.Configuration.Category;
|
||||
|
||||
public sealed class StateFilterRowNode : HorizontalListNode
|
||||
{
|
||||
private readonly LabelTextNode _labelNode;
|
||||
private readonly TextButtonNode _stateButton;
|
||||
private const float LabelWidth = 120f;
|
||||
private const float ButtonWidth = 100f;
|
||||
|
||||
private readonly StateFilterButton _stateButton;
|
||||
private readonly Action? _onChanged;
|
||||
private StateFilter _filter;
|
||||
|
||||
private static readonly string[] StateLabels = { "Ignored", "Allow", "Disallow" };
|
||||
|
||||
public StateFilterRowNode(string label, StateFilter filter, Action?onChanged = null)
|
||||
{
|
||||
_filter = filter;
|
||||
_onChanged = onChanged;
|
||||
Size = new Vector2(280, 24);
|
||||
Size = new Vector2(LabelWidth + ButtonWidth + 8f, 24);
|
||||
ItemSpacing = 8.0f;
|
||||
|
||||
_labelNode = new LabelTextNode
|
||||
var labelNode = new LabelTextNode
|
||||
{
|
||||
TextFlags = TextFlags.AutoAdjustNodeSize,
|
||||
Size = new Vector2(100, 24),
|
||||
Size = new Vector2(LabelWidth, 24),
|
||||
String = $"{label}:",
|
||||
TextColor = ColorHelper.GetColor(8),
|
||||
AlignmentType = AlignmentType.Right,
|
||||
};
|
||||
AddNode(_labelNode);
|
||||
AddNode(labelNode);
|
||||
|
||||
_stateButton = new TextButtonNode
|
||||
_stateButton = new StateFilterButton
|
||||
{
|
||||
Size = new Vector2(100, 24),
|
||||
String = StateLabels[_filter.State],
|
||||
OnClick = CycleState,
|
||||
Size = new Vector2(ButtonWidth, 24),
|
||||
States = [0, 1, 2],
|
||||
SelectedState = _filter.State,
|
||||
OnStateChanged = newState =>
|
||||
{
|
||||
_filter.State = newState;
|
||||
_onChanged?.Invoke();
|
||||
}
|
||||
};
|
||||
AddNode(_stateButton);
|
||||
}
|
||||
|
||||
private void CycleState()
|
||||
{
|
||||
_filter.State = (_filter.State + 1) % 3;
|
||||
_stateButton.String = StateLabels[_filter.State];
|
||||
_onChanged?.Invoke();
|
||||
}
|
||||
|
||||
public void SetState(StateFilter 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.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using KamiToolKit.Classes;
|
||||
using KamiToolKit.Nodes;
|
||||
|
||||
namespace AetherBags.Nodes.Configuration.Category;
|
||||
|
||||
public sealed class StringListEditorNode : VerticalListNode
|
||||
{
|
||||
private List<string> _list;
|
||||
private readonly TextInputNode _addInput;
|
||||
private const float LabelWidth = 300f;
|
||||
private const float RowHeight = 28f;
|
||||
|
||||
private List<string> _list = [];
|
||||
|
||||
private readonly LabelTextNode _headerLabel;
|
||||
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;
|
||||
_onChanged = onChanged;
|
||||
get => _headerLabel.String;
|
||||
init => _headerLabel.String = value;
|
||||
}
|
||||
|
||||
public StringListEditorNode()
|
||||
{
|
||||
FitContents = true;
|
||||
ItemSpacing = 4.0f;
|
||||
|
||||
var headerLabel = new LabelTextNode
|
||||
_headerLabel = new LabelTextNode
|
||||
{
|
||||
TextFlags = TextFlags.AutoAdjustNodeSize,
|
||||
Size = new Vector2(280, 18),
|
||||
String = label,
|
||||
TextColor = ColorHelper.GetColor(8),
|
||||
};
|
||||
AddNode(headerLabel);
|
||||
AddNode(_headerLabel);
|
||||
|
||||
_itemsContainer = new VerticalListNode
|
||||
{
|
||||
FitContents = true,
|
||||
Size = new Vector2(LabelWidth + 40f, 0),
|
||||
ItemSpacing = 2.0f,
|
||||
FitContents = true,
|
||||
FirstItemSpacing = 2,
|
||||
};
|
||||
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
|
||||
{
|
||||
Size = new Vector2(200, 28),
|
||||
Size = new Vector2(200, RowHeight),
|
||||
PlaceholderString = "Add new...",
|
||||
OnInputComplete = text =>
|
||||
{
|
||||
var value = text.ExtractText();
|
||||
if (!string.IsNullOrWhiteSpace(value) && ! _list.Contains(value))
|
||||
{
|
||||
_list.Add(value);
|
||||
_addInput?.String = "";
|
||||
RefreshItems();
|
||||
_onChanged?.Invoke();
|
||||
}
|
||||
},
|
||||
OnInputComplete = _ => AddCurrentValue(),
|
||||
};
|
||||
addRow.AddNode(_addInput);
|
||||
_addRow.AddNode(_addInput);
|
||||
|
||||
var addButton = new TextButtonNode
|
||||
{
|
||||
Size = new Vector2(60, 28),
|
||||
Size = new Vector2(60, RowHeight),
|
||||
String = "Add",
|
||||
OnClick = () =>
|
||||
{
|
||||
var value = _addInput.String;
|
||||
if (!string.IsNullOrWhiteSpace(value) && !_list.Contains(value))
|
||||
{
|
||||
_list.Add(value);
|
||||
_addInput.String = "";
|
||||
RefreshItems();
|
||||
_onChanged?.Invoke();
|
||||
}
|
||||
},
|
||||
OnClick = AddCurrentValue,
|
||||
};
|
||||
addRow.AddNode(addButton);
|
||||
_addRow.AddNode(addButton);
|
||||
|
||||
AddNode(addRow);
|
||||
|
||||
RefreshItems();
|
||||
AddNode(_addRow);
|
||||
}
|
||||
|
||||
public void SetList(List<string> newList)
|
||||
@@ -87,35 +80,54 @@ public sealed class StringListEditorNode : VerticalListNode
|
||||
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()
|
||||
{
|
||||
_itemsContainer.SyncWithListData(
|
||||
_list,
|
||||
node => node.Value,
|
||||
value => new StringListItemNode(value)
|
||||
_itemsContainer.Clear();
|
||||
|
||||
foreach (var value in _list)
|
||||
{
|
||||
Size = new Vector2(280, 22),
|
||||
OnRemove = () =>
|
||||
{
|
||||
_list.Remove(value);
|
||||
RefreshItems();
|
||||
_onChanged?.Invoke();
|
||||
},
|
||||
_itemsContainer.AddNode(CreateItemNode(value));
|
||||
}
|
||||
|
||||
if (_list.Count == 0)
|
||||
{
|
||||
_itemsContainer.Height = 0;
|
||||
}
|
||||
);
|
||||
|
||||
_itemsContainer.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();
|
||||
OnChanged?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class StringListItemNode : HorizontalListNode
|
||||
{
|
||||
private const float LabelWidth = 300f;
|
||||
|
||||
public string Value { get; }
|
||||
public Action? OnRemove { get; init; }
|
||||
|
||||
@@ -124,20 +136,18 @@ public sealed class StringListItemNode : HorizontalListNode
|
||||
Value = value;
|
||||
ItemSpacing = 4.0f;
|
||||
|
||||
var itemLabel = new LabelTextNode
|
||||
AddNode(new LabelTextNode
|
||||
{
|
||||
Size = new Vector2(220, 22),
|
||||
Size = new Vector2(LabelWidth, 24),
|
||||
String = value,
|
||||
TextColor = ColorHelper.GetColor(3),
|
||||
};
|
||||
AddNode(itemLabel);
|
||||
});
|
||||
|
||||
var removeButton = new TextButtonNode
|
||||
AddNode(new CircleButtonNode
|
||||
{
|
||||
Size = new Vector2(50, 22),
|
||||
String = "X",
|
||||
Size = new Vector2(28, 28),
|
||||
Icon = ButtonIcon.Cross,
|
||||
OnClick = () => OnRemove?.Invoke(),
|
||||
};
|
||||
AddNode(removeButton);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,76 +1,79 @@
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using KamiToolKit.Classes;
|
||||
using KamiToolKit.Nodes;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using KamiToolKit.Classes;
|
||||
using KamiToolKit.Nodes;
|
||||
|
||||
namespace AetherBags.Nodes.Configuration.Category;
|
||||
|
||||
public sealed class UintListEditorNode : VerticalListNode
|
||||
{
|
||||
private List<uint> _list;
|
||||
private readonly NumericInputNode _addInput;
|
||||
private const float LabelWidth = 300f;
|
||||
private const float RowHeight = 28f;
|
||||
|
||||
private List<uint> _list = [];
|
||||
|
||||
private readonly LabelTextNode _headerLabel;
|
||||
private readonly VerticalListNode _itemsContainer;
|
||||
private readonly Action? _onChanged;
|
||||
private readonly Func<uint, string>? _labelResolver;
|
||||
private readonly HorizontalListNode _addRow;
|
||||
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;
|
||||
_onChanged = onChanged;
|
||||
_labelResolver = labelResolver;
|
||||
get => _headerLabel.String;
|
||||
init => _headerLabel.String = value;
|
||||
}
|
||||
|
||||
public UintListEditorNode()
|
||||
{
|
||||
FitContents = true;
|
||||
ItemSpacing = 4.0f;
|
||||
|
||||
var headerLabel = new LabelTextNode
|
||||
_headerLabel = new LabelTextNode
|
||||
{
|
||||
TextFlags = TextFlags.AutoAdjustNodeSize,
|
||||
Size = new Vector2(280, 18),
|
||||
String = label,
|
||||
TextColor = ColorHelper.GetColor(8),
|
||||
};
|
||||
AddNode(headerLabel);
|
||||
AddNode(_headerLabel);
|
||||
|
||||
_itemsContainer = new VerticalListNode
|
||||
{
|
||||
FitContents = true,
|
||||
Size = new Vector2(LabelWidth + 40f, 0),
|
||||
ItemSpacing = 2.0f,
|
||||
FitContents = true,
|
||||
FirstItemSpacing = 2,
|
||||
};
|
||||
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
|
||||
{
|
||||
Size = new Vector2(120, 28),
|
||||
Size = new Vector2(120, RowHeight),
|
||||
Min = 0,
|
||||
Max = int.MaxValue,
|
||||
Value = 0,
|
||||
};
|
||||
addRow.AddNode(_addInput);
|
||||
_addRow.AddNode(_addInput);
|
||||
|
||||
var addButton = new TextButtonNode
|
||||
{
|
||||
Size = new Vector2(60, 28),
|
||||
Size = new Vector2(60, RowHeight),
|
||||
String = "Add",
|
||||
OnClick = () =>
|
||||
{
|
||||
var value = (uint)_addInput.Value;
|
||||
if (! _list.Contains(value))
|
||||
{
|
||||
_list.Add(value);
|
||||
RefreshItems();
|
||||
_onChanged?.Invoke();
|
||||
}
|
||||
},
|
||||
OnClick = AddCurrentValue,
|
||||
};
|
||||
addRow.AddNode(addButton);
|
||||
_addRow.AddNode(addButton);
|
||||
|
||||
AddNode(addRow);
|
||||
|
||||
RefreshItems();
|
||||
AddNode(_addRow);
|
||||
}
|
||||
|
||||
public void SetList(List<uint> newList)
|
||||
@@ -79,35 +82,53 @@ public sealed class UintListEditorNode : VerticalListNode
|
||||
RefreshItems();
|
||||
}
|
||||
|
||||
private void AddCurrentValue()
|
||||
{
|
||||
var value = (uint)_addInput.Value;
|
||||
if (!_list.Contains(value))
|
||||
{
|
||||
_list.Add(value);
|
||||
RefreshItems();
|
||||
OnChanged?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private void RefreshItems()
|
||||
{
|
||||
_itemsContainer.SyncWithListData(
|
||||
_list,
|
||||
node => node.Value,
|
||||
value => new UintListItemNode(value, _labelResolver)
|
||||
_itemsContainer.Clear();
|
||||
|
||||
foreach (var value in _list)
|
||||
{
|
||||
Size = new Vector2(280, 22),
|
||||
OnRemove = () =>
|
||||
{
|
||||
_list.Remove(value);
|
||||
RefreshItems();
|
||||
_onChanged?.Invoke();
|
||||
},
|
||||
_itemsContainer.AddNode(CreateItemNode(value));
|
||||
}
|
||||
|
||||
if (_list.Count == 0)
|
||||
{
|
||||
_itemsContainer.Height = 0;
|
||||
}
|
||||
);
|
||||
|
||||
_itemsContainer.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();
|
||||
OnChanged?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class UintListItemNode : HorizontalListNode
|
||||
{
|
||||
private const float LabelWidth = 300f;
|
||||
|
||||
public uint Value { get; }
|
||||
public Action? OnRemove { get; init; }
|
||||
|
||||
@@ -116,22 +137,22 @@ public sealed class UintListItemNode : HorizontalListNode
|
||||
Value = value;
|
||||
ItemSpacing = 4.0f;
|
||||
|
||||
var displayText = labelResolver != null ? $"{value} - {labelResolver(value)}" : value.ToString();
|
||||
var itemLabel = new LabelTextNode
|
||||
var displayText = labelResolver is not null
|
||||
? $"{value} - {labelResolver(value)}"
|
||||
: value.ToString();
|
||||
|
||||
AddNode(new LabelTextNode
|
||||
{
|
||||
TextFlags = TextFlags.AutoAdjustNodeSize,
|
||||
Size = new Vector2(220, 22),
|
||||
Size = new Vector2(LabelWidth, 24),
|
||||
String = displayText,
|
||||
TextColor = ColorHelper.GetColor(3),
|
||||
};
|
||||
AddNode(itemLabel);
|
||||
});
|
||||
|
||||
var removeButton = new TextButtonNode
|
||||
AddNode(new CircleButtonNode
|
||||
{
|
||||
Size = new Vector2(50, 22),
|
||||
String = "X",
|
||||
Size = new Vector2(28, 28),
|
||||
Icon = ButtonIcon.Cross,
|
||||
OnClick = () => OnRemove?.Invoke(),
|
||||
};
|
||||
AddNode(removeButton);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,7 @@ public sealed class ImportExportResetNode : HorizontalListNode
|
||||
Height = 32,
|
||||
Width = 100,
|
||||
String = "Reset",
|
||||
TextNode = { TextColor = ColorHelper.GetColor(50) },
|
||||
TextTooltip = " Reset configuration\n(hold button to confirm)",
|
||||
OnClick = ResetConfig
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user