Add search bar to editor Actors list.
This commit is contained in:
@@ -21,6 +21,22 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
{
|
{
|
||||||
public class ActorSelectorLogic : ChromeLogic
|
public class ActorSelectorLogic : ChromeLogic
|
||||||
{
|
{
|
||||||
|
class ActorSelectorActor
|
||||||
|
{
|
||||||
|
public readonly ActorInfo Actor;
|
||||||
|
public readonly string[] Categories;
|
||||||
|
public readonly string[] SearchTerms;
|
||||||
|
public readonly string Tooltip;
|
||||||
|
|
||||||
|
public ActorSelectorActor(ActorInfo actor, string[] categories, string[] searchTerms, string tooltip)
|
||||||
|
{
|
||||||
|
Actor = actor;
|
||||||
|
Categories = categories;
|
||||||
|
SearchTerms = searchTerms;
|
||||||
|
Tooltip = tooltip;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
readonly EditorViewportControllerWidget editor;
|
readonly EditorViewportControllerWidget editor;
|
||||||
readonly DropDownButtonWidget ownersDropDown;
|
readonly DropDownButtonWidget ownersDropDown;
|
||||||
readonly ScrollPanelWidget panel;
|
readonly ScrollPanelWidget panel;
|
||||||
@@ -28,10 +44,14 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
readonly Ruleset mapRules;
|
readonly Ruleset mapRules;
|
||||||
readonly World world;
|
readonly World world;
|
||||||
readonly WorldRenderer worldRenderer;
|
readonly WorldRenderer worldRenderer;
|
||||||
readonly List<string> allCategories;
|
readonly string[] allCategories;
|
||||||
readonly List<string> selectedCategories = new List<string>();
|
readonly HashSet<string> selectedCategories = new HashSet<string>();
|
||||||
|
readonly List<string> filteredCategories = new List<string>();
|
||||||
|
|
||||||
|
readonly ActorSelectorActor[] allActors;
|
||||||
|
|
||||||
PlayerReference selectedOwner;
|
PlayerReference selectedOwner;
|
||||||
|
string searchFilter;
|
||||||
|
|
||||||
[ObjectCreator.UseCtor]
|
[ObjectCreator.UseCtor]
|
||||||
public ActorSelectorLogic(Widget widget, World world, WorldRenderer worldRenderer)
|
public ActorSelectorLogic(Widget widget, World world, WorldRenderer worldRenderer)
|
||||||
@@ -77,25 +97,119 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
ownersDropDown.Text = selectedOwner.Name;
|
ownersDropDown.Text = selectedOwner.Name;
|
||||||
ownersDropDown.TextColor = selectedOwner.Color.RGB;
|
ownersDropDown.TextColor = selectedOwner.Color.RGB;
|
||||||
|
|
||||||
var actorCategorySelector = widget.Get<DropDownButtonWidget>("ACTOR_CATEGORY");
|
|
||||||
var filtersPanel = Ui.LoadWidget("ACTOR_CATEGORY_FILTER_PANEL", null, new WidgetArgs());
|
|
||||||
var categoryTemplate = filtersPanel.Get<CheckboxWidget>("CATEGORY_TEMPLATE");
|
|
||||||
var tileSetId = world.Map.Rules.TileSet.Id;
|
var tileSetId = world.Map.Rules.TileSet.Id;
|
||||||
allCategories = mapRules.Actors.Where(a => !a.Value.Name.Contains('^')).Select(a => a.Value.TraitInfoOrDefault<EditorTilesetFilterInfo>())
|
var allActorsTemp = new List<ActorSelectorActor>();
|
||||||
.Where(i => i != null && i.Categories != null &&
|
foreach (var a in mapRules.Actors.Values)
|
||||||
!(i.ExcludeTilesets != null && i.ExcludeTilesets.Contains(tileSetId)) && !(i.RequireTilesets != null && !i.RequireTilesets.Contains(tileSetId)))
|
{
|
||||||
.SelectMany(i => i.Categories).Distinct().OrderBy(i => i).ToList();
|
// Partial templates are not allowed
|
||||||
selectedCategories.AddRange(allCategories);
|
if (a.Name.Contains('^'))
|
||||||
|
continue;
|
||||||
|
|
||||||
var selectButtons = filtersPanel.Get<ContainerWidget>("SELECT_CATEGORIES_BUTTONS");
|
// Actor must have a preview associated with it
|
||||||
filtersPanel.AddChild(selectButtons);
|
if (!a.HasTraitInfo<IRenderActorPreviewInfo>())
|
||||||
filtersPanel.Bounds.Height = Math.Min(allCategories.Count * categoryTemplate.Bounds.Height + 5 + selectButtons.Bounds.Height, panel.Bounds.Height);
|
continue;
|
||||||
|
|
||||||
|
var editorData = a.TraitInfoOrDefault<EditorTilesetFilterInfo>();
|
||||||
|
|
||||||
|
// Actor must be included in at least one category
|
||||||
|
if (editorData == null || editorData.Categories == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Excluded by tileset
|
||||||
|
if (editorData.ExcludeTilesets != null && editorData.ExcludeTilesets.Contains(tileSetId))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (editorData.RequireTilesets != null && !editorData.RequireTilesets.Contains(tileSetId))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var tooltip = a.TraitInfos<EditorOnlyTooltipInfo>().FirstOrDefault(ti => ti.EnabledByDefault) as TooltipInfoBase
|
||||||
|
?? a.TraitInfos<TooltipInfo>().FirstOrDefault(ti => ti.EnabledByDefault);
|
||||||
|
|
||||||
|
var searchTerms = new List<string>() { a.Name };
|
||||||
|
if (tooltip != null)
|
||||||
|
searchTerms.Add(tooltip.Name);
|
||||||
|
|
||||||
|
var tooltipText = (tooltip == null ? "Type: " : tooltip.Name + "\nType: ") + a.Name;
|
||||||
|
allActorsTemp.Add(new ActorSelectorActor(a, editorData.Categories, searchTerms.ToArray(), tooltipText));
|
||||||
|
}
|
||||||
|
|
||||||
|
allActors = allActorsTemp.ToArray();
|
||||||
|
|
||||||
|
allCategories = allActors.SelectMany(ac => ac.Categories)
|
||||||
|
.Distinct()
|
||||||
|
.OrderBy(x => x)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
foreach (var c in allCategories)
|
||||||
|
{
|
||||||
|
selectedCategories.Add(c);
|
||||||
|
filteredCategories.Add(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
var searchTextField = widget.Get<TextFieldWidget>("SEARCH_TEXTFIELD");
|
||||||
|
searchTextField.OnTextEdited = () =>
|
||||||
|
{
|
||||||
|
searchFilter = searchTextField.Text.Trim();
|
||||||
|
filteredCategories.Clear();
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(searchFilter))
|
||||||
|
filteredCategories.AddRange(
|
||||||
|
allActors.Where(t => t.SearchTerms.Any(
|
||||||
|
s => s.IndexOf(searchFilter, StringComparison.OrdinalIgnoreCase) >= 0))
|
||||||
|
.SelectMany(t => t.Categories)
|
||||||
|
.Distinct()
|
||||||
|
.OrderBy(x => x));
|
||||||
|
else
|
||||||
|
filteredCategories.AddRange(allCategories);
|
||||||
|
|
||||||
|
InitializeActorPreviews();
|
||||||
|
};
|
||||||
|
|
||||||
|
var actorCategorySelector = widget.Get<DropDownButtonWidget>("CATEGORIES_DROPDOWN");
|
||||||
|
actorCategorySelector.GetText = () =>
|
||||||
|
{
|
||||||
|
if (selectedCategories.Count == 0)
|
||||||
|
return "None";
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(searchFilter))
|
||||||
|
return "Search Results";
|
||||||
|
|
||||||
|
if (selectedCategories.Count == 1)
|
||||||
|
return selectedCategories.First();
|
||||||
|
|
||||||
|
if (selectedCategories.Count == allCategories.Length)
|
||||||
|
return "All";
|
||||||
|
|
||||||
|
return "Multiple";
|
||||||
|
};
|
||||||
|
|
||||||
|
actorCategorySelector.OnMouseDown = _ =>
|
||||||
|
{
|
||||||
|
if (searchTextField != null)
|
||||||
|
searchTextField.YieldKeyboardFocus();
|
||||||
|
|
||||||
|
actorCategorySelector.RemovePanel();
|
||||||
|
actorCategorySelector.AttachPanel(CreateCategoriesPanel());
|
||||||
|
};
|
||||||
|
|
||||||
|
InitializeActorPreviews();
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget CreateCategoriesPanel()
|
||||||
|
{
|
||||||
|
var categoriesPanel = Ui.LoadWidget("ACTOR_CATEGORY_FILTER_PANEL", null, new WidgetArgs());
|
||||||
|
var categoryTemplate = categoriesPanel.Get<CheckboxWidget>("CATEGORY_TEMPLATE");
|
||||||
|
|
||||||
|
var selectButtons = categoriesPanel.Get<ContainerWidget>("SELECT_CATEGORIES_BUTTONS");
|
||||||
|
categoriesPanel.AddChild(selectButtons);
|
||||||
|
|
||||||
var selectAll = selectButtons.Get<ButtonWidget>("SELECT_ALL");
|
var selectAll = selectButtons.Get<ButtonWidget>("SELECT_ALL");
|
||||||
selectAll.OnClick = () =>
|
selectAll.OnClick = () =>
|
||||||
{
|
{
|
||||||
selectedCategories.Clear();
|
selectedCategories.Clear();
|
||||||
selectedCategories.AddRange(allCategories);
|
foreach (var c in allCategories)
|
||||||
|
selectedCategories.Add(c);
|
||||||
|
|
||||||
InitializeActorPreviews();
|
InitializeActorPreviews();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -106,13 +220,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
InitializeActorPreviews();
|
InitializeActorPreviews();
|
||||||
};
|
};
|
||||||
|
|
||||||
actorCategorySelector.OnMouseDown = _ =>
|
var categoryHeight = 5 + selectButtons.Bounds.Height;
|
||||||
{
|
foreach (var cat in filteredCategories)
|
||||||
actorCategorySelector.RemovePanel();
|
|
||||||
actorCategorySelector.AttachPanel(filtersPanel);
|
|
||||||
};
|
|
||||||
|
|
||||||
foreach (var cat in allCategories)
|
|
||||||
{
|
{
|
||||||
var category = (CheckboxWidget)categoryTemplate.Clone();
|
var category = (CheckboxWidget)categoryTemplate.Clone();
|
||||||
category.GetText = () => cat;
|
category.GetText = () => cat;
|
||||||
@@ -120,18 +229,19 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
category.IsVisible = () => true;
|
category.IsVisible = () => true;
|
||||||
category.OnClick = () =>
|
category.OnClick = () =>
|
||||||
{
|
{
|
||||||
if (selectedCategories.Contains(cat))
|
if (!selectedCategories.Remove(cat))
|
||||||
selectedCategories.Remove(cat);
|
|
||||||
else
|
|
||||||
selectedCategories.Add(cat);
|
selectedCategories.Add(cat);
|
||||||
|
|
||||||
InitializeActorPreviews();
|
InitializeActorPreviews();
|
||||||
};
|
};
|
||||||
|
|
||||||
filtersPanel.AddChild(category);
|
categoriesPanel.AddChild(category);
|
||||||
|
categoryHeight += categoryTemplate.Bounds.Height;
|
||||||
}
|
}
|
||||||
|
|
||||||
InitializeActorPreviews();
|
categoriesPanel.Bounds.Height = Math.Min(categoryHeight, panel.Bounds.Height);
|
||||||
|
|
||||||
|
return categoriesPanel;
|
||||||
}
|
}
|
||||||
|
|
||||||
void InitializeActorPreviews()
|
void InitializeActorPreviews()
|
||||||
@@ -140,29 +250,15 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
if (!selectedCategories.Any())
|
if (!selectedCategories.Any())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var actors = mapRules.Actors.Where(a => !a.Value.Name.Contains('^'))
|
foreach (var a in allActors)
|
||||||
.Select(a => a.Value);
|
|
||||||
var tileSetId = world.Map.Rules.TileSet.Id;
|
|
||||||
|
|
||||||
foreach (var a in actors)
|
|
||||||
{
|
{
|
||||||
var actor = a;
|
if (!selectedCategories.Overlaps(a.Categories))
|
||||||
if (actor.HasTraitInfo<BridgeInfo>()) // bridge layer takes care about that automatically
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (!actor.HasTraitInfo<IRenderActorPreviewInfo>())
|
if (!string.IsNullOrEmpty(searchFilter) && !a.SearchTerms.Any(s => s.IndexOf(searchFilter, StringComparison.OrdinalIgnoreCase) >= 0))
|
||||||
continue;
|
|
||||||
|
|
||||||
var filter = actor.TraitInfoOrDefault<EditorTilesetFilterInfo>();
|
|
||||||
if (filter == null || filter.Categories == null || !filter.Categories.Intersect(selectedCategories).Any())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (filter.ExcludeTilesets != null && filter.ExcludeTilesets.Contains(tileSetId))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (filter.RequireTilesets != null && !filter.RequireTilesets.Contains(tileSetId))
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
var actor = a.Actor;
|
||||||
var td = new TypeDictionary();
|
var td = new TypeDictionary();
|
||||||
td.Add(new OwnerInit(selectedOwner.Name));
|
td.Add(new OwnerInit(selectedOwner.Name));
|
||||||
td.Add(new FactionInit(selectedOwner.Faction));
|
td.Add(new FactionInit(selectedOwner.Faction));
|
||||||
@@ -192,10 +288,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
item.Bounds.Height = preview.Bounds.Height + 2 * preview.Bounds.Y;
|
item.Bounds.Height = preview.Bounds.Height + 2 * preview.Bounds.Y;
|
||||||
item.IsVisible = () => true;
|
item.IsVisible = () => true;
|
||||||
|
|
||||||
var tooltip = actor.TraitInfos<EditorOnlyTooltipInfo>().FirstOrDefault(Exts.IsTraitEnabled) as TooltipInfoBase
|
item.GetTooltipText = () => a.Tooltip;
|
||||||
?? actor.TraitInfos<TooltipInfo>().FirstOrDefault(Exts.IsTraitEnabled);
|
|
||||||
|
|
||||||
item.GetTooltipText = () => (tooltip == null ? "Type: " : tooltip.Name + "\nType: ") + actor.Name;
|
|
||||||
|
|
||||||
panel.AddChild(item);
|
panel.AddChild(item);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -316,18 +316,49 @@ Container@EDITOR_WORLD_ROOT:
|
|||||||
Width: PARENT_RIGHT
|
Width: PARENT_RIGHT
|
||||||
Height: PARENT_BOTTOM
|
Height: PARENT_BOTTOM
|
||||||
Children:
|
Children:
|
||||||
DropDownButton@OWNERS_DROPDOWN:
|
Background:
|
||||||
Width: PARENT_RIGHT
|
Width: 61
|
||||||
|
Height: 75
|
||||||
|
Background: panel-black
|
||||||
|
Children:
|
||||||
|
Label@SEARCH_LABEL:
|
||||||
|
Width: PARENT_RIGHT - 5
|
||||||
|
Height: 25
|
||||||
|
Text: Search:
|
||||||
|
Align: Right
|
||||||
|
Font: TinyBold
|
||||||
|
Label@CATEGORIES_LABEL:
|
||||||
|
Y: 24
|
||||||
|
Width: PARENT_RIGHT - 5
|
||||||
|
Height: 25
|
||||||
|
Text: Filter:
|
||||||
|
Align: Right
|
||||||
|
Font: TinyBold
|
||||||
|
Label@OWNERS_LABEL:
|
||||||
|
Y: 48
|
||||||
|
Width: PARENT_RIGHT - 5
|
||||||
|
Height: 25
|
||||||
|
Text: Owner:
|
||||||
|
Align: Right
|
||||||
|
Font: TinyBold
|
||||||
|
TextField@SEARCH_TEXTFIELD:
|
||||||
|
X: 60
|
||||||
|
Width: PARENT_RIGHT - 60
|
||||||
|
Height: 25
|
||||||
|
DropDownButton@CATEGORIES_DROPDOWN:
|
||||||
|
X: 60
|
||||||
|
Y: 24
|
||||||
|
Width: PARENT_RIGHT - 60
|
||||||
Height: 25
|
Height: 25
|
||||||
Font: Bold
|
Font: Bold
|
||||||
DropDownButton@ACTOR_CATEGORY:
|
DropDownButton@OWNERS_DROPDOWN:
|
||||||
Y: 25
|
X: 60
|
||||||
Width: PARENT_RIGHT
|
Y: 48
|
||||||
|
Width: PARENT_RIGHT - 60
|
||||||
Height: 25
|
Height: 25
|
||||||
Text: Categories
|
|
||||||
Font: Bold
|
Font: Bold
|
||||||
ScrollPanel@ACTORTEMPLATE_LIST:
|
ScrollPanel@ACTORTEMPLATE_LIST:
|
||||||
Y: 50
|
Y: 72
|
||||||
Width: PARENT_RIGHT
|
Width: PARENT_RIGHT
|
||||||
Height: PARENT_BOTTOM - 50
|
Height: PARENT_BOTTOM - 50
|
||||||
TopBottomSpacing: 4
|
TopBottomSpacing: 4
|
||||||
@@ -417,7 +448,7 @@ Container@EDITOR_WORLD_ROOT:
|
|||||||
Contrast: true
|
Contrast: true
|
||||||
|
|
||||||
ScrollPanel@ACTOR_CATEGORY_FILTER_PANEL:
|
ScrollPanel@ACTOR_CATEGORY_FILTER_PANEL:
|
||||||
Width: 250
|
Width: 190
|
||||||
Children:
|
Children:
|
||||||
Container@SELECT_CATEGORIES_BUTTONS:
|
Container@SELECT_CATEGORIES_BUTTONS:
|
||||||
Width: PARENT_RIGHT
|
Width: PARENT_RIGHT
|
||||||
@@ -426,15 +457,15 @@ ScrollPanel@ACTOR_CATEGORY_FILTER_PANEL:
|
|||||||
Button@SELECT_ALL:
|
Button@SELECT_ALL:
|
||||||
X: 10
|
X: 10
|
||||||
Y: 2
|
Y: 2
|
||||||
Width: 100
|
Width: 60
|
||||||
Height: 25
|
Height: 25
|
||||||
Text: Select all
|
Text: All
|
||||||
Button@SELECT_NONE:
|
Button@SELECT_NONE:
|
||||||
X: 120
|
X: PARENT_RIGHT - WIDTH - 34
|
||||||
Y: 2
|
Y: 2
|
||||||
Width: 100
|
Width: 60
|
||||||
Height: 25
|
Height: 25
|
||||||
Text: Select none
|
Text: None
|
||||||
Checkbox@CATEGORY_TEMPLATE:
|
Checkbox@CATEGORY_TEMPLATE:
|
||||||
X: 5
|
X: 5
|
||||||
Y: 5
|
Y: 5
|
||||||
|
|||||||
@@ -289,24 +289,49 @@ Container@EDITOR_WORLD_ROOT:
|
|||||||
Width: 240
|
Width: 240
|
||||||
Height: WINDOW_BOTTOM - 382
|
Height: WINDOW_BOTTOM - 382
|
||||||
Children:
|
Children:
|
||||||
DropDownButton@OWNERS_DROPDOWN:
|
Label@SEARCH_LABEL:
|
||||||
X: 10
|
|
||||||
Y: 10
|
Y: 10
|
||||||
Width: 220
|
Width: 55
|
||||||
|
Height: 25
|
||||||
|
Text: Search:
|
||||||
|
Align: Right
|
||||||
|
Font: TinyBold
|
||||||
|
TextField@SEARCH_TEXTFIELD:
|
||||||
|
X: 60
|
||||||
|
Y: 10
|
||||||
|
Width: PARENT_RIGHT - 70
|
||||||
|
Height: 25
|
||||||
|
Label@CATEGORIES_LABEL:
|
||||||
|
Y: 34
|
||||||
|
Width: 55
|
||||||
|
Height: 25
|
||||||
|
Text: Filter:
|
||||||
|
Align: Right
|
||||||
|
Font: TinyBold
|
||||||
|
DropDownButton@CATEGORIES_DROPDOWN:
|
||||||
|
X: 60
|
||||||
|
Y: 34
|
||||||
|
Width: PARENT_RIGHT - 70
|
||||||
Height: 25
|
Height: 25
|
||||||
Font: Bold
|
Font: Bold
|
||||||
DropDownButton@ACTOR_CATEGORY:
|
Label@OWNERS_LABEL:
|
||||||
X: 10
|
Y: 58
|
||||||
Y: 35
|
Width: 55
|
||||||
Width: 220
|
Height: 25
|
||||||
|
Text: Owner:
|
||||||
|
Align: Right
|
||||||
|
Font: TinyBold
|
||||||
|
DropDownButton@OWNERS_DROPDOWN:
|
||||||
|
X: 60
|
||||||
|
Y: 58
|
||||||
|
Width: PARENT_RIGHT - 70
|
||||||
Height: 25
|
Height: 25
|
||||||
Text: Categories
|
|
||||||
Font: Bold
|
Font: Bold
|
||||||
ScrollPanel@ACTORTEMPLATE_LIST:
|
ScrollPanel@ACTORTEMPLATE_LIST:
|
||||||
X: 10
|
X: 10
|
||||||
Y: 60
|
Y: 82
|
||||||
Width: 220
|
Width: PARENT_RIGHT - 20
|
||||||
Height: PARENT_BOTTOM - 70
|
Height: PARENT_BOTTOM - 92
|
||||||
TopBottomSpacing: 4
|
TopBottomSpacing: 4
|
||||||
ItemSpacing: 4
|
ItemSpacing: 4
|
||||||
Children:
|
Children:
|
||||||
@@ -403,25 +428,25 @@ Container@EDITOR_WORLD_ROOT:
|
|||||||
Contrast: true
|
Contrast: true
|
||||||
|
|
||||||
ScrollPanel@ACTOR_CATEGORY_FILTER_PANEL:
|
ScrollPanel@ACTOR_CATEGORY_FILTER_PANEL:
|
||||||
Width: 220
|
Width: 170
|
||||||
Children:
|
Children:
|
||||||
Container@SELECT_CATEGORIES_BUTTONS:
|
Container@SELECT_CATEGORIES_BUTTONS:
|
||||||
Width: 220
|
Width: PARENT_RIGHT
|
||||||
Height: 29
|
Height: 29
|
||||||
Children:
|
Children:
|
||||||
Button@SELECT_ALL:
|
Button@SELECT_ALL:
|
||||||
X: 5
|
X: 10
|
||||||
Y: 2
|
Y: 2
|
||||||
Width: 90
|
Width: 60
|
||||||
Height: 25
|
Height: 25
|
||||||
Text: Select all
|
Text: All
|
||||||
Font: Bold
|
Font: Bold
|
||||||
Button@SELECT_NONE:
|
Button@SELECT_NONE:
|
||||||
X: 100
|
X: PARENT_RIGHT - WIDTH - 34
|
||||||
Y: 2
|
Y: 2
|
||||||
Width: 90
|
Width: 60
|
||||||
Height: 25
|
Height: 25
|
||||||
Text: Select none
|
Text: None
|
||||||
Font: Bold
|
Font: Bold
|
||||||
Checkbox@CATEGORY_TEMPLATE:
|
Checkbox@CATEGORY_TEMPLATE:
|
||||||
X: 5
|
X: 5
|
||||||
|
|||||||
Reference in New Issue
Block a user