diff --git a/client/Assets/Vanguard/Runtime/App/DeckBuilderSceneController.cs b/client/Assets/Vanguard/Runtime/App/DeckBuilderSceneController.cs index 3dd2f2c..0528c45 100644 --- a/client/Assets/Vanguard/Runtime/App/DeckBuilderSceneController.cs +++ b/client/Assets/Vanguard/Runtime/App/DeckBuilderSceneController.cs @@ -2,6 +2,7 @@ using UnityEngine; using Vanguard.Api.Contracts; using Vanguard.Auth; using Vanguard.Meta; +using Vanguard.State; using Vanguard.UI; namespace Vanguard.App @@ -9,6 +10,7 @@ namespace Vanguard.App public sealed class DeckBuilderSceneController : SceneControllerBase { private DeckBuilderView _view; + private PlayerTopBar _topBar; private void Start() { @@ -21,12 +23,24 @@ namespace Vanguard.App () => _ = Flow.LoadLobbyAsync(), deck => _ = Flow.OpenLobbyWithQueueAsync(deck), info => _ = Flow.LoadBattleAsync(info)); + + _topBar = new PlayerTopBar( + canvas.transform, + Resolve(), + Resolve(), + () => _ = Flow.LoadLoginAsync(), + null, + null, + null, + () => _ = Flow.LoadLobbyAsync()); + _view.Show(); } private void OnDestroy() { _view?.Hide(); + _topBar?.Dispose(); } } } diff --git a/client/Assets/Vanguard/Runtime/UI/DeckBuilderView.cs b/client/Assets/Vanguard/Runtime/UI/DeckBuilderView.cs index 7d1f80c..7ad8125 100644 --- a/client/Assets/Vanguard/Runtime/UI/DeckBuilderView.cs +++ b/client/Assets/Vanguard/Runtime/UI/DeckBuilderView.cs @@ -33,7 +33,6 @@ namespace Vanguard.UI private readonly MetaService _meta; private readonly AuthService _auth; - private readonly Action _onBack; private readonly Action _onPlay; private readonly Action _onPracticeStarted; @@ -44,7 +43,6 @@ namespace Vanguard.UI private RectTransform _deckListContent; private RectTransform _deckCardContent; private InputField _nameInput; - private Text _title; private Text _status; private Text _filterSummary; private Text _stats; @@ -53,12 +51,17 @@ namespace Vanguard.UI 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; @@ -90,6 +93,7 @@ namespace Vanguard.UI private DeckDto _editing; private int _dust; private bool _dirty; + private bool _deckOpen; private bool _loading; private bool _busy; @@ -103,19 +107,19 @@ namespace Vanguard.UI { _meta = meta; _auth = auth; - _onBack = onBack; _onPlay = onPlay; _onPracticeStarted = onPracticeStarted; _root = UiKit.CreatePanel(parent, "DeckBuilderScreen", new Color(0.018f, 0.03f, 0.06f, 0.97f)); - BuildTopBar(); 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); } @@ -131,49 +135,20 @@ namespace Vanguard.UI { _filterOverlay.gameObject.SetActive(false); _confirmOverlay.gameObject.SetActive(false); + _deckMenuOverlay.gameObject.SetActive(false); _cardOverlay.gameObject.SetActive(false); _root.gameObject.SetActive(false); } // ------------------------------------------------------------ каркас - private void BuildTopBar() - { - 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 back = UiKit.CreateButton(top, "Back", "← В лобби", () => _onBack(), 17); - SetFixedSize(back, 130f, 44f); - - _title = UiKit.CreateText(top, "Title", "Колоды", 28, TextPrimary, TextAnchor.MiddleLeft); - _title.fontStyle = FontStyle.Bold; - SetFixedSize(_title, 200f, 44f); - - _filterSummary = UiKit.CreateText( - top, "FilterSummary", "Все карты", 15, TextMuted, TextAnchor.MiddleLeft); - _filterSummary.horizontalOverflow = HorizontalWrapMode.Wrap; - 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); - } - 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, 28f); - body.offsetMax = new Vector2(-34f, -108f); + body.offsetMin = new Vector2(34f, 24f); + body.offsetMax = new Vector2(-34f, -120f); UiKit.AddHorizontalLayout(body, 0, 14); var collectionArea = UiKit.CreatePanel( @@ -184,16 +159,39 @@ namespace Vanguard.UI 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(174f, 238f), - new Vector2(10f, 10f), + new Vector2(216f, 242f), + 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.constraint = GridLayoutGroup.Constraint.FixedColumnCount; + grid.constraintCount = 6; + grid.childAlignment = TextAnchor.UpperLeft; + } + _status = UiKit.CreateText( collectionArea, "Status", string.Empty, 15, TextMuted, TextAnchor.MiddleLeft); @@ -221,17 +219,9 @@ namespace Vanguard.UI 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 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(); @@ -315,16 +305,21 @@ namespace Vanguard.UI var overlay = UiKit.CreatePanel(_root, "FilterOverlay", new Color(0f, 0f, 0f, 0.78f)); var card = UiKit.CreateCenteredCard( - overlay, "FilterCard", new Vector2(900f, 860f), PanelFill); + 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, "Клан", 300f, flexible: true); + _clanChips = BuildFilterSection(card, "Клан", 260f, flexible: true); var footer = UiKit.CreatePanel(card, "Footer", new Color(0f, 0f, 0f, 0f)); UiKit.SetPreferredHeight(footer, 48f); @@ -376,6 +371,33 @@ namespace Vanguard.UI 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( @@ -575,11 +597,35 @@ namespace Vanguard.UI row.GetComponent().color = isOpen ? new Color(0.10f, 0.18f, 0.32f, 1f) : RowFill; + UiKit.AddHorizontalLayout(row, 8, 8); + + var emblem = UiKit.CreatePanel(row, "Emblem", new Color(0f, 0f, 0f, 0f)); + SetFixedSize(emblem, 48f, 48f); + var clan = DeckClan(deck); + var color = CardArt.ClanColor(clan ?? "None"); + NeonUi.AddImage( + emblem, + "Crest", + NeonUi.BodySliced( + 12, + new Color(color.r, color.g, color.b, 0.35f), + new Color(color.r, color.g, color.b, 0.95f), + 2), + new Vector2(48f, 48f), + Vector2.zero, + true); + var gem = NeonUi.AddImage( + emblem, + "Gem", + NeonUi.Body(20, 20, 4, color, Color.white, 1), + new Vector2(20f, 20f), + Vector2.zero); + gem.rectTransform.localRotation = Quaternion.Euler(0f, 0f, 45f); var content = UiKit.CreatePanel(row, "Content", new Color(0f, 0f, 0f, 0f)); - UiKit.Stretch(content); - content.offsetMin = new Vector2(10f, 8f); - content.offsetMax = new Vector2(-96f, -8f); + var contentElement = content.gameObject.AddComponent(); + contentElement.flexibleWidth = 1f; + contentElement.preferredHeight = 64f; UiKit.AddVerticalLayout(content, 0, 2); var name = UiKit.CreateText( @@ -593,55 +639,77 @@ namespace Vanguard.UI var info = UiKit.CreateText( content, "Info", - deck.Cards.Count + "/50 · T " + validation.Triggers + "/16 · " - + FormatName(deck.Format), + deck.Cards.Count + "/50 · T " + validation.Triggers + "/16", 12, - TextMuted, + validation.IsValid + ? new Color(0.55f, 0.9f, 0.62f) + : new Color(1f, 0.66f, 0.5f), TextAnchor.MiddleLeft); UiKit.MakeLiteral(info); UiKit.SetPreferredHeight(info, 18f); - var grades = UiKit.CreateText( - content, "Grades", GradeLine(deck.Cards), 11, TextDim, TextAnchor.MiddleLeft); - UiKit.MakeLiteral(grades); - UiKit.SetPreferredHeight(grades, 16f); - - var badge = UiKit.CreatePanel( - row, - "Validity", - validation.IsValid - ? new Color(0.12f, 0.34f, 0.22f, 0.95f) - : new Color(0.34f, 0.12f, 0.14f, 0.95f)); - var badgeRect = (RectTransform)badge.transform; - badgeRect.anchorMin = new Vector2(1f, 1f); - badgeRect.anchorMax = new Vector2(1f, 1f); - badgeRect.pivot = new Vector2(1f, 1f); - badgeRect.sizeDelta = new Vector2(84f, 22f); - badgeRect.anchoredPosition = new Vector2(-8f, -8f); - badge.GetComponent().sprite = NeonUi.BodySliced( - 11, - validation.IsValid - ? new Color(0.12f, 0.34f, 0.22f, 0.95f) - : new Color(0.34f, 0.12f, 0.14f, 0.95f), - validation.IsValid - ? new Color(0.35f, 0.8f, 0.5f, 0.8f) - : new Color(0.9f, 0.4f, 0.4f, 0.8f), - 1); - badge.GetComponent().type = Image.Type.Sliced; - badge.GetComponent().color = Color.white; - - var badgeText = UiKit.CreateText( - badge, - "Label", - validation.IsValid ? "Готова" : "Не готова", - 11, - validation.IsValid - ? new Color(0.65f, 0.95f, 0.75f) - : new Color(1f, 0.62f, 0.55f)); - UiKit.Stretch(badgeText.rectTransform); + var kebab = UiKit.CreateButton(row, "Menu", string.Empty, () => OpenDeckMenu(deck), 14); + SetFixedSize(kebab, 34f, 34f); + SetButtonColor(kebab, new Color(0.06f, 0.11f, 0.22f, 0.9f)); + for (var i = -1; i <= 1; i++) + { + NeonUi.AddImage( + kebab.transform, + "Dot" + i, + NeonUi.Circle(4, TextMuted), + new Vector2(4f, 4f), + new Vector2(0f, i * 7f)); + } } } + private void OpenDeckMenu(DeckDto deck) + { + _deckMenuDeck = deck; + _deckMenuTitle.text = deck.Name; + _deckMenuOverlay.SetAsLastSibling(); + _deckMenuOverlay.gameObject.SetActive(true); + } + + private void CloseDeckMenu() + { + _deckMenuDeck = null; + _deckMenuOverlay.gameObject.SetActive(false); + } + + private void OpenDeckFromMenu() + { + var deck = _deckMenuDeck; + CloseDeckMenu(); + if (deck != null) + OpenDeck(deck); + } + + private void DeleteDeckFromMenu() + { + var deck = _deckMenuDeck; + CloseDeckMenu(); + if (deck != null) + RequestDelete(deck); + } + + private string DeckClan(DeckDto deck) + { + if (deck == null || deck.Cards == null) + return null; + + foreach (var printingId in deck.Cards) + { + if (_catalog.TryGetValue(printingId, out var meta) + && !string.IsNullOrEmpty(meta.Clan)) + { + return meta.Clan; + } + } + + return null; + } + // ------------------------------------------------------------ редактор колоды private void RebuildDeckCards() @@ -708,30 +776,35 @@ namespace Vanguard.UI private void ShowDeckList() { - _title.text = "Колоды"; + _deckOpen = false; _deckListPanel.gameObject.SetActive(true); _deckPanel.gameObject.SetActive(false); _filterOverlay.gameObject.SetActive(false); _cardOverlay.gameObject.SetActive(false); + _deckMenuOverlay.gameObject.SetActive(false); } private void ShowDeckPanel() { - _title.text = "Колода"; + _deckOpen = true; _deckListPanel.gameObject.SetActive(false); _deckPanel.gameObject.SetActive(true); _filterOverlay.gameObject.SetActive(false); _cardOverlay.gameObject.SetActive(false); + _deckMenuOverlay.gameObject.SetActive(false); RebuildDeckPanel(); + RebuildCollection(); } private void BackToDeckList() { _dirty = false; + _editing = null; + _cards.Clear(); _deckListPanel.gameObject.SetActive(true); _deckPanel.gameObject.SetActive(false); BuildDeckList(); - RefreshCounts(); + RebuildCollection(); ShowDeckList(); } @@ -790,60 +863,101 @@ namespace Vanguard.UI { var cell = UiKit.CreatePanel( _collectionContent, "Owned_" + item.PrintingId, RowFill); - UiKit.AddVerticalLayout(cell, 6, 2); + UiKit.AddVerticalLayout(cell, 0, 0); AddRightClick(cell.gameObject, item.PrintingId, meta); var frame = cell.gameObject.AddComponent(); frame.effectColor = CardArt.ClanColor(meta.Clan); frame.effectDistance = new Vector2(2f, -2f); - var button = cell.gameObject.AddComponent