using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using Vanguard.Api;
using Vanguard.Api.Contracts;
using Vanguard.Auth;
using Vanguard.Battle;
using Vanguard.Meta;
namespace Vanguard.UI
{
///
/// Декбилдер: слева коллекция с мультифильтрами, справа панель, которая
/// показывает либо список колод, либо карты открытой колоды.
///
public sealed class DeckBuilderView
{
private const float PanelWidth = 420f;
private const float RowHeight = 92f;
private static readonly Color TextPrimary = new Color(0.88f, 0.93f, 1f);
private static readonly Color TextMuted = new Color(0.62f, 0.70f, 0.84f);
private static readonly Color TextDim = new Color(0.50f, 0.58f, 0.72f);
private static readonly Color Accent = new Color(0.30f, 0.65f, 1f);
private static readonly Color PanelFill = new Color(0.06f, 0.08f, 0.14f, 0.96f);
private static readonly Color RowFill = new Color(0.08f, 0.11f, 0.18f, 1f);
private static readonly Color ChipOn = new Color(0.16f, 0.40f, 0.80f, 0.95f);
private static readonly Color ChipOff = new Color(0.05f, 0.10f, 0.22f, 0.9f);
private readonly MetaService _meta;
private readonly AuthService _auth;
private readonly Action _onPlay;
private readonly Action _onPracticeStarted;
private readonly RectTransform _root;
private RectTransform _collectionContent;
private RectTransform _deckListPanel;
private RectTransform _deckPanel;
private RectTransform _deckListContent;
private RectTransform _deckCardContent;
private InputField _nameInput;
private Text _status;
private Text _filterSummary;
private Text _stats;
private Text _deckCount;
private Button _saveButton;
private Button _botButton;
private Button _duelButton;
private Button _deleteButton;
private RectTransform _deckMenuOverlay;
private Text _deckMenuTitle;
private DeckDto _deckMenuDeck;
private RectTransform _filterOverlay;
private RectTransform _rarityChips;
private RectTransform _gradeChips;
private RectTransform _triggerChips;
private RectTransform _clanChips;
private RectTransform _confirmOverlay;
private InputField _searchInput;
private Text _confirmText;
private DeckDto _pendingDelete;
private RectTransform _cardOverlay;
private Image _cardArt;
private Text _cardArtName;
private Text _cardTitle;
private Text _cardSubtitle;
private Text _cardStats;
private Text _cardAbilities;
private Text _cardOwned;
private Text _cardStatus;
private Button _dismantleButton;
private Button _craftButton;
private CollectionItemDto _selectedItem;
private CardCatalogItemDto _selectedMeta;
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 _countTexts =
new Dictionary(StringComparer.Ordinal);
private readonly DeckFilter _filter = new DeckFilter();
private readonly DeckFilter _draftFilter = new DeckFilter();
private DeckDto _editing;
private int _dust;
private bool _dirty;
private bool _deckOpen;
private bool _loading;
private bool _busy;
public DeckBuilderView(
Transform parent,
MetaService meta,
AuthService auth,
Action onBack,
Action onPlay,
Action onPracticeStarted)
{
_meta = meta;
_auth = auth;
_onPlay = onPlay;
_onPracticeStarted = onPracticeStarted;
_root = UiKit.CreatePanel(parent, "DeckBuilderScreen", new Color(0.018f, 0.03f, 0.06f, 0.97f));
BuildBody();
_filterOverlay = BuildFilterOverlay();
_confirmOverlay = BuildDeleteConfirm();
_deckMenuOverlay = BuildDeckMenu();
_cardOverlay = BuildCardOverlay();
_filterOverlay.gameObject.SetActive(false);
_confirmOverlay.gameObject.SetActive(false);
_deckMenuOverlay.gameObject.SetActive(false);
_cardOverlay.gameObject.SetActive(false);
_root.gameObject.SetActive(false);
}
public RectTransform Root => _root;
/// Esc: закрывает верхний оверлей, если он открыт.
public void CloseTopmost()
{
if (_cardOverlay.gameObject.activeSelf)
{
CloseCardOverlay();
return;
}
if (_confirmOverlay.gameObject.activeSelf)
{
CancelDelete();
return;
}
if (_deckMenuOverlay.gameObject.activeSelf)
{
CloseDeckMenu();
return;
}
if (_filterOverlay.gameObject.activeSelf)
CloseFilters();
}
public void Show()
{
UiKit.SetActive(_root, true);
ShowDeckList();
_ = LoadAsync();
}
public void Hide()
{
_filterOverlay.gameObject.SetActive(false);
_confirmOverlay.gameObject.SetActive(false);
_deckMenuOverlay.gameObject.SetActive(false);
_cardOverlay.gameObject.SetActive(false);
_root.gameObject.SetActive(false);
}
// ------------------------------------------------------------ каркас
private void BuildBody()
{
var body = UiKit.CreatePanel(_root, "Body", new Color(0f, 0f, 0f, 0f));
body.anchorMin = Vector2.zero;
body.anchorMax = Vector2.one;
body.offsetMin = new Vector2(34f, 24f);
body.offsetMax = new Vector2(-34f, -120f);
UiKit.AddHorizontalLayout(body, 0, 14);
var bodyLayout = body.GetComponent();
if (bodyLayout != null)
bodyLayout.childForceExpandWidth = false;
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 filterBar = UiKit.CreatePanel(collectionArea, "FilterBar", new Color(0f, 0f, 0f, 0f));
UiKit.SetPreferredHeight(filterBar, 44f);
UiKit.AddHorizontalLayout(filterBar, 0, 8);
_filterSummary = UiKit.CreateText(
filterBar, "FilterSummary", "Все карты", 15, TextMuted, TextAnchor.MiddleLeft);
_filterSummary.horizontalOverflow = HorizontalWrapMode.Wrap;
SetFlexible(_filterSummary, 1f, 44f);
var filters = UiKit.CreateButton(filterBar, "Filters", "Фильтры", OpenFilters, 16);
SetFixedSize(filters, 110f, 38f);
var reset = UiKit.CreateButton(filterBar, "ResetFilters", "Сброс", ResetFilters, 16);
SetFixedSize(reset, 90f, 38f);
var scroll = UiKit.CreateGridScrollView(
collectionArea,
"CollectionGrid",
new Vector2(270f, 440f),
new Vector2(14f, 14f),
out _collectionContent);
var scrollElement = scroll.gameObject.AddComponent();
scrollElement.flexibleHeight = 1f;
scrollElement.minHeight = 220f;
var grid = _collectionContent.GetComponent();
if (grid != null)
grid.childAlignment = TextAnchor.UpperLeft;
_status = UiKit.CreateText(
collectionArea, "Status", string.Empty, 15,
TextMuted, TextAnchor.MiddleLeft);
_status.horizontalOverflow = HorizontalWrapMode.Wrap;
UiKit.SetPreferredHeight(_status, 30f);
var right = UiKit.CreatePanel(
body, "RightPanel", new Color(0.04f, 0.06f, 0.10f, 0.85f));
var rightElement = right.gameObject.AddComponent();
rightElement.preferredWidth = PanelWidth;
rightElement.minWidth = PanelWidth;
rightElement.flexibleWidth = 0f;
rightElement.flexibleHeight = 1f;
rightElement.minHeight = 300f;
_deckListPanel = BuildDeckListPanel(right);
_deckPanel = BuildDeckPanel(right);
_deckListPanel.gameObject.SetActive(true);
_deckPanel.gameObject.SetActive(false);
}
private RectTransform BuildDeckListPanel(RectTransform parent)
{
var panel = UiKit.CreatePanel(parent, "DeckListPanel", new Color(0f, 0f, 0f, 0f));
UiKit.Stretch(panel);
UiKit.AddVerticalLayout(panel, 10, 8);
var create = UiKit.CreateButton(panel, "Create", "+ Новый набор", NewDeck, 17);
UiKit.SetPreferredHeight(create, 52f);
SetButtonColor(create, new Color(0.10f, 0.28f, 0.58f, 0.95f));
var scroll = UiKit.CreateScrollView(panel, "DeckListScroll", out _deckListContent, 8f);
var scrollElement = scroll.gameObject.AddComponent();
scrollElement.flexibleHeight = 1f;
scrollElement.minHeight = 200f;
var hint = UiKit.CreateText(
panel, "Hint", "Нажмите на колоду,\nчтобы открыть редактор.", 13,
TextDim, TextAnchor.MiddleLeft);
hint.horizontalOverflow = HorizontalWrapMode.Wrap;
UiKit.SetPreferredHeight(hint, 42f);
return panel;
}
private RectTransform BuildDeckPanel(RectTransform parent)
{
var panel = UiKit.CreatePanel(parent, "DeckPanel", new Color(0f, 0f, 0f, 0f));
UiKit.Stretch(panel);
UiKit.AddVerticalLayout(panel, 10, 6);
var header = UiKit.CreatePanel(panel, "Header", new Color(0f, 0f, 0f, 0f));
UiKit.SetPreferredHeight(header, 36f);
UiKit.AddHorizontalLayout(header, 0, 8);
var back = UiKit.CreateButton(header, "BackToList", "← К списку колод", BackToDeckList, 14);
SetFixedSize(back, 180f, 34f);
_deckCount = UiKit.CreateText(
header, "DeckCount", "0/50", 16, TextPrimary, TextAnchor.MiddleRight);
_deckCount.fontStyle = FontStyle.Bold;
SetFlexible(_deckCount, 1f, 34f);
_nameInput = UiKit.CreateInput(panel, "Name", "Название колоды");
UiKit.SetPreferredHeight(_nameInput, 44f);
_nameInput.onValueChanged.AddListener(_ =>
{
_dirty = true;
UpdateStats();
});
_stats = UiKit.CreateText(
panel, "Stats", string.Empty, 13, TextMuted, TextAnchor.UpperLeft);
_stats.horizontalOverflow = HorizontalWrapMode.Wrap;
UiKit.SetPreferredHeight(_stats, 44f);
var scroll = UiKit.CreateScrollView(panel, "DeckCardScroll", out _deckCardContent, 6f);
var scrollElement = scroll.gameObject.AddComponent();
scrollElement.flexibleHeight = 1f;
scrollElement.minHeight = 200f;
_saveButton = UiKit.CreateButton(panel, "Save", "Сохранить", SaveDeck, 16);
UiKit.SetPreferredHeight(_saveButton, 42f);
var row = UiKit.CreatePanel(panel, "Actions", new Color(0f, 0f, 0f, 0f));
UiKit.SetPreferredHeight(row, 40f);
UiKit.AddHorizontalLayout(row, 0, 8);
_botButton = UiKit.CreateButton(row, "Bot", "С ботом", PlayBot, 15);
_duelButton = UiKit.CreateButton(row, "Duel", "В бой", PlayDeck, 15);
SetFlexible(_botButton, 1f, 38f);
SetFlexible(_duelButton, 1f, 38f);
_deleteButton = UiKit.CreateButton(panel, "Delete", "Удалить колоду", RequestDeleteCurrent, 14);
UiKit.SetPreferredHeight(_deleteButton, 34f);
SetButtonColor(_deleteButton, new Color(0.30f, 0.08f, 0.11f, 0.95f));
var hint = UiKit.CreateText(
panel, "Hint", "ЛКМ — убрать копию,\nПКМ — посмотреть карту.", 13,
TextDim, TextAnchor.MiddleLeft);
hint.horizontalOverflow = HorizontalWrapMode.Wrap;
UiKit.SetPreferredHeight(hint, 36f);
return panel;
}
// ------------------------------------------------------------ оверлеи
private RectTransform BuildFilterOverlay()
{
var overlay = UiKit.CreatePanel(_root, "FilterOverlay", new Color(0f, 0f, 0f, 0.78f));
var card = UiKit.CreateCenteredCard(
overlay, "FilterCard", new Vector2(900f, 940f), PanelFill);
UiKit.AddVerticalLayout(card, 24, 10);
var title = UiKit.CreateText(card, "Title", "Фильтры коллекции", 28, TextPrimary);
title.fontStyle = FontStyle.Bold;
UiKit.SetPreferredHeight(title, 40f);
_searchInput = UiKit.CreateInput(card, "Search", "Поиск по названию");
UiKit.SetPreferredHeight(_searchInput, 44f);
_searchInput.onValueChanged.AddListener(value => _draftFilter.Search = value);
_rarityChips = BuildFilterSection(card, "Редкость", 44f);
_gradeChips = BuildFilterSection(card, "Ранг", 44f);
_triggerChips = BuildFilterSection(card, "Триггер", 44f);
_clanChips = BuildFilterSection(card, "Клан", 260f, flexible: true);
var footer = UiKit.CreatePanel(card, "Footer", new Color(0f, 0f, 0f, 0f));
UiKit.SetPreferredHeight(footer, 48f);
UiKit.AddHorizontalLayout(footer, 0, 8);
var reset = UiKit.CreateButton(footer, "Reset", "Сбросить", ResetDraftFilters, 16);
SetFixedSize(reset, 200f, 44f);
var cancel = UiKit.CreateButton(footer, "Cancel", "Отмена", CloseFilters, 16);
SetFixedSize(cancel, 180f, 44f);
var apply = UiKit.CreateButton(footer, "Apply", "Применить", ApplyFilters, 16);
SetFixedSize(apply, 220f, 44f);
return overlay;
}
private static RectTransform BuildFilterSection(
RectTransform parent,
string title,
float height,
bool flexible = false)
{
var caption = UiKit.CreateText(
parent, "Caption_" + title, title, 17, TextMuted, TextAnchor.MiddleLeft);
UiKit.SetPreferredHeight(caption, 24f);
var container = UiKit.CreatePanel(parent, "Chips_" + title, new Color(0f, 0f, 0f, 0f));
var element = container.gameObject.AddComponent();
element.preferredHeight = height;
if (flexible)
{
element.flexibleHeight = 1f;
element.minHeight = 140f;
}
else
{
element.flexibleHeight = 0f;
}
if (flexible)
{
var scroll = UiKit.CreateScrollView(container, "Scroll", out var content, 4f);
UiKit.Stretch(scroll);
return content;
}
UiKit.AddHorizontalLayout(container, 0, 6);
return container;
}
private RectTransform BuildDeckMenu()
{
var overlay = UiKit.CreatePanel(_root, "DeckMenuOverlay", new Color(0f, 0f, 0f, 0.78f));
var card = UiKit.CreateCenteredCard(
overlay, "MenuCard", new Vector2(520f, 320f), PanelFill);
UiKit.AddVerticalLayout(card, 28, 10);
_deckMenuTitle = UiKit.CreateText(
card, "Title", string.Empty, 24, TextPrimary);
_deckMenuTitle.fontStyle = FontStyle.Bold;
UiKit.MakeLiteral(_deckMenuTitle);
UiKit.SetPreferredHeight(_deckMenuTitle, 40f);
var open = UiKit.CreateButton(card, "Open", "Открыть", OpenDeckFromMenu, 17);
UiKit.SetPreferredHeight(open, 48f);
var delete = UiKit.CreateButton(card, "Delete", "Удалить", DeleteDeckFromMenu, 17);
UiKit.SetPreferredHeight(delete, 48f);
SetButtonColor(delete, new Color(0.30f, 0.08f, 0.11f, 0.95f));
var cancel = UiKit.CreateButton(card, "Cancel", "Отмена", CloseDeckMenu, 17);
UiKit.SetPreferredHeight(cancel, 48f);
return overlay;
}
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), PanelFill);
UiKit.AddVerticalLayout(card, 30, 12);
var title = UiKit.CreateText(card, "Title", "Удалить колоду?", 28, TextPrimary);
title.fontStyle = FontStyle.Bold;
UiKit.SetPreferredHeight(title, 42f);
_confirmText = UiKit.CreateText(
card, "Text", string.Empty, 19, TextMuted);
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 RectTransform BuildCardOverlay()
{
var overlay = UiKit.CreatePanel(_root, "CardOverlay", new Color(0f, 0f, 0f, 0.82f));
var card = UiKit.CreateCenteredCard(
overlay, "CardView", new Vector2(1040f, 660f), new Color(0.05f, 0.07f, 0.11f, 1f));
var body = UiKit.CreatePanel(card, "Body", new Color(0f, 0f, 0f, 0f));
body.anchorMin = Vector2.zero;
body.anchorMax = Vector2.one;
body.offsetMin = new Vector2(26f, 26f);
body.offsetMax = new Vector2(-26f, -26f);
UiKit.AddHorizontalLayout(body, 0, 22);
var art = UiKit.CreatePanel(body, "Art", new Color(0.2f, 0.3f, 0.5f, 1f));
SetFixedSize(art, 360f, 540f);
_cardArt = art.GetComponent();
_cardArtName = UiKit.CreateText(
art, "ArtName", string.Empty, 18,
new Color(1f, 1f, 1f, 0.92f), TextAnchor.MiddleCenter);
UiKit.MakeLiteral(_cardArtName);
UiKit.Stretch(_cardArtName.rectTransform);
_cardArtName.rectTransform.offsetMin = new Vector2(14f, 16f);
_cardArtName.rectTransform.offsetMax = new Vector2(-14f, -16f);
_cardArtName.horizontalOverflow = HorizontalWrapMode.Wrap;
var info = UiKit.CreatePanel(body, "Info", new Color(0f, 0f, 0f, 0f));
var infoElement = info.gameObject.AddComponent();
infoElement.flexibleWidth = 1f;
infoElement.flexibleHeight = 1f;
UiKit.AddVerticalLayout(info, 4, 6);
_cardTitle = UiKit.CreateText(
info, "Title", string.Empty, 30, Color.white, TextAnchor.MiddleLeft);
UiKit.MakeLiteral(_cardTitle);
_cardTitle.fontStyle = FontStyle.Bold;
_cardTitle.horizontalOverflow = HorizontalWrapMode.Wrap;
UiKit.SetPreferredHeight(_cardTitle, 44f);
_cardSubtitle = UiKit.CreateText(
info, "Subtitle", string.Empty, 16, TextMuted, TextAnchor.MiddleLeft);
UiKit.MakeLiteral(_cardSubtitle);
UiKit.SetPreferredHeight(_cardSubtitle, 26f);
_cardStats = UiKit.CreateText(
info, "Stats", string.Empty, 15,
new Color(0.72f, 0.78f, 0.92f), TextAnchor.UpperLeft);
_cardStats.horizontalOverflow = HorizontalWrapMode.Wrap;
UiKit.SetPreferredHeight(_cardStats, 88f);
var abilitiesCaption = UiKit.CreateText(
info, "AbilitiesCaption", "Способности", 16, TextMuted, TextAnchor.MiddleLeft);
UiKit.SetPreferredHeight(abilitiesCaption, 24f);
_cardAbilities = UiKit.CreateText(
info, "Abilities", string.Empty, 15,
new Color(0.82f, 0.86f, 0.96f), TextAnchor.UpperLeft);
UiKit.MakeLiteral(_cardAbilities);
_cardAbilities.horizontalOverflow = HorizontalWrapMode.Wrap;
var abilitiesElement = _cardAbilities.gameObject.AddComponent();
abilitiesElement.flexibleHeight = 1f;
abilitiesElement.minHeight = 100f;
_cardOwned = UiKit.CreateText(
info, "Owned", string.Empty, 15,
new Color(0.98f, 0.82f, 0.42f), TextAnchor.MiddleLeft);
UiKit.SetPreferredHeight(_cardOwned, 24f);
_cardStatus = UiKit.CreateText(
info, "Status", string.Empty, 14, TextDim, TextAnchor.UpperLeft);
_cardStatus.horizontalOverflow = HorizontalWrapMode.Wrap;
UiKit.SetPreferredHeight(_cardStatus, 38f);
var actions = UiKit.CreatePanel(info, "Actions", new Color(0f, 0f, 0f, 0f));
UiKit.SetPreferredHeight(actions, 50f);
UiKit.AddHorizontalLayout(actions, 0, 8);
_dismantleButton = UiKit.CreateButton(
actions, "Dismantle", "Разобрать", DismantleSelected, 16);
SetFixedSize(_dismantleButton, 225f, 46f);
_craftButton = UiKit.CreateButton(
actions, "Craft", "Скрафтить", CraftSelected, 16);
SetFixedSize(_craftButton, 225f, 46f);
var close = UiKit.CreateButton(actions, "Close", "Закрыть", CloseCardOverlay, 16);
SetFixedSize(close, 110f, 46f);
return overlay;
}
// ------------------------------------------------------------ загрузка
private async Task LoadAsync()
{
if (_loading)
return;
_loading = true;
SetStatus("Загружаем коллекцию и колоды…", false);
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());
_dust = collection.Balances != null ? collection.Balances.Dust : 0;
_catalog.Clear();
foreach (var item in catalog)
_catalog[item.PrintingId] = item;
_decks.Clear();
_decks.AddRange(decks);
BuildDeckList();
RebuildCollection();
SetStatus(
_decks.Count == 0
? "Колод пока нет. Пыль: " + _dust + ". ПКМ по карте — подробности."
: "Карт в коллекции: " + _collection.Count + " · пыль " + _dust
+ " · ПКМ по карте — подробности.",
false);
}
catch (ApiException exception)
{
SetStatus(exception.Message, true);
}
catch (Exception exception)
{
Debug.LogException(exception);
SetStatus("Не удалось загрузить декбилдер.", true);
}
finally
{
_loading = false;
}
}
// ------------------------------------------------------------ список колод
private void BuildDeckList()
{
ClearChildren(_deckListContent);
if (_decks.Count == 0)
{
var empty = UiKit.CreateText(
_deckListContent, "Empty", "Нет колод", 15, TextDim);
UiKit.SetPreferredHeight(empty, 90f);
return;
}
foreach (var deck in _decks)
{
var row = UiKit.CreatePanel(
_deckListContent, "DeckRow_" + deck.Id, RowFill);
var rowElement = row.gameObject.AddComponent();
rowElement.preferredHeight = RowHeight;
var button = row.gameObject.AddComponent