using System.Collections.Generic; using UnityEditor; using UnityEditor.UIElements; using UnityEngine; using UnityEngine.UIElements; 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 and CraftingRecipe). The left pane lists every asset of /// the active tab; the right pane edits the selection through a dedicated custom view /// (ItemEditorView / RecipeEditorView), 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 } #endregion #region Constants private const string UssPath = "Assets/GAME/Script/Editor/Database/AshwildDatabase.uss"; #endregion #region State private Tab activeTab = Tab.Items; private string searchFilter = string.Empty; 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; #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 two family tabs, a search field, and the database rebuild action. /// 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"); toolbar.Add(itemsTab); toolbar.Add(recipesTab); VisualElement spacer = new VisualElement(); spacer.AddToClassList("ash-toolbar-spacer"); toolbar.Add(spacer); ToolbarSearchField search = new ToolbarSearchField(); search.AddToClassList("ash-search"); search.RegisterValueChangedCallback(evt => { searchFilter = evt.newValue ?? string.Empty; RefreshList(); }); toolbar.Add(search); 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); selectedAsset = null; RefreshList(); ShowSelection(); } #endregion #region Left Pane /// /// Builds the left pane: a styled list of the active family plus a footer "New" button. /// private VisualElement BuildLeftPane() { VisualElement left = new VisualElement(); left.AddToClassList("ash-left"); 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 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