diff --git a/IMPLEMENTATION_CHECKLIST.md b/IMPLEMENTATION_CHECKLIST.md
index 6eaafe7..d541fac 100644
--- a/IMPLEMENTATION_CHECKLIST.md
+++ b/IMPLEMENTATION_CHECKLIST.md
@@ -245,8 +245,9 @@
список колод; магазин и декбилдер подключены.
- [x] Очередь: classic-ranked, опрос, отмена, тикет, переход в матч.
- [x] Простой декбилдер: фильтры (клан, грейд, триггер) в оверлее,
- сброс, статистика триггеров/грейдов, коллекция сеткой, текущая колода,
- создание/редактирование/сохранение, лимиты 50/4/owned.
+ сброс, сортировка клан→ранг→имя, карточная сетка с количеством,
+ узкий список колод справа, редактор с вертикальным списком карт,
+ статистика по рангам и общему числу, добавление/удаление кликом.
- [ ] Декбилдер: drag&drop, live-валидация, редкость/тип/арт, пресеты.
- [x] Базовая коллекция: список, балансы, разбор в пыль с подтверждением;
арты и «сколько в колодах» — дальше.
diff --git a/client/Assets/Vanguard/Runtime/UI/DeckBuilderView.cs b/client/Assets/Vanguard/Runtime/UI/DeckBuilderView.cs
index f8e33ab..54a781c 100644
--- a/client/Assets/Vanguard/Runtime/UI/DeckBuilderView.cs
+++ b/client/Assets/Vanguard/Runtime/UI/DeckBuilderView.cs
@@ -12,8 +12,8 @@ using Vanguard.Meta;
namespace Vanguard.UI
{
///
- /// Простой декбилдер: коллекция слева, текущая колода справа,
- /// фильтры в оверлее, статистика по триггерам и грейдам.
+ /// Декбилдер в духе HS: коллекция в центре, узкая колонка колод/карт справа.
+ /// Список колод и редактор используют один и тот же блок коллекции.
///
public sealed class DeckBuilderView
{
@@ -21,13 +21,14 @@ namespace Vanguard.UI
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 _listContent;
- private RectTransform _collectionContent;
- private RectTransform _deckContent;
+ private RectTransform _deckListContent;
+ private RectTransform _listCollectionContent;
+ private RectTransform _editorCollectionContent;
+ private RectTransform _deckCardContent;
private InputField _nameInput;
private Text _listStatus;
private Text _editorStatus;
@@ -44,10 +45,12 @@ namespace Vanguard.UI
private readonly List _collection = new List();
private readonly Dictionary _catalog =
new Dictionary(StringComparer.Ordinal);
-
private readonly List _cards = new List();
- private readonly Dictionary _collectionCountTexts =
+ 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 = "Все";
@@ -68,11 +71,11 @@ namespace Vanguard.UI
_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);
@@ -98,35 +101,83 @@ namespace Vanguard.UI
{
var view = UiKit.CreatePanel(_root, "DeckList", new Color(0.018f, 0.03f, 0.06f, 0.97f));
- var top = BuildTopBar(view, "Колоды", out _);
- var create = UiKit.CreateButton(top, "Create", "Создать колоду", NewDeck, 19);
- UiKit.SetPreferredHeight(create, 46f, 0f);
+ 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(44f, 30f);
- body.offsetMax = new Vector2(-44f, -110f);
- UiKit.AddVerticalLayout(body, 8, 10);
+ body.offsetMin = new Vector2(34f, 28f);
+ body.offsetMax = new Vector2(-34f, -108f);
+ UiKit.AddHorizontalLayout(body, 0, 14);
- var hint = UiKit.CreateText(
- body,
- "Hint",
- "Откройте колоду для редактирования или создайте новую. «В бой» ставит колоду в очередь.",
- 17,
- new Color(0.60f, 0.68f, 0.84f),
- TextAnchor.MiddleLeft);
- UiKit.SetPreferredHeight(hint, 26f);
+ 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.CreateScrollView(body, "DeckListScroll", out _listContent, 8f);
- var element = scroll.gameObject.AddComponent();
- element.flexibleHeight = 1f;
- element.minHeight = 200f;
+ 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(
- body, "Status", string.Empty, 18,
+ collectionArea, "Status", string.Empty, 16,
new Color(0.62f, 0.68f, 0.82f), TextAnchor.MiddleLeft);
- UiKit.SetPreferredHeight(_listStatus, 34f);
+ 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;
}
@@ -135,6 +186,95 @@ namespace Vanguard.UI
{
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);
@@ -142,104 +282,12 @@ namespace Vanguard.UI
top.anchoredPosition = Vector2.zero;
top.sizeDelta = new Vector2(0f, 88f);
UiKit.AddHorizontalLayout(top, 18, 12);
-
- var back = UiKit.CreateButton(top, "Back", "← Колоды", BackToCards, 18);
- UiKit.SetPreferredHeight(back, 44f, 0f);
-
- var title = UiKit.CreateText(top, "Title", "Конструктор", 28, Color.white, TextAnchor.MiddleLeft);
- title.fontStyle = FontStyle.Bold;
- UiKit.SetPreferredHeight(title, 44f, 0f);
-
- _nameInput = UiKit.CreateInput(top, "Name", "Название колоды");
- UiKit.SetPreferredHeight(_nameInput, 46f);
-
- var save = UiKit.CreateButton(top, "Save", "Сохранить", SaveDeck, 18);
- UiKit.SetPreferredHeight(save, 44f, 0f);
-
- 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, 30f);
- body.offsetMax = new Vector2(-34f, -106f);
- UiKit.AddHorizontalLayout(body, 0, 14);
-
- var left = UiKit.CreatePanel(body, "CollectionArea", new Color(0.04f, 0.06f, 0.10f, 0.7f));
- var leftElement = left.gameObject.AddComponent();
- leftElement.flexibleWidth = 1f;
- leftElement.flexibleHeight = 1f;
- leftElement.minHeight = 300f;
- UiKit.AddVerticalLayout(left, 10, 6);
-
- var filterRow = UiKit.CreatePanel(left, "FilterRow", new Color(0f, 0f, 0f, 0f));
- UiKit.SetPreferredHeight(filterRow, 48f);
- UiKit.AddHorizontalLayout(filterRow, 0, 10);
-
- var filters = UiKit.CreateButton(filterRow, "Filters", "Фильтры", OpenFilters, 18);
- UiKit.SetPreferredHeight(filters, 44f, 0f);
-
- var reset = UiKit.CreateButton(filterRow, "ResetFilters", "Сбросить фильтры", ResetFilters, 17);
- UiKit.SetPreferredHeight(reset, 44f, 0f);
-
- _filterSummary = UiKit.CreateText(
- filterRow, "FilterSummary", "Фильтры: все", 16,
- new Color(0.60f, 0.68f, 0.84f), TextAnchor.MiddleLeft);
- UiKit.SetPreferredHeight(_filterSummary, 44f);
-
- var statsPanel = UiKit.CreatePanel(left, "Stats", new Color(0.06f, 0.08f, 0.13f, 1f));
- UiKit.SetPreferredHeight(statsPanel, 76f);
- UiKit.AddVerticalLayout(statsPanel, 8, 2);
-
- _stats = UiKit.CreateText(
- statsPanel, "Text", string.Empty, 16,
- new Color(0.72f, 0.78f, 0.92f), TextAnchor.MiddleLeft);
- _stats.horizontalOverflow = HorizontalWrapMode.Wrap;
- UiKit.SetPreferredHeight(_stats, 56f);
-
- var gridScroll = UiKit.CreateGridScrollView(
- left,
- "CollectionGrid",
- new Vector2(166f, 186f),
- new Vector2(8f, 8f),
- out _collectionContent);
- var gridElement = gridScroll.gameObject.AddComponent();
- gridElement.flexibleHeight = 1f;
- gridElement.minHeight = 220f;
-
- var right = UiKit.CreatePanel(body, "DeckArea", new Color(0.04f, 0.06f, 0.10f, 0.85f));
- var rightElement = right.gameObject.AddComponent();
- rightElement.preferredWidth = 430f;
- rightElement.minWidth = 360f;
- rightElement.flexibleWidth = 0f;
- rightElement.flexibleHeight = 1f;
- rightElement.minHeight = 300f;
- UiKit.AddVerticalLayout(right, 10, 6);
-
- _deckCount = UiKit.CreateText(
- right, "DeckCount", "Колода: 0/50", 20, Color.white, TextAnchor.MiddleLeft);
- _deckCount.fontStyle = FontStyle.Bold;
- UiKit.SetPreferredHeight(_deckCount, 30f);
-
- var deckScroll = UiKit.CreateHorizontalScrollView(
- right, "DeckScroll", out _deckContent, 6f);
- var deckElement = deckScroll.gameObject.AddComponent();
- deckElement.flexibleHeight = 1f;
- deckElement.minHeight = 220f;
-
- var editorStatusPanel = UiKit.CreatePanel(right, "StatusPanel", new Color(0f, 0f, 0f, 0f));
- UiKit.SetPreferredHeight(editorStatusPanel, 54f);
- _editorStatus = UiKit.CreateText(
- editorStatusPanel, "Status", string.Empty, 16,
- new Color(0.62f, 0.68f, 0.82f), TextAnchor.UpperLeft);
- _editorStatus.horizontalOverflow = HorizontalWrapMode.Wrap;
- UiKit.SetPreferredHeight(_editorStatus, 50f);
-
- return view;
+ return top;
}
private RectTransform BuildFilterOverlay()
{
- var overlay = UiKit.CreatePanel(
- _editorView, "FilterOverlay", new Color(0f, 0f, 0f, 0.78f));
+ var overlay = UiKit.CreatePanel(_root, "FilterOverlay", new Color(0f, 0f, 0f, 0.78f));
var card = UiKit.CreateCenteredCard(
overlay, "FilterCard", new Vector2(860f, 820f),
@@ -304,29 +352,6 @@ namespace Vanguard.UI
return overlay;
}
- private void RequestDelete(DeckDto deck)
- {
- _pendingDelete = deck;
- _confirmText.text = deck.Name + "\nКолода будет удалена без восстановления.";
- _confirmOverlay.SetAsLastSibling();
- _confirmOverlay.gameObject.SetActive(true);
- }
-
- private void CancelDelete()
- {
- _pendingDelete = null;
- _confirmOverlay.gameObject.SetActive(false);
- }
-
- private void ConfirmDelete()
- {
- var deck = _pendingDelete;
- _confirmOverlay.gameObject.SetActive(false);
- _pendingDelete = null;
- if (deck != null)
- DeleteDeck(deck);
- }
-
private void AddFilterSection(Transform parent, string title, string[] labels, Action onSelected)
{
var caption = UiKit.CreateText(
@@ -355,25 +380,6 @@ namespace Vanguard.UI
UiKit.SetPreferredHeight(button, 40f);
}
- private RectTransform BuildTopBar(RectTransform view, string titleText, out Text title)
- {
- 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);
-
- var back = UiKit.CreateButton(top, "Back", "← В лобби", () => _onBack(), 18);
- UiKit.SetPreferredHeight(back, 44f, 0f);
-
- title = UiKit.CreateText(top, "Title", titleText, 30, Color.white, TextAnchor.MiddleLeft);
- title.fontStyle = FontStyle.Bold;
- UiKit.SetPreferredHeight(title, 46f);
- return top;
- }
-
private async Task LoadAsync()
{
if (_loading)
@@ -382,7 +388,7 @@ namespace Vanguard.UI
_loading = true;
_listStatus.text = "Загружаем коллекцию и колоды…";
_listStatus.color = new Color(0.62f, 0.68f, 0.82f);
- ClearChildren(_listContent);
+ ClearChildren(_deckListContent);
try
{
@@ -399,13 +405,14 @@ namespace Vanguard.UI
foreach (var item in catalog)
_catalog[item.PrintingId] = item;
RebuildClanFilters();
+
_decks.Clear();
_decks.AddRange(decks);
-
BuildDeckList();
+ RebuildCollectionGrids();
_listStatus.text = _decks.Count == 0
? "Колод пока нет. Создайте первую."
- : "Колод: " + _decks.Count + ".";
+ : "Карт в коллекции: " + _collection.Count + ".";
}
catch (ApiException exception)
{
@@ -426,50 +433,39 @@ namespace Vanguard.UI
private void BuildDeckList()
{
- ClearChildren(_listContent);
+ 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(
- _listContent, "Deck_" + deck.Id, new Color(0.07f, 0.09f, 0.14f, 1f));
+ _deckListContent, "DeckRow_" + deck.Id, new Color(0.08f, 0.11f, 0.17f, 1f));
var rowElement = row.gameObject.AddComponent();
- rowElement.preferredHeight = 82f;
- UiKit.AddHorizontalLayout(row, 14, 10);
+ rowElement.preferredHeight = 76f;
+ UiKit.AddVerticalLayout(row, 10, 2);
+
+ var button = row.gameObject.AddComponent