372 lines
14 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|