diff --git a/client/Assets/Vanguard/Runtime/UI/CardImageService.cs b/client/Assets/Vanguard/Runtime/UI/CardImageService.cs index 5881c4b..009ed2f 100644 --- a/client/Assets/Vanguard/Runtime/UI/CardImageService.cs +++ b/client/Assets/Vanguard/Runtime/UI/CardImageService.cs @@ -15,6 +15,11 @@ namespace Vanguard.UI private const string ResourceFolder = "CardArt/"; private static readonly Dictionary Cache = new Dictionary(); + private static readonly Dictionary IllustrationCache = + new Dictionary(); + + /// Доля высоты карты, занятая иллюстрацией (до полосы флейвора). + public const float IllustrationHeight = 0.59f; public static void Apply(Image target, string imageKey, Sprite fallback) { @@ -27,6 +32,48 @@ namespace Vanguard.UI target.preserveAspect = false; } + /// + /// Превью только иллюстрации: верхняя часть карты без рамки, имени и + /// текста способности. Пропорции сохраняются. + /// + public static void ApplyIllustration(Image target, string imageKey, Sprite fallback) + { + if (target == null) + return; + + var sprite = ResolveIllustration(imageKey, fallback); + target.sprite = sprite; + target.color = Color.white; + target.preserveAspect = true; + } + + /// Спрайт иллюстрации (обрезанная верхняя часть карты). + public static Sprite ResolveIllustration(string imageKey, Sprite fallback) + { + if (string.IsNullOrWhiteSpace(imageKey)) + return fallback; + + if (IllustrationCache.TryGetValue(imageKey, out var cached)) + return cached != null ? cached : fallback; + + var texture = Resources.Load(ResourceFolder + imageKey); + if (texture == null) + { + IllustrationCache[imageKey] = null; + return fallback; + } + + var height = Mathf.Max(1, Mathf.RoundToInt(texture.height * IllustrationHeight)); + var sprite = Sprite.Create( + texture, + new Rect(0f, texture.height - height, texture.width, height), + new Vector2(0.5f, 0.5f), + 100f); + sprite.name = imageKey + "_illustration"; + IllustrationCache[imageKey] = sprite; + return sprite; + } + /// Спрайт реального арта или процедурный fallback. public static Sprite Resolve(string imageKey, Sprite fallback) { diff --git a/client/Assets/Vanguard/Runtime/UI/DeckBuilderView.cs b/client/Assets/Vanguard/Runtime/UI/DeckBuilderView.cs index ba12ac7..7d1f80c 100644 --- a/client/Assets/Vanguard/Runtime/UI/DeckBuilderView.cs +++ b/client/Assets/Vanguard/Runtime/UI/DeckBuilderView.cs @@ -14,32 +14,50 @@ using Vanguard.Meta; namespace Vanguard.UI { /// - /// Декбилдер в духе HS: коллекция в центре, узкая колонка колод/карт справа. - /// Список колод и редактор используют один и тот же блок коллекции. + /// Декбилдер: слева коллекция с мультифильтрами, справа панель, которая + /// показывает либо список колод, либо карты открытой колоды. /// public sealed class DeckBuilderView { + private const float PanelWidth = 380f; + 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 _onBack; private readonly Action _onPlay; private readonly Action _onPracticeStarted; - private readonly RectTransform _root; - private readonly RectTransform _listView; - private readonly RectTransform _editorView; + private readonly RectTransform _root; + private RectTransform _collectionContent; + private RectTransform _deckListPanel; + private RectTransform _deckPanel; private RectTransform _deckListContent; - private RectTransform _listCollectionContent; - private RectTransform _editorCollectionContent; private RectTransform _deckCardContent; private InputField _nameInput; - private Text _listStatus; - private Text _editorStatus; + private Text _title; + private Text _status; + private Text _filterSummary; private Text _stats; private Text _deckCount; - private Text _filterSummary; + private Button _saveButton; + private Button _botButton; + private Button _duelButton; + private Button _deleteButton; + private RectTransform _filterOverlay; - private RectTransform _clanContent; + private RectTransform _gradeChips; + private RectTransform _triggerChips; + private RectTransform _clanChips; private RectTransform _confirmOverlay; private Text _confirmText; private DeckDto _pendingDelete; @@ -63,16 +81,15 @@ namespace Vanguard.UI 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 = + 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 string _filterClan = "Все"; - private string _filterTrigger = "Все"; - private int _filterGrade = -1; + private bool _dirty; private bool _loading; private bool _busy; @@ -91,14 +108,12 @@ namespace Vanguard.UI _onPracticeStarted = onPracticeStarted; _root = UiKit.CreatePanel(parent, "DeckBuilderScreen", new Color(0.018f, 0.03f, 0.06f, 0.97f)); - _listView = BuildListView(); - _editorView = BuildEditorView(); + BuildTopBar(); + BuildBody(); _filterOverlay = BuildFilterOverlay(); _confirmOverlay = BuildDeleteConfirm(); _cardOverlay = BuildCardOverlay(); - _listView.gameObject.SetActive(false); - _editorView.gameObject.SetActive(false); _filterOverlay.gameObject.SetActive(false); _confirmOverlay.gameObject.SetActive(false); _cardOverlay.gameObject.SetActive(false); @@ -108,7 +123,7 @@ namespace Vanguard.UI public void Show() { UiKit.SetActive(_root, true); - ShowList(); + ShowDeckList(); _ = LoadAsync(); } @@ -120,21 +135,29 @@ namespace Vanguard.UI _root.gameObject.SetActive(false); } - private RectTransform BuildListView() + // ------------------------------------------------------------ каркас + + private void BuildTopBar() { - var view = UiKit.CreatePanel(_root, "DeckList", new Color(0.018f, 0.03f, 0.06f, 0.97f)); + var top = UiKit.CreatePanel(_root, "TopBar", new Color(0.06f, 0.08f, 0.13f, 1f)); + var topRect = (RectTransform)top.transform; + topRect.anchorMin = new Vector2(0f, 1f); + topRect.anchorMax = new Vector2(1f, 1f); + topRect.pivot = new Vector2(0.5f, 1f); + topRect.anchoredPosition = Vector2.zero; + topRect.sizeDelta = new Vector2(0f, 88f); + UiKit.AddHorizontalLayout(top, 18, 12); - var top = BuildTopBar(view); var back = UiKit.CreateButton(top, "Back", "← В лобби", () => _onBack(), 17); - SetFixedSize(back, 120f, 44f); + SetFixedSize(back, 130f, 44f); - var title = UiKit.CreateText(top, "Title", "Коллекция", 28, Color.white, TextAnchor.MiddleLeft); - title.fontStyle = FontStyle.Bold; - SetFixedSize(title, 150f, 44f); + _title = UiKit.CreateText(top, "Title", "Колоды", 28, TextPrimary, TextAnchor.MiddleLeft); + _title.fontStyle = FontStyle.Bold; + SetFixedSize(_title, 200f, 44f); _filterSummary = UiKit.CreateText( - top, "FilterSummary", "Все карты", 16, - new Color(0.60f, 0.68f, 0.84f), TextAnchor.MiddleLeft); + top, "FilterSummary", "Все карты", 15, TextMuted, TextAnchor.MiddleLeft); + _filterSummary.horizontalOverflow = HorizontalWrapMode.Wrap; SetFlexible(_filterSummary, 1f, 44f); var filters = UiKit.CreateButton(top, "Filters", "Фильтры", OpenFilters, 17); @@ -142,105 +165,11 @@ namespace Vanguard.UI 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() + private void BuildBody() { - 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 bot = UiKit.CreateButton(top, "Bot", "С ботом", PlayBot, 17); - SetFixedSize(bot, 100f, 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)); + 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, 28f); @@ -260,94 +189,191 @@ namespace Vanguard.UI "CollectionGrid", new Vector2(174f, 238f), new Vector2(10f, 10f), - out _editorCollectionContent); + out _collectionContent); 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); + _status = UiKit.CreateText( + collectionArea, "Status", string.Empty, 15, + TextMuted, TextAnchor.MiddleLeft); + _status.horizontalOverflow = HorizontalWrapMode.Wrap; + UiKit.SetPreferredHeight(_status, 30f); - 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 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 header = UiKit.CreatePanel(panel, "Header", new Color(0f, 0f, 0f, 0f)); + UiKit.SetPreferredHeight(header, 38f); + UiKit.AddHorizontalLayout(header, 0, 8); + + var title = UiKit.CreateText( + header, "Title", "Мои колоды", 20, TextPrimary, TextAnchor.MiddleLeft); + title.fontStyle = FontStyle.Bold; + SetFlexible(title, 1f, 34f); + + var create = UiKit.CreateButton(header, "Create", "Создать", NewDeck, 15); + SetFixedSize(create, 120f, 36f); + + 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( - deckArea, "DeckCount", "Колода: 0/50", 19, Color.white, TextAnchor.MiddleLeft); + header, "DeckCount", "0/50", 16, TextPrimary, TextAnchor.MiddleRight); _deckCount.fontStyle = FontStyle.Bold; - UiKit.SetPreferredHeight(_deckCount, 28f); + SetFlexible(_deckCount, 1f, 34f); - var deckScroll = UiKit.CreateScrollView(deckArea, "DeckCardScroll", out _deckCardContent, 6f); - var deckScrollElement = deckScroll.gameObject.AddComponent(); - deckScrollElement.flexibleHeight = 1f; - deckScrollElement.minHeight = 200f; + _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( - deckArea, "Hint", "ЛКМ — убрать копию,\nПКМ — посмотреть карту.", 14, - new Color(0.58f, 0.66f, 0.80f), TextAnchor.MiddleLeft); + panel, "Hint", "ЛКМ — убрать копию,\nПКМ — посмотреть карту.", 13, + TextDim, TextAnchor.MiddleLeft); hint.horizontalOverflow = HorizontalWrapMode.Wrap; - UiKit.SetPreferredHeight(hint, 46f); + UiKit.SetPreferredHeight(hint, 36f); - return view; + return panel; } - 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); + overlay, "FilterCard", new Vector2(900f, 860f), PanelFill); + UiKit.AddVerticalLayout(card, 24, 10); - var title = UiKit.CreateText(card, "Title", "Фильтры коллекции", 28, Color.white); + var title = UiKit.CreateText(card, "Title", "Фильтры коллекции", 28, TextPrimary); 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); + _gradeChips = BuildFilterSection(card, "Ранг", 44f); + _triggerChips = BuildFilterSection(card, "Триггер", 44f); + _clanChips = BuildFilterSection(card, "Клан", 300f, flexible: true); - var clanTitle = UiKit.CreateText( - card, "ClanTitle", "Клан", 17, new Color(0.66f, 0.74f, 0.90f), TextAnchor.MiddleLeft); - UiKit.SetPreferredHeight(clanTitle, 24f); + var footer = UiKit.CreatePanel(card, "Footer", new Color(0f, 0f, 0f, 0f)); + UiKit.SetPreferredHeight(footer, 48f); + UiKit.AddHorizontalLayout(footer, 0, 8); - var clanScroll = UiKit.CreateScrollView(card, "ClanScroll", out _clanContent, 4f); - var clanElement = clanScroll.gameObject.AddComponent(); - clanElement.flexibleHeight = 1f; - clanElement.minHeight = 150f; + var reset = UiKit.CreateButton(footer, "Reset", "Сбросить", ResetDraftFilters, 16); + SetFixedSize(reset, 200f, 44f); - var close = UiKit.CreateButton(card, "Close", "Готово", CloseFilters, 20); - UiKit.SetPreferredHeight(close, 48f); + 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 void RebuildClanFilters() + private static RectTransform BuildFilterSection( + RectTransform parent, + string title, + float height, + bool flexible = false) { - ClearChildren(_clanContent); - AddClanButton(_clanContent, "Все"); - foreach (var clan in _catalog.Values.Select(item => item.Clan).Distinct().OrderBy(item => item)) - AddClanButton(_clanContent, clan); + 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 BuildDeleteConfirm() @@ -356,17 +382,15 @@ namespace Vanguard.UI _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)); + overlay, "ConfirmCard", new Vector2(640f, 300f), PanelFill); UiKit.AddVerticalLayout(card, 30, 12); - var title = UiKit.CreateText(card, "Title", "Удалить колоду?", 28, Color.white); + var title = UiKit.CreateText(card, "Title", "Удалить колоду?", 28, TextPrimary); title.fontStyle = FontStyle.Bold; UiKit.SetPreferredHeight(title, 42f); _confirmText = UiKit.CreateText( - card, "Text", string.Empty, 19, - new Color(0.72f, 0.78f, 0.92f)); + card, "Text", string.Empty, 19, TextMuted); UiKit.SetPreferredHeight(_confirmText, 70f); var yes = UiKit.CreateButton(card, "Yes", "Удалить", ConfirmDelete, 20); @@ -382,8 +406,7 @@ namespace Vanguard.UI { 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)); + 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; @@ -419,8 +442,7 @@ namespace Vanguard.UI UiKit.SetPreferredHeight(_cardTitle, 44f); _cardSubtitle = UiKit.CreateText( - info, "Subtitle", string.Empty, 16, - new Color(0.62f, 0.70f, 0.84f), TextAnchor.MiddleLeft); + info, "Subtitle", string.Empty, 16, TextMuted, TextAnchor.MiddleLeft); UiKit.MakeLiteral(_cardSubtitle); UiKit.SetPreferredHeight(_cardSubtitle, 26f); @@ -431,8 +453,7 @@ namespace Vanguard.UI UiKit.SetPreferredHeight(_cardStats, 88f); var abilitiesCaption = UiKit.CreateText( - info, "AbilitiesCaption", "Способности", 16, - new Color(0.66f, 0.74f, 0.90f), TextAnchor.MiddleLeft); + info, "AbilitiesCaption", "Способности", 16, TextMuted, TextAnchor.MiddleLeft); UiKit.SetPreferredHeight(abilitiesCaption, 24f); _cardAbilities = UiKit.CreateText( @@ -450,8 +471,7 @@ namespace Vanguard.UI UiKit.SetPreferredHeight(_cardOwned, 24f); _cardStatus = UiKit.CreateText( - info, "Status", string.Empty, 14, - new Color(0.62f, 0.68f, 0.82f), TextAnchor.UpperLeft); + info, "Status", string.Empty, 14, TextDim, TextAnchor.UpperLeft); _cardStatus.horizontalOverflow = HorizontalWrapMode.Wrap; UiKit.SetPreferredHeight(_cardStatus, 38f); @@ -473,6 +493,843 @@ namespace Vanguard.UI 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