using System; using Minesweeper.Core; using Minesweeper.Config; using TMPro; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; namespace Minesweeper.Presentation.Views { public sealed class CellView : MonoBehaviour, IPointerClickHandler, IPointerDownHandler, IPointerUpHandler { [SerializeField] private Button button; [SerializeField] private Image backgroundImage; [SerializeField] private RectTransform contentRoot; [SerializeField] private Image contentImage; [SerializeField] private TMP_Text label; private int x; private int y; private bool inputEnabled = true; private bool isOpened; public event Action OpenRequested; public event Action FlagRequested; public event Action PressStarted; public event Action PressEnded; public void Bind(Button button, Image backgroundImage, RectTransform contentRoot, Image contentImage, TMP_Text label) { this.button = button; this.backgroundImage = backgroundImage; this.contentRoot = contentRoot; this.contentImage = contentImage; this.label = label; } public void Initialize(int x, int y) { this.x = x; this.y = y; gameObject.name = $"bt_{x}_{y}"; } public void SetInputEnabled(bool enabled) { inputEnabled = enabled; if (button != null) { button.interactable = enabled && !isOpened; } } public void Render(BoardCellData cell, MinesweeperUiConfig config, float pixelsPerUnitMultiplier, float contentPadding, bool revealUnflaggedMines) { isOpened = cell.IsOpened; var revealMine = revealUnflaggedMines && cell.IsMine && !cell.IsFlagged; if (contentRoot != null) { contentRoot.offsetMin = new Vector2(contentPadding, contentPadding); contentRoot.offsetMax = new Vector2(-contentPadding, -contentPadding); } if (backgroundImage != null) { backgroundImage.pixelsPerUnitMultiplier = pixelsPerUnitMultiplier; backgroundImage.color = cell.IsOpened || revealMine ? config.OpenedCellColor : config.ClosedCellColor; } if (contentImage != null) { contentImage.pixelsPerUnitMultiplier = pixelsPerUnitMultiplier; } RenderContent(cell, config, revealMine); if (button != null) { button.interactable = inputEnabled && !cell.IsOpened; } } private void RenderContent(BoardCellData cell, MinesweeperUiConfig config, bool revealMine) { var showFlag = cell.IsFlagged && !cell.IsOpened; var showMine = (cell.IsOpened && cell.IsMine) || revealMine; var showMineAsText = showMine && config.MineSprite == null; var showNumber = cell.IsOpened && !cell.IsMine && cell.NeighborMines > 0; if (contentImage != null) { SetActiveIfChanged(contentImage.gameObject, showFlag || (showMine && !showMineAsText)); if (showFlag) { SetSpriteIfChanged(contentImage, config.FlagSprite); } else if (showMine) { SetSpriteIfChanged(contentImage, config.MineSprite); } contentImage.color = Color.white; } if (label != null) { SetActiveIfChanged(label.gameObject, showNumber || showMineAsText); SetTextIfChanged(label, showMineAsText ? "M" : showNumber ? cell.NeighborMines.ToString() : string.Empty); label.color = config.GetNumberTextColor(cell.NeighborMines); } } private static void SetActiveIfChanged(GameObject target, bool active) { if (target.activeSelf != active) { target.SetActive(active); } } private static void SetSpriteIfChanged(Image image, Sprite sprite) { if (image.sprite != sprite) { image.sprite = sprite; } } private static void SetTextIfChanged(TMP_Text text, string value) { if (text.text != value) { text.text = value; } } public void OnPointerClick(PointerEventData eventData) { if (!inputEnabled) { return; } if (eventData.button == PointerEventData.InputButton.Left) { OpenRequested?.Invoke(x, y); } else if (eventData.button == PointerEventData.InputButton.Right) { FlagRequested?.Invoke(x, y); } } public void OnPointerDown(PointerEventData eventData) { if (inputEnabled && eventData.button == PointerEventData.InputButton.Left) { PressStarted?.Invoke(); } } public void OnPointerUp(PointerEventData eventData) { if (eventData.button == PointerEventData.InputButton.Left) { PressEnded?.Invoke(); } } } }