using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using UnityEngine; using UnityEngine.UI; using Vanguard.Api; using Vanguard.Api.Contracts; using Vanguard.Auth; using Vanguard.Meta; namespace Vanguard.UI { /// /// Декбилдер в духе HS: коллекция в центре, узкая колонка колод/карт справа. /// Список колод и редактор используют один и тот же блок коллекции. /// public sealed class DeckBuilderView { private readonly MetaService _meta; private readonly AuthService _auth; private readonly Action _onBack; private readonly Action _onPlay; private readonly RectTransform _root; private readonly RectTransform _listView; private readonly RectTransform _editorView; private RectTransform _deckListContent; private RectTransform _listCollectionContent; private RectTransform _editorCollectionContent; private RectTransform _deckCardContent; private InputField _nameInput; private Text _listStatus; private Text _editorStatus; private Text _stats; private Text _deckCount; private Text _filterSummary; private RectTransform _filterOverlay; private RectTransform _clanContent; private RectTransform _confirmOverlay; private Text _confirmText; private DeckDto _pendingDelete; private readonly List _decks = new List(); private readonly List _collection = new List(); private readonly Dictionary _catalog = new Dictionary(StringComparer.Ordinal); private readonly List _cards = new List(); private readonly Dictionary _listCountTexts = new Dictionary(StringComparer.Ordinal); private readonly Dictionary _editorCountTexts = new Dictionary(StringComparer.Ordinal); private DeckDto _editing; private string _filterClan = "Все"; private string _filterTrigger = "Все"; private int _filterGrade = -1; private bool _loading; private bool _busy; public DeckBuilderView( Transform parent, MetaService meta, AuthService auth, Action onBack, Action onPlay) { _meta = meta; _auth = auth; _onBack = onBack; _onPlay = onPlay; _root = UiKit.CreatePanel(parent, "DeckBuilderScreen", new Color(0.018f, 0.03f, 0.06f, 0.97f)); _listView = BuildListView(); _editorView = BuildEditorView(); _filterOverlay = BuildFilterOverlay(); _confirmOverlay = BuildDeleteConfirm(); _listView.gameObject.SetActive(false); _editorView.gameObject.SetActive(false); _filterOverlay.gameObject.SetActive(false); _confirmOverlay.gameObject.SetActive(false); _root.gameObject.SetActive(false); } public void Show() { UiKit.SetActive(_root, true); ShowList(); _ = LoadAsync(); } public void Hide() { _filterOverlay.gameObject.SetActive(false); _confirmOverlay.gameObject.SetActive(false); _root.gameObject.SetActive(false); } private RectTransform BuildListView() { var view = UiKit.CreatePanel(_root, "DeckList", new Color(0.018f, 0.03f, 0.06f, 0.97f)); var top = BuildTopBar(view); var back = UiKit.CreateButton(top, "Back", "← В лобби", () => _onBack(), 17); SetFixedSize(back, 120f, 44f); var title = UiKit.CreateText(top, "Title", "Колоды", 28, Color.white, TextAnchor.MiddleLeft); title.fontStyle = FontStyle.Bold; SetFixedSize(title, 150f, 44f); _filterSummary = UiKit.CreateText( top, "FilterSummary", "Все карты", 16, new Color(0.60f, 0.68f, 0.84f), TextAnchor.MiddleLeft); SetFlexible(_filterSummary, 1f, 44f); var filters = UiKit.CreateButton(top, "Filters", "Фильтры", OpenFilters, 17); SetFixedSize(filters, 110f, 44f); var reset = UiKit.CreateButton(top, "ResetFilters", "Сброс", ResetFilters, 17); SetFixedSize(reset, 90f, 44f); var create = UiKit.CreateButton(top, "Create", "Создать колоду", NewDeck, 17); SetFixedSize(create, 180f, 44f); var body = UiKit.CreatePanel(view, "Body", new Color(0f, 0f, 0f, 0f)); body.anchorMin = Vector2.zero; body.anchorMax = Vector2.one; body.offsetMin = new Vector2(34f, 28f); body.offsetMax = new Vector2(-34f, -108f); UiKit.AddHorizontalLayout(body, 0, 14); var collectionArea = UiKit.CreatePanel( body, "CollectionArea", new Color(0.04f, 0.06f, 0.10f, 0.55f)); var collectionElement = collectionArea.gameObject.AddComponent(); collectionElement.flexibleWidth = 1f; collectionElement.flexibleHeight = 1f; collectionElement.minHeight = 300f; UiKit.AddVerticalLayout(collectionArea, 8, 6); var scroll = UiKit.CreateGridScrollView( collectionArea, "CollectionGrid", new Vector2(174f, 238f), new Vector2(10f, 10f), out _listCollectionContent); var scrollElement = scroll.gameObject.AddComponent(); scrollElement.flexibleHeight = 1f; scrollElement.minHeight = 220f; _listStatus = UiKit.CreateText( collectionArea, "Status", string.Empty, 16, new Color(0.62f, 0.68f, 0.82f), TextAnchor.MiddleLeft); UiKit.SetPreferredHeight(_listStatus, 32f); var deckArea = UiKit.CreatePanel( body, "DeckArea", new Color(0.04f, 0.06f, 0.10f, 0.85f)); var deckElement = deckArea.gameObject.AddComponent(); deckElement.preferredWidth = 300f; deckElement.minWidth = 280f; deckElement.flexibleWidth = 0f; deckElement.flexibleHeight = 1f; deckElement.minHeight = 300f; UiKit.AddVerticalLayout(deckArea, 10, 6); var deckTitle = UiKit.CreateText( deckArea, "Title", "Мои колоды", 20, Color.white, TextAnchor.MiddleLeft); deckTitle.fontStyle = FontStyle.Bold; UiKit.SetPreferredHeight(deckTitle, 30f); var deckScroll = UiKit.CreateScrollView(deckArea, "DeckListScroll", out _deckListContent, 8f); var deckScrollElement = deckScroll.gameObject.AddComponent(); deckScrollElement.flexibleHeight = 1f; deckScrollElement.minHeight = 200f; var hint = UiKit.CreateText( deckArea, "Hint", "Нажмите на колоду,\nчтобы открыть редактор.", 14, new Color(0.58f, 0.66f, 0.80f), TextAnchor.MiddleLeft); hint.horizontalOverflow = HorizontalWrapMode.Wrap; UiKit.SetPreferredHeight(hint, 46f); return view; } private RectTransform BuildEditorView() { var view = UiKit.CreatePanel(_root, "DeckEditor", new Color(0.018f, 0.03f, 0.06f, 0.97f)); var top = BuildTopBar(view); var back = UiKit.CreateButton(top, "Back", "← Колоды", BackToList, 17); SetFixedSize(back, 120f, 44f); _nameInput = UiKit.CreateInput(top, "Name", "Название колоды"); SetFlexible(_nameInput, 0.7f, 46f); var filters = UiKit.CreateButton(top, "Filters", "Фильтры", OpenFilters, 17); SetFixedSize(filters, 110f, 44f); var reset = UiKit.CreateButton(top, "ResetFilters", "Сброс", ResetFilters, 17); SetFixedSize(reset, 90f, 44f); _stats = UiKit.CreateText( top, "Stats", string.Empty, 15, new Color(0.72f, 0.78f, 0.92f), TextAnchor.MiddleLeft); _stats.horizontalOverflow = HorizontalWrapMode.Wrap; SetFlexible(_stats, 1.4f, 50f); var save = UiKit.CreateButton(top, "Save", "Сохранить", SaveDeck, 17); SetFixedSize(save, 120f, 44f); var duel = UiKit.CreateButton(top, "Duel", "В бой", PlayDeck, 17); SetFixedSize(duel, 90f, 44f); var delete = UiKit.CreateButton(top, "Delete", "Удалить", RequestDeleteCurrent, 17); SetFixedSize(delete, 100f, 44f); var body = UiKit.CreatePanel(view, "Body", new Color(0f, 0f, 0f, 0f)); body.anchorMin = Vector2.zero; body.anchorMax = Vector2.one; body.offsetMin = new Vector2(34f, 28f); body.offsetMax = new Vector2(-34f, -108f); UiKit.AddHorizontalLayout(body, 0, 14); var collectionArea = UiKit.CreatePanel( body, "CollectionArea", new Color(0.04f, 0.06f, 0.10f, 0.55f)); var collectionElement = collectionArea.gameObject.AddComponent(); collectionElement.flexibleWidth = 1f; collectionElement.flexibleHeight = 1f; collectionElement.minHeight = 300f; UiKit.AddVerticalLayout(collectionArea, 8, 6); var scroll = UiKit.CreateGridScrollView( collectionArea, "CollectionGrid", new Vector2(174f, 238f), new Vector2(10f, 10f), out _editorCollectionContent); var scrollElement = scroll.gameObject.AddComponent(); scrollElement.flexibleHeight = 1f; scrollElement.minHeight = 220f; _editorStatus = UiKit.CreateText( collectionArea, "Status", string.Empty, 16, new Color(0.62f, 0.68f, 0.82f), TextAnchor.MiddleLeft); UiKit.SetPreferredHeight(_editorStatus, 32f); var deckArea = UiKit.CreatePanel( body, "DeckArea", new Color(0.04f, 0.06f, 0.10f, 0.85f)); var deckElement = deckArea.gameObject.AddComponent(); deckElement.preferredWidth = 300f; deckElement.minWidth = 280f; deckElement.flexibleWidth = 0f; deckElement.flexibleHeight = 1f; deckElement.minHeight = 300f; UiKit.AddVerticalLayout(deckArea, 10, 6); _deckCount = UiKit.CreateText( deckArea, "DeckCount", "Колода: 0/50", 19, Color.white, TextAnchor.MiddleLeft); _deckCount.fontStyle = FontStyle.Bold; UiKit.SetPreferredHeight(_deckCount, 28f); var deckScroll = UiKit.CreateScrollView(deckArea, "DeckCardScroll", out _deckCardContent, 6f); var deckScrollElement = deckScroll.gameObject.AddComponent(); deckScrollElement.flexibleHeight = 1f; deckScrollElement.minHeight = 200f; var hint = UiKit.CreateText( deckArea, "Hint", "Нажмите на карту,\nчтобы убрать одну копию.", 14, new Color(0.58f, 0.66f, 0.80f), TextAnchor.MiddleLeft); hint.horizontalOverflow = HorizontalWrapMode.Wrap; UiKit.SetPreferredHeight(hint, 46f); return view; } private RectTransform BuildTopBar(RectTransform view) { var top = UiKit.CreatePanel(view, "TopBar", new Color(0.06f, 0.08f, 0.13f, 1f)); top.anchorMin = new Vector2(0f, 1f); top.anchorMax = new Vector2(1f, 1f); top.pivot = new Vector2(0.5f, 1f); top.anchoredPosition = Vector2.zero; top.sizeDelta = new Vector2(0f, 88f); UiKit.AddHorizontalLayout(top, 18, 12); return top; } private RectTransform BuildFilterOverlay() { var overlay = UiKit.CreatePanel(_root, "FilterOverlay", new Color(0f, 0f, 0f, 0.78f)); var card = UiKit.CreateCenteredCard( overlay, "FilterCard", new Vector2(860f, 820f), new Color(0.06f, 0.08f, 0.13f, 1f)); UiKit.AddVerticalLayout(card, 26, 10); var title = UiKit.CreateText(card, "Title", "Фильтры коллекции", 28, Color.white); title.fontStyle = FontStyle.Bold; UiKit.SetPreferredHeight(title, 40f); AddFilterSection(card, "Ранг", new[] { "Все", "0", "1", "2", "3" }, OnGradeFilter); AddFilterSection(card, "Триггер", new[] { "Все", "None", "Critical", "Draw", "Stand", "Heal" }, OnTriggerFilter); var clanTitle = UiKit.CreateText( card, "ClanTitle", "Клан", 17, new Color(0.66f, 0.74f, 0.90f), TextAnchor.MiddleLeft); UiKit.SetPreferredHeight(clanTitle, 24f); var clanScroll = UiKit.CreateScrollView(card, "ClanScroll", out _clanContent, 4f); var clanElement = clanScroll.gameObject.AddComponent(); clanElement.flexibleHeight = 1f; clanElement.minHeight = 150f; var close = UiKit.CreateButton(card, "Close", "Готово", CloseFilters, 20); UiKit.SetPreferredHeight(close, 48f); return overlay; } private void RebuildClanFilters() { ClearChildren(_clanContent); AddClanButton(_clanContent, "Все"); foreach (var clan in _catalog.Values.Select(item => item.Clan).Distinct().OrderBy(item => item)) AddClanButton(_clanContent, clan); } private RectTransform BuildDeleteConfirm() { var overlay = UiKit.CreatePanel( _root, "DeleteDeckConfirm", new Color(0f, 0f, 0f, 0.78f)); var card = UiKit.CreateCenteredCard( overlay, "ConfirmCard", new Vector2(640f, 300f), new Color(0.06f, 0.08f, 0.13f, 1f)); UiKit.AddVerticalLayout(card, 30, 12); var title = UiKit.CreateText(card, "Title", "Удалить колоду?", 28, Color.white); title.fontStyle = FontStyle.Bold; UiKit.SetPreferredHeight(title, 42f); _confirmText = UiKit.CreateText( card, "Text", string.Empty, 19, new Color(0.72f, 0.78f, 0.92f)); UiKit.SetPreferredHeight(_confirmText, 70f); var yes = UiKit.CreateButton(card, "Yes", "Удалить", ConfirmDelete, 20); UiKit.SetPreferredHeight(yes, 48f); var no = UiKit.CreateButton(card, "No", "Отмена", CancelDelete, 20); UiKit.SetPreferredHeight(no, 44f); return overlay; } private void AddFilterSection(Transform parent, string title, string[] labels, Action onSelected) { var caption = UiKit.CreateText( parent, "Caption_" + title, title, 17, new Color(0.66f, 0.74f, 0.90f), TextAnchor.MiddleLeft); UiKit.SetPreferredHeight(caption, 24f); var row = UiKit.CreatePanel(parent, "Row_" + title, new Color(0f, 0f, 0f, 0f)); UiKit.SetPreferredHeight(row, 44f); UiKit.AddHorizontalLayout(row, 0, 6); foreach (var label in labels) { var captured = label; var button = UiKit.CreateButton( row, "Filter_" + label, label, () => onSelected(captured), 16); UiKit.SetPreferredHeight(button, 40f); } } private void AddClanButton(Transform parent, string clan) { var captured = clan; var button = UiKit.CreateButton( parent, "Clan_" + clan, clan, () => OnClanFilter(captured), 16); UiKit.SetPreferredHeight(button, 40f); } private async Task LoadAsync() { if (_loading) return; _loading = true; _listStatus.text = "Загружаем коллекцию и колоды…"; _listStatus.color = new Color(0.62f, 0.68f, 0.82f); ClearChildren(_deckListContent); try { await _auth.EnsureAccessAsync(); var collection = await _meta.GetCollectionAsync(); var catalog = await _meta.GetCatalogAsync(); var decks = await _meta.GetDecksAsync(); if (!_root.gameObject.activeSelf) return; _collection.Clear(); _collection.AddRange(collection.Items ?? new List()); _catalog.Clear(); foreach (var item in catalog) _catalog[item.PrintingId] = item; RebuildClanFilters(); _decks.Clear(); _decks.AddRange(decks); BuildDeckList(); RebuildCollectionGrids(); _listStatus.text = _decks.Count == 0 ? "Колод пока нет. Создайте первую." : "Карт в коллекции: " + _collection.Count + "."; } catch (ApiException exception) { _listStatus.text = exception.Message; _listStatus.color = new Color(1f, 0.55f, 0.45f); } catch (Exception exception) { Debug.LogException(exception); _listStatus.text = "Не удалось загрузить декбилдер."; _listStatus.color = new Color(1f, 0.55f, 0.45f); } finally { _loading = false; } } private void BuildDeckList() { ClearChildren(_deckListContent); if (_decks.Count == 0) { var empty = UiKit.CreateText( _deckListContent, "Empty", "Нет колод", 16, new Color(0.58f, 0.66f, 0.80f)); UiKit.SetPreferredHeight(empty, 90f); return; } foreach (var deck in _decks) { var row = UiKit.CreatePanel( _deckListContent, "DeckRow_" + deck.Id, new Color(0.08f, 0.11f, 0.17f, 1f)); var rowElement = row.gameObject.AddComponent(); rowElement.preferredHeight = 76f; UiKit.AddVerticalLayout(row, 10, 2); var button = row.gameObject.AddComponent