diff --git a/OpenRA.Mods.Common/Widgets/Logic/Editor/TileSelectorLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Editor/TileSelectorLogic.cs index 741dffb3c0..94fc44fd54 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Editor/TileSelectorLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Editor/TileSelectorLogic.cs @@ -10,6 +10,7 @@ #endregion using System; +using System.Collections.Generic; using System.Linq; using OpenRA.Graphics; using OpenRA.Widgets; @@ -18,52 +19,123 @@ namespace OpenRA.Mods.Common.Widgets.Logic { public class TileSelectorLogic : ChromeLogic { + class TileSelectorTemplate + { + public readonly TerrainTemplateInfo Template; + public readonly string[] Categories; + public readonly string[] SearchTerms; + public readonly string Tooltip; + + public TileSelectorTemplate(TerrainTemplateInfo template) + { + Template = template; + Categories = template.Categories; + Tooltip = template.Id.ToString(); + SearchTerms = new[] { Tooltip }; + } + } + + readonly TileSet tileset; + readonly WorldRenderer worldRenderer; readonly EditorViewportControllerWidget editor; readonly ScrollPanelWidget panel; readonly ScrollItemWidget itemTemplate; + readonly TileSelectorTemplate[] allTemplates; + + string selectedCategory; + string userSelectedCategory; + string searchFilter; [ObjectCreator.UseCtor] public TileSelectorLogic(Widget widget, WorldRenderer worldRenderer) { - var rules = worldRenderer.World.Map.Rules; - var tileset = rules.TileSet; + tileset = worldRenderer.World.Map.Rules.TileSet; + this.worldRenderer = worldRenderer; editor = widget.Parent.Get("MAP_EDITOR"); panel = widget.Get("TILETEMPLATE_LIST"); itemTemplate = panel.Get("TILEPREVIEW_TEMPLATE"); panel.Layout = new GridLayout(panel); - var tileCategorySelector = widget.Get("TILE_CATEGORY"); - var categories = tileset.EditorTemplateOrder; + allTemplates = tileset.Templates.Values.Select(t => new TileSelectorTemplate(t)).ToArray(); + + var orderedCategories = allTemplates.SelectMany(t => t.Categories) + .Distinct() + .OrderBy(CategoryOrder) + .ToArray(); + + var searchTextField = widget.Get("SEARCH_TEXTFIELD"); + searchTextField.OnTextEdited = () => + { + searchFilter = searchTextField.Text.Trim(); + selectedCategory = string.IsNullOrEmpty(searchFilter) ? userSelectedCategory : null; + + InitializeTilePreview(); + }; + + Func categoryTitle = s => s != null ? s : "Search Results"; Func setupItem = (option, template) => { - var item = ScrollItemWidget.Setup(template, - () => tileCategorySelector.Text == option, - () => { tileCategorySelector.Text = option; IntializeTilePreview(widget, worldRenderer, tileset, option); }); + var item = ScrollItemWidget.Setup(template, () => selectedCategory == option, () => + { + selectedCategory = option; + if (option != null) + userSelectedCategory = option; - item.Get("LABEL").GetText = () => option; + InitializeTilePreview(); + }); + + var title = categoryTitle(option); + item.Get("LABEL").GetText = () => title; return item; }; + var tileCategorySelector = widget.Get("CATEGORIES_DROPDOWN"); tileCategorySelector.OnClick = () => - tileCategorySelector.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 270, categories, setupItem); + { + if (searchTextField != null) + searchTextField.YieldKeyboardFocus(); - tileCategorySelector.Text = categories.First(); - IntializeTilePreview(widget, worldRenderer, tileset, categories.First()); + var categories = orderedCategories.AsEnumerable(); + if (!string.IsNullOrEmpty(searchFilter)) + { + var filteredCategories = allTemplates.Where(t => t.SearchTerms.Any( + s => s.IndexOf(searchFilter, StringComparison.OrdinalIgnoreCase) >= 0)) + .SelectMany(t => t.Categories) + .Distinct() + .OrderBy(CategoryOrder); + categories = new string[] { null }.Concat(filteredCategories); + } + + tileCategorySelector.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 270, categories, setupItem); + }; + + var actorCategorySelector = widget.Get("CATEGORIES_DROPDOWN"); + actorCategorySelector.GetText = () => categoryTitle(selectedCategory); + + selectedCategory = userSelectedCategory = orderedCategories.First(); + InitializeTilePreview(); } - void IntializeTilePreview(Widget widget, WorldRenderer worldRenderer, TileSet tileset, string category) + int CategoryOrder(string category) + { + var i = tileset.EditorTemplateOrder.IndexOf(category); + return i >= 0 ? i : int.MaxValue; + } + + void InitializeTilePreview() { panel.RemoveChildren(); - var categoryTiles = tileset.Templates.Where(t => t.Value.Categories.Contains(category)).Select(t => t.Value).ToList(); - var tileIds = categoryTiles.Where(t => t.Categories[0] == category) - .Concat(categoryTiles.Where(t => t.Categories[0] != category)) - .Select(t => t.Id); - - foreach (var t in tileIds) + foreach (var t in allTemplates) { - var tileId = t; + if (selectedCategory != null && !t.Categories.Contains(selectedCategory)) + continue; + + if (!string.IsNullOrEmpty(searchFilter) && !t.SearchTerms.Any(s => s.IndexOf(searchFilter, StringComparison.OrdinalIgnoreCase) >= 0)) + continue; + + var tileId = t.Template.Id; var item = ScrollItemWidget.Setup(itemTemplate, () => { var brush = editor.CurrentBrush as EditorTileBrush; return brush != null && brush.Template == tileId; }, () => editor.SetBrush(new EditorTileBrush(editor, tileId, worldRenderer))); @@ -86,7 +158,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic item.Bounds.Width = preview.Bounds.Width + 2 * preview.Bounds.X; item.Bounds.Height = preview.Bounds.Height + 2 * preview.Bounds.Y; item.IsVisible = () => true; - item.GetTooltipText = () => tileId.ToString(); + item.GetTooltipText = () => t.Tooltip; panel.AddChild(item); } diff --git a/mods/cnc/chrome/editor.yaml b/mods/cnc/chrome/editor.yaml index 6933db0051..d34e668ec0 100644 --- a/mods/cnc/chrome/editor.yaml +++ b/mods/cnc/chrome/editor.yaml @@ -258,14 +258,38 @@ Container@EDITOR_WORLD_ROOT: Width: PARENT_RIGHT Height: PARENT_BOTTOM Children: - DropDownButton@TILE_CATEGORY: - Width: PARENT_RIGHT + Background: + Width: 61 + Height: 50 + 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 + TextField@SEARCH_TEXTFIELD: + X: 60 + Width: PARENT_RIGHT - 60 + Height: 25 + DropDownButton@CATEGORIES_DROPDOWN: + X: 60 + Y: 24 + Width: PARENT_RIGHT - 60 Height: 25 Font: Bold ScrollPanel@TILETEMPLATE_LIST: - Y: 24 + Y: 48 Width: PARENT_RIGHT - Height: PARENT_BOTTOM - 24 + Height: PARENT_BOTTOM - 48 TopBottomSpacing: 4 ItemSpacing: 4 Children: diff --git a/mods/common/chrome/editor.yaml b/mods/common/chrome/editor.yaml index 6ba38bde29..fd61be7b6a 100644 --- a/mods/common/chrome/editor.yaml +++ b/mods/common/chrome/editor.yaml @@ -230,17 +230,36 @@ Container@EDITOR_WORLD_ROOT: Width: 240 Height: WINDOW_BOTTOM - 382 Children: - DropDownButton@TILE_CATEGORY: - X: 10 + Label@SEARCH_LABEL: 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 Font: Bold ScrollPanel@TILETEMPLATE_LIST: X: 10 - Y: 35 + Y: 58 Width: PARENT_RIGHT - 20 - Height: PARENT_BOTTOM - 45 + Height: PARENT_BOTTOM - 68 TopBottomSpacing: 4 ItemSpacing: 4 Children: