using System; using System.Collections.Generic; using System.Linq; using TH1_Logic.Questionnaire; using TMPro; using UnityEngine; using UnityEngine.UI; namespace TH1_UI.View.Outside { public class UIOutsideQuestionnaireQuestionMono : MonoBehaviour { public TextMeshProUGUI IndexText; public TextMeshProUGUI TitleText; public TextMeshProUGUI HintText; public TMP_InputField OpenInput; public Transform OptionListContent; public QuestionnaireQuestionInfo QuestionInfo { get; private set; } private readonly List _optionItems = new List(); private Action _onAnswerChanged; private bool _updatingOptionState; public void SetContent(int index, QuestionnaireQuestionInfo questionInfo, GameObject optionPrefab, Action onAnswerChanged) { EnsureLayout(); QuestionInfo = questionInfo; _onAnswerChanged = onAnswerChanged; if (IndexText != null) { IndexText.text = index.ToString(); } if (TitleText != null) { var title = UIOutsideQuestionnaireView.ResolveText(questionInfo?.Title); TitleText.text = questionInfo != null && questionInfo.Required ? $"{title} *" : title; } if (HintText != null) { var hint = UIOutsideQuestionnaireView.ResolveText(questionInfo?.Hint); HintText.text = hint; HintText.gameObject.SetActive(!string.IsNullOrEmpty(hint)); } var isOpen = questionInfo != null && questionInfo.QuestionType == QuestionnaireQuestionType.Open; if (OpenInput != null) { OpenInput.gameObject.SetActive(isOpen); OpenInput.onValueChanged.RemoveAllListeners(); OpenInput.text = string.Empty; OpenInput.onValueChanged.AddListener(_ => _onAnswerChanged?.Invoke()); } if (OptionListContent != null) { OptionListContent.gameObject.SetActive(!isOpen); } BuildOptions(optionPrefab); } public void ApplyAnswer(QuestionnaireAnswer answer) { if (answer == null) return; if (QuestionInfo?.QuestionType == QuestionnaireQuestionType.Open) { if (OpenInput != null) { OpenInput.SetTextWithoutNotify(answer.OpenText ?? string.Empty); } return; } var selected = answer.SelectedOptionIds != null ? new HashSet(answer.SelectedOptionIds) : new HashSet(); foreach (var item in _optionItems) { var id = item.OptionInfo?.OptionId; item.SetSelectedWithoutNotify(!string.IsNullOrEmpty(id) && selected.Contains(id)); } } public void ClearAnswer() { if (OpenInput != null) { OpenInput.SetTextWithoutNotify(string.Empty); } foreach (var item in _optionItems) { item.SetSelectedWithoutNotify(false); } _onAnswerChanged?.Invoke(); } public void SetReadOnly(bool readOnly) { if (OpenInput != null) { OpenInput.interactable = !readOnly; } foreach (var item in _optionItems) { item.SetInteractable(!readOnly); } } public bool HasAnswer() { if (QuestionInfo == null) return false; if (QuestionInfo.QuestionType == QuestionnaireQuestionType.Open) { return OpenInput != null && !string.IsNullOrWhiteSpace(OpenInput.text); } return _optionItems.Any(item => item.IsOn); } public QuestionnaireAnswer CreateAnswer() { var answer = new QuestionnaireAnswer { QuestionId = QuestionInfo?.QuestionId, QuestionType = QuestionInfo != null ? QuestionInfo.QuestionType : QuestionnaireQuestionType.Open, SelectedOptionIds = new List(), OpenText = string.Empty }; if (QuestionInfo == null) return answer; if (QuestionInfo.QuestionType == QuestionnaireQuestionType.Open) { answer.OpenText = OpenInput != null ? OpenInput.text : string.Empty; return answer; } foreach (var option in _optionItems) { if (!option.IsOn || string.IsNullOrEmpty(option.OptionInfo?.OptionId)) continue; answer.SelectedOptionIds.Add(option.OptionInfo.OptionId); } return answer; } private void BuildOptions(GameObject optionPrefab) { foreach (Transform child in OptionListContent) { Destroy(child.gameObject); } _optionItems.Clear(); if (QuestionInfo == null || QuestionInfo.QuestionType == QuestionnaireQuestionType.Open || QuestionInfo.Options == null) return; foreach (var option in QuestionInfo.Options) { if (option == null) continue; var item = CreateOptionItem(optionPrefab); item.SetContent(option, false, OnOptionValueChanged); _optionItems.Add(item); } } private UIOutsideQuestionnaireOptionMono CreateOptionItem(GameObject optionPrefab) { GameObject go; if (optionPrefab != null) { go = Instantiate(optionPrefab, OptionListContent); } else { go = new GameObject("Option", typeof(RectTransform)); go.transform.SetParent(OptionListContent, false); } var item = go.GetComponent(); if (item == null) { item = go.AddComponent(); } return item; } private void OnOptionValueChanged(UIOutsideQuestionnaireOptionMono changedItem, bool isOn) { if (_updatingOptionState || QuestionInfo == null) { return; } _updatingOptionState = true; if (QuestionInfo.QuestionType == QuestionnaireQuestionType.SingleChoice && isOn) { foreach (var item in _optionItems) { if (item != changedItem) { item.SetSelectedWithoutNotify(false); } } } else if (QuestionInfo.QuestionType == QuestionnaireQuestionType.MultipleChoice && isOn && QuestionInfo.MaxSelectCount > 0) { var selectedCount = _optionItems.Count(item => item.IsOn); if (selectedCount > QuestionInfo.MaxSelectCount) { changedItem.SetSelectedWithoutNotify(false); } } _updatingOptionState = false; _onAnswerChanged?.Invoke(); } private void EnsureLayout() { var image = GetComponent(); if (image == null) { image = gameObject.AddComponent(); } image.color = new Color(1f, 1f, 1f, 0.06f); var layout = GetComponent(); if (layout == null) { layout = gameObject.AddComponent(); } layout.padding = new RectOffset(18, 18, 14, 16); layout.spacing = 8f; layout.childControlWidth = true; layout.childControlHeight = true; layout.childForceExpandWidth = true; layout.childForceExpandHeight = false; var fitter = GetComponent(); if (fitter == null) { fitter = gameObject.AddComponent(); } fitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize; var layoutElement = GetComponent(); if (layoutElement == null) { layoutElement = gameObject.AddComponent(); } layoutElement.minHeight = 110f; if (TitleText == null) { CreateHeader(); } if (HintText == null) { HintText = UIOutsideQuestionnaireView.CreateText(transform, "Hint", "", 18f, new Color(0.74f, 0.76f, 0.72f, 1f), TextAlignmentOptions.Left); HintText.enableWordWrapping = true; } if (OpenInput == null) { OpenInput = CreateOpenInput(transform); } if (OptionListContent == null) { var optionList = new GameObject("OptionList", typeof(RectTransform)); optionList.transform.SetParent(transform, false); OptionListContent = optionList.transform; var optionLayout = optionList.AddComponent(); optionLayout.spacing = 6f; optionLayout.childControlWidth = true; optionLayout.childControlHeight = true; optionLayout.childForceExpandWidth = true; optionLayout.childForceExpandHeight = false; var optionFitter = optionList.AddComponent(); optionFitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize; } } private void CreateHeader() { var header = new GameObject("Header", typeof(RectTransform)); header.transform.SetParent(transform, false); var headerLayout = header.AddComponent(); headerLayout.spacing = 10f; headerLayout.childControlWidth = true; headerLayout.childControlHeight = true; headerLayout.childForceExpandWidth = false; headerLayout.childForceExpandHeight = false; headerLayout.childAlignment = TextAnchor.UpperLeft; IndexText = UIOutsideQuestionnaireView.CreateText(header.transform, "Index", "", 20f, new Color(0.2f, 0.85f, 0.72f, 1f), TextAlignmentOptions.Center); IndexText.fontStyle = FontStyles.Bold; var indexLayout = IndexText.gameObject.AddComponent(); indexLayout.preferredWidth = 34f; indexLayout.minWidth = 34f; TitleText = UIOutsideQuestionnaireView.CreateText(header.transform, "Title", "", 21f, new Color(0.98f, 0.96f, 0.88f, 1f), TextAlignmentOptions.Left); TitleText.fontStyle = FontStyles.Bold; TitleText.enableWordWrapping = true; TitleText.gameObject.AddComponent().flexibleWidth = 1f; } private static TMP_InputField CreateOpenInput(Transform parent) { var inputRoot = new GameObject("OpenInput", typeof(RectTransform), typeof(Image), typeof(TMP_InputField), typeof(LayoutElement)); inputRoot.transform.SetParent(parent, false); var layout = inputRoot.GetComponent(); layout.minHeight = 96f; layout.preferredHeight = 110f; var image = inputRoot.GetComponent(); image.color = new Color(0f, 0f, 0f, 0.24f); var input = inputRoot.GetComponent(); input.lineType = TMP_InputField.LineType.MultiLineNewline; input.characterLimit = 500; input.targetGraphic = image; var viewport = new GameObject("TextViewport", typeof(RectTransform), typeof(RectMask2D)); viewport.transform.SetParent(inputRoot.transform, false); var viewportRect = viewport.GetComponent(); viewportRect.anchorMin = Vector2.zero; viewportRect.anchorMax = Vector2.one; viewportRect.offsetMin = new Vector2(12f, 8f); viewportRect.offsetMax = new Vector2(-12f, -8f); var text = UIOutsideQuestionnaireView.CreateText(viewport.transform, "Text", "", 20f, new Color(0.95f, 0.95f, 0.9f, 1f), TextAlignmentOptions.TopLeft); text.rectTransform.anchorMin = Vector2.zero; text.rectTransform.anchorMax = Vector2.one; text.rectTransform.offsetMin = Vector2.zero; text.rectTransform.offsetMax = Vector2.zero; text.enableWordWrapping = true; text.raycastTarget = false; var placeholder = UIOutsideQuestionnaireView.CreateText(viewport.transform, "Placeholder", "", 20f, new Color(0.72f, 0.72f, 0.68f, 0.7f), TextAlignmentOptions.TopLeft); placeholder.rectTransform.anchorMin = Vector2.zero; placeholder.rectTransform.anchorMax = Vector2.one; placeholder.rectTransform.offsetMin = Vector2.zero; placeholder.rectTransform.offsetMax = Vector2.zero; placeholder.enableWordWrapping = true; placeholder.raycastTarget = false; input.textViewport = viewportRect; input.textComponent = text; input.placeholder = placeholder; return input; } } }