using System.Collections.Generic; using UnityEditor; using UnityEditor.UIElements; using UnityEngine; using UnityEngine.UIElements; using Ashwild.Building; using Ashwild.Crafting; using Ashwild.Inventory; namespace Ashwild.EditorTools { /// /// The Ashwild Database — a UI Toolkit window for browsing, creating and editing the project's /// authored ScriptableObjects (ItemData, CraftingRecipe and BuildableData). The left pane lists /// every asset of the active tab; the right pane edits the selection through a dedicated custom /// view (ItemEditorView / RecipeEditorView / BuildableEditorView), and for items offers one-click /// generation of the world pickup and in-hand prefabs around a chosen source object (see /// ItemAssetFactory). Pure UI: all asset/prefab work is delegated to the factories and views. /// public class AshwildDatabaseWindow : EditorWindow { #region Types /// /// Which asset family the left list currently shows. /// private enum Tab { Items, Recipes, Buildables } #endregion #region Constants private const string UssPath = "Assets/GAME/Script/Editor/Database/AshwildDatabase.uss"; private const string AllTypesLabel = "All Types"; #endregion #region State private Tab activeTab = Tab.Items; private string searchFilter = string.Empty; private string itemTypeFilter = AllTypesLabel; private readonly List sourceItems = new List(); private Object selectedAsset; #endregion #region UI References private ListView listView; private VisualElement rightPane; private Button itemsTab; private Button recipesTab; private Button buildablesTab; private PopupField typeFilterField; #endregion #region Menu /// /// Opens (or focuses) the database window. /// [MenuItem("Tools/Ashwild/Database")] public static void Open() { AshwildDatabaseWindow window = GetWindow(); window.titleContent = new GUIContent("Ashwild Database"); window.minSize = new Vector2(720, 420); } #endregion #region Window Lifecycle /// /// Builds the full window tree once when the window opens. /// private void CreateGUI() { VisualElement root = rootVisualElement; root.AddToClassList("ash-root"); StyleSheet sheet = AssetDatabase.LoadAssetAtPath(UssPath); if (sheet != null) root.styleSheets.Add(sheet); root.Add(BuildToolbar()); VisualElement body = new VisualElement(); body.AddToClassList("ash-body"); body.Add(BuildLeftPane()); body.Add(BuildRightPane()); root.Add(body); RefreshList(); } /// /// Rebuilds the list when assets change outside the window (created/deleted/renamed elsewhere). /// private void OnProjectChange() { if (listView != null) RefreshList(); } #endregion #region Toolbar /// /// Builds the top toolbar: the three family tabs and the database rebuild action. The search /// field and type filter live in the left-pane header, directly above the list they filter. /// private VisualElement BuildToolbar() { VisualElement toolbar = new VisualElement(); toolbar.AddToClassList("ash-toolbar"); itemsTab = MakeTab("Items", Tab.Items, "d_Prefab Icon"); recipesTab = MakeTab("Recipes", Tab.Recipes, "d_ScriptableObject Icon"); buildablesTab = MakeTab("Buildables", Tab.Buildables, "d_BuildSettings.Standalone"); toolbar.Add(itemsTab); toolbar.Add(recipesTab); toolbar.Add(buildablesTab); VisualElement spacer = new VisualElement(); spacer.AddToClassList("ash-toolbar-spacer"); toolbar.Add(spacer); Button rebuild = new Button(ItemDatabaseBuilder.Rebuild) { text = "Rebuild DB" }; rebuild.AddToClassList("ash-btn"); rebuild.tooltip = "Re-scan every ItemData and rewrite the network ItemDatabase."; toolbar.Add(rebuild); return toolbar; } /// /// Creates one toolbar tab button (icon + label) that switches the active family when clicked. /// The icon is a built-in editor icon resolved by name; it is simply skipped if not found. /// private Button MakeTab(string label, Tab tab, string iconName) { Button button = new Button(() => SetTab(tab)); button.AddToClassList("ash-tab"); if (tab == activeTab) button.AddToClassList("ash-tab--active"); Texture icon = EditorGUIUtility.IconContent(iconName).image; if (icon != null) { Image image = new Image { image = icon, scaleMode = ScaleMode.ScaleToFit }; image.AddToClassList("ash-tab__icon"); button.Add(image); } button.Add(new Label(label)); return button; } /// /// Switches the active family, repaints the tab highlight, clears the selection and reloads. /// private void SetTab(Tab tab) { if (tab == activeTab) return; activeTab = tab; itemsTab.EnableInClassList("ash-tab--active", tab == Tab.Items); recipesTab.EnableInClassList("ash-tab--active", tab == Tab.Recipes); buildablesTab.EnableInClassList("ash-tab--active", tab == Tab.Buildables); UpdateTypeFilterVisibility(); selectedAsset = null; RefreshList(); ShowSelection(); } #endregion #region Left Pane /// /// Builds the left pane: a filter header (search + item-type filter), a styled list of the /// active family, and a footer "New" button. /// private VisualElement BuildLeftPane() { VisualElement left = new VisualElement(); left.AddToClassList("ash-left"); left.Add(BuildListHeader()); listView = new ListView(sourceItems) { fixedItemHeight = 48, selectionType = SelectionType.Single, makeItem = MakeRow, bindItem = BindRow }; listView.AddToClassList("ash-list"); listView.selectionChanged += OnListSelectionChanged; left.Add(listView); VisualElement footer = new VisualElement(); footer.AddToClassList("ash-footer"); Button newButton = new Button(CreateNewAsset) { text = "+ New" }; newButton.AddToClassList("ash-btn"); newButton.AddToClassList("ash-btn--primary"); footer.Add(newButton); left.Add(footer); return left; } /// /// Builds the header sitting above the list: a search field that filters by name across every /// family, and an item-type dropdown that narrows the item list to a single type. The type /// filter is only meaningful for items, so it is hidden on the Recipes and Buildables tabs. /// private VisualElement BuildListHeader() { VisualElement header = new VisualElement(); header.AddToClassList("ash-listheader"); ToolbarSearchField search = new ToolbarSearchField(); search.AddToClassList("ash-listsearch"); search.RegisterValueChangedCallback(evt => { searchFilter = evt.newValue ?? string.Empty; RefreshList(); }); header.Add(search); List typeChoices = new List { AllTypesLabel }; typeChoices.AddRange(System.Enum.GetNames(typeof(ItemType))); typeFilterField = new PopupField(typeChoices, 0); typeFilterField.AddToClassList("ash-typefilter"); typeFilterField.tooltip = "Show only items of the selected type."; typeFilterField.RegisterValueChangedCallback(evt => { itemTypeFilter = evt.newValue ?? AllTypesLabel; RefreshList(); }); header.Add(typeFilterField); return header; } /// /// Shows the item-type dropdown only on the Items tab and resets it to "all" when leaving, so a /// stale type filter never silently narrows a list it cannot apply to. /// private void UpdateTypeFilterVisibility() { if (typeFilterField == null) return; bool showForItems = activeTab == Tab.Items; typeFilterField.style.display = showForItems ? DisplayStyle.Flex : DisplayStyle.None; if (!showForItems) { itemTypeFilter = AllTypesLabel; typeFilterField.SetValueWithoutNotify(AllTypesLabel); } } /// /// Builds the reusable visual for a single list row (icon, name, type tag). /// private VisualElement MakeRow() { VisualElement row = new VisualElement(); row.AddToClassList("ash-row"); VisualElement icon = new VisualElement { name = "icon" }; icon.AddToClassList("ash-row__icon"); row.Add(icon); Label label = new Label { name = "label" }; label.AddToClassList("ash-row__label"); row.Add(label); Label tag = new Label { name = "tag" }; tag.AddToClassList("ash-row__tag"); row.Add(tag); return row; } /// /// Fills a row visual from the asset at the given index (icon from the item/result sprite). /// private void BindRow(VisualElement element, int index) { Object asset = sourceItems[index]; element.Q