TH1/Unity/Assets/Scripts/TH1_UI/View/Outside/UIOutsideQuestionnaireQuestionMono.cs

372 lines
14 KiB
C#

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<UIOutsideQuestionnaireOptionMono> _optionItems = new List<UIOutsideQuestionnaireOptionMono>();
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<string>(answer.SelectedOptionIds)
: new HashSet<string>();
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<string>(),
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<UIOutsideQuestionnaireOptionMono>();
if (item == null)
{
item = go.AddComponent<UIOutsideQuestionnaireOptionMono>();
}
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<Image>();
if (image == null)
{
image = gameObject.AddComponent<Image>();
}
image.color = new Color(1f, 1f, 1f, 0.06f);
var layout = GetComponent<VerticalLayoutGroup>();
if (layout == null)
{
layout = gameObject.AddComponent<VerticalLayoutGroup>();
}
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<ContentSizeFitter>();
if (fitter == null)
{
fitter = gameObject.AddComponent<ContentSizeFitter>();
}
fitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
var layoutElement = GetComponent<LayoutElement>();
if (layoutElement == null)
{
layoutElement = gameObject.AddComponent<LayoutElement>();
}
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<VerticalLayoutGroup>();
optionLayout.spacing = 6f;
optionLayout.childControlWidth = true;
optionLayout.childControlHeight = true;
optionLayout.childForceExpandWidth = true;
optionLayout.childForceExpandHeight = false;
var optionFitter = optionList.AddComponent<ContentSizeFitter>();
optionFitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
}
}
private void CreateHeader()
{
var header = new GameObject("Header", typeof(RectTransform));
header.transform.SetParent(transform, false);
var headerLayout = header.AddComponent<HorizontalLayoutGroup>();
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<LayoutElement>();
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<LayoutElement>().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<LayoutElement>();
layout.minHeight = 96f;
layout.preferredHeight = 110f;
var image = inputRoot.GetComponent<Image>();
image.color = new Color(0f, 0f, 0f, 0.24f);
var input = inputRoot.GetComponent<TMP_InputField>();
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<RectTransform>();
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;
}
}
}