928 lines
30 KiB
C#
928 lines
30 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using Aisu.SIM1.Face.Core;
|
|
using Aisu.SIM1.Face.Protocol;
|
|
using UnityEngine;
|
|
using UnityEngine.UI;
|
|
|
|
namespace Aisu.SIM1.Face.UI.Common
|
|
{
|
|
/// <summary>
|
|
/// FaceModule UI 公共状态。
|
|
/// </summary>
|
|
public sealed class FaceModuleUiState
|
|
{
|
|
// 公开数据字段。
|
|
public bool IsBusy;
|
|
// 公开数据字段。
|
|
public string CurrentPage;
|
|
// 公开数据字段。
|
|
public string LastResult;
|
|
// 公开数据字段。
|
|
public float Progress;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 所有功能页面的基类。
|
|
/// </summary>
|
|
public abstract class FaceModulePageBase : MonoBehaviour
|
|
{
|
|
// 内部状态字段。
|
|
protected FaceModuleManager m_manager;
|
|
// 内部状态字段。
|
|
protected FaceModuleLogPanel m_logPanel;
|
|
// 内部状态字段。
|
|
protected FaceModuleLoadingView m_loadingView;
|
|
// 内部状态字段。
|
|
protected Text m_resultText;
|
|
|
|
/// <summary>
|
|
/// 初始化页面。
|
|
/// </summary>
|
|
public virtual void Init(FaceModuleManager manager, FaceModuleLogPanel logPanel, FaceModuleLoadingView loadingView)
|
|
{
|
|
m_manager = manager;
|
|
m_logPanel = logPanel;
|
|
m_loadingView = loadingView;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 页面显示。
|
|
/// </summary>
|
|
public virtual void Show()
|
|
{
|
|
gameObject.SetActive(true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 页面隐藏。
|
|
/// </summary>
|
|
public virtual void Hide()
|
|
{
|
|
gameObject.SetActive(false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 由代码生成页面内容。
|
|
/// </summary>
|
|
public virtual void Build(FaceModuleUiBuilder builder)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// 执行 RequireConnected 方法。
|
|
/// </summary>
|
|
protected bool RequireConnected()
|
|
{
|
|
if (m_manager != null && m_manager.IsOpen)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
SetResult("模组未连接");
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 执行 RequireReady 方法。
|
|
/// </summary>
|
|
protected bool RequireReady()
|
|
{
|
|
if (RequireConnected() && m_manager.IsReady)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
SetResult("模组未 READY");
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 执行 SetResult 方法。
|
|
/// </summary>
|
|
protected void SetResult(string message)
|
|
{
|
|
if (m_resultText != null)
|
|
{
|
|
m_resultText.text = message;
|
|
}
|
|
|
|
m_logPanel?.Info(message);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 执行 SetError 方法。
|
|
/// </summary>
|
|
protected void SetError(Exception exception)
|
|
{
|
|
SetResult(exception.Message);
|
|
m_logPanel?.Error(exception.Message);
|
|
Debug.LogException(exception);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 执行 ReadByte 方法。
|
|
/// </summary>
|
|
protected byte ReadByte(InputField input, byte fallback)
|
|
{
|
|
return input != null && byte.TryParse(input.text, out var value) ? value : fallback;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 执行 ReadInt 方法。
|
|
/// </summary>
|
|
protected int ReadInt(InputField input, int fallback)
|
|
{
|
|
return input != null && int.TryParse(input.text, out var value) ? value : fallback;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 执行 ReadText 方法。
|
|
/// </summary>
|
|
protected string ReadText(InputField input)
|
|
{
|
|
return input != null ? input.text : string.Empty;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 执行 RunWithButton 方法。
|
|
/// </summary>
|
|
protected void RunWithButton(Button button, Func<System.Threading.Tasks.Task> action)
|
|
{
|
|
if (button != null)
|
|
{
|
|
button.interactable = false;
|
|
}
|
|
|
|
RunAsync(async () =>
|
|
{
|
|
try
|
|
{
|
|
await action();
|
|
}
|
|
finally
|
|
{
|
|
if (button != null)
|
|
{
|
|
button.interactable = true;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// 执行 RunAsync 方法。
|
|
/// </summary>
|
|
protected async void RunAsync(Func<System.Threading.Tasks.Task> action)
|
|
{
|
|
try
|
|
{
|
|
m_loadingView?.Show("处理中...");
|
|
await action();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
SetError(ex);
|
|
}
|
|
finally
|
|
{
|
|
m_loadingView?.Hide();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 顶部状态栏。
|
|
/// </summary>
|
|
public sealed class FaceModuleStatusBar : MonoBehaviour
|
|
{
|
|
// Inspector 绑定字段。
|
|
[SerializeField] private Text m_connectionText;
|
|
// Inspector 绑定字段。
|
|
[SerializeField] private Text m_readyText;
|
|
// Inspector 绑定字段。
|
|
[SerializeField] private Text m_statusText;
|
|
// Inspector 绑定字段。
|
|
[SerializeField] private Text m_commandText;
|
|
// Inspector 绑定字段。
|
|
[SerializeField] private Text m_errorText;
|
|
// 内部状态字段。
|
|
private FaceModuleManager m_manager;
|
|
private Action<FaceResultCode, string> m_errorHandler; // Manager 错误事件回调。
|
|
|
|
#region Unity API
|
|
|
|
/// <summary>
|
|
/// 执行 Update 方法。
|
|
/// </summary>
|
|
private void Update()
|
|
{
|
|
Refresh();
|
|
}
|
|
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// 初始化状态栏。
|
|
/// </summary>
|
|
public void Init(FaceModuleManager manager)
|
|
{
|
|
if (m_manager != null && m_errorHandler != null)
|
|
{
|
|
m_manager.OnError -= m_errorHandler;
|
|
}
|
|
|
|
m_manager = manager;
|
|
if (m_manager != null)
|
|
{
|
|
m_errorHandler = (_, message) =>
|
|
{
|
|
if (m_errorText != null)
|
|
{
|
|
m_errorText.text = "错误: " + message;
|
|
}
|
|
};
|
|
m_manager.OnError += m_errorHandler;
|
|
}
|
|
|
|
Refresh();
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
if (m_manager != null && m_errorHandler != null)
|
|
{
|
|
m_manager.OnError -= m_errorHandler;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 绑定状态栏文本。
|
|
/// </summary>
|
|
public void Bind(Text connectionText, Text readyText, Text statusText, Text commandText, Text errorText)
|
|
{
|
|
m_connectionText = connectionText;
|
|
m_readyText = readyText;
|
|
m_statusText = statusText;
|
|
m_commandText = commandText;
|
|
m_errorText = errorText;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 刷新显示。
|
|
/// </summary>
|
|
public void Refresh()
|
|
{
|
|
if (m_manager == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (m_connectionText != null) m_connectionText.text = m_manager.IsReady ? "硬件已连接" : (m_manager.IsOpen ? "端口已打开" : "未连接");
|
|
if (m_readyText != null) m_readyText.text = m_manager.IsReady ? "READY" : "未 READY";
|
|
if (m_statusText != null) m_statusText.text = "状态: " + m_manager.CurrentStatus;
|
|
if (m_commandText != null) m_commandText.text = "命令: " + m_manager.CurrentCommand;
|
|
if (m_errorText != null && !string.IsNullOrEmpty(m_manager.LastError)) m_errorText.text = "错误: " + m_manager.LastError;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 左侧导航栏。
|
|
/// </summary>
|
|
public sealed class FaceModuleNavigationPanel : MonoBehaviour
|
|
{
|
|
private readonly List<Button> m_buttons = new List<Button>();
|
|
// 内部状态字段。
|
|
private FaceModuleMainPanel m_mainPanel;
|
|
|
|
/// <summary>
|
|
/// 初始化导航栏。
|
|
/// </summary>
|
|
public void Init(FaceModuleMainPanel mainPanel)
|
|
{
|
|
m_mainPanel = mainPanel;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 添加页面导航按钮。
|
|
/// </summary>
|
|
public void AddNavigation(string pageName, string label, FaceModuleUiBuilder builder)
|
|
{
|
|
var button = builder.Button(transform, label, () => m_mainPanel.ShowPage(pageName));
|
|
m_buttons.Add(button);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 底部日志面板。
|
|
/// </summary>
|
|
public sealed class FaceModuleLogPanel : MonoBehaviour
|
|
{
|
|
// Inspector 绑定字段。
|
|
[SerializeField] private Text m_logText;
|
|
// Inspector 绑定字段。
|
|
[SerializeField] private ScrollRect m_scrollRect;
|
|
// Inspector 绑定字段。
|
|
[SerializeField] private Toggle m_autoScrollToggle;
|
|
private readonly List<string> m_lines = new List<string>();
|
|
private FaceModuleManager m_manager; // 当前绑定的 Manager。
|
|
private Action<FaceResultCode, string> m_errorHandler; // Manager 错误事件回调。
|
|
private Action m_readyHandler; // Manager READY 事件回调。
|
|
|
|
/// <summary>
|
|
/// 绑定日志 UI。
|
|
/// </summary>
|
|
public void Bind(Text logText, ScrollRect scrollRect, Toggle autoScrollToggle)
|
|
{
|
|
m_logText = logText;
|
|
m_scrollRect = scrollRect;
|
|
m_autoScrollToggle = autoScrollToggle;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 连接 Manager 日志事件。
|
|
/// </summary>
|
|
public void Init(FaceModuleManager manager)
|
|
{
|
|
UnbindManagerEvents();
|
|
m_manager = manager;
|
|
if (manager == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
m_errorHandler = (_, message) => Error(message);
|
|
m_readyHandler = () => Info("模组 READY");
|
|
manager.OnRawLog += Raw;
|
|
manager.OnError += m_errorHandler;
|
|
manager.OnReady += m_readyHandler;
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
UnbindManagerEvents();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 添加业务日志。
|
|
/// </summary>
|
|
public void Info(string message)
|
|
{
|
|
Append("INFO", message);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 添加错误日志。
|
|
/// </summary>
|
|
public void Error(string message)
|
|
{
|
|
Append("ERROR", message);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 添加原始收发日志。
|
|
/// </summary>
|
|
public void Raw(string message)
|
|
{
|
|
var tag = message.StartsWith("TX", StringComparison.OrdinalIgnoreCase) ? "TX" : "RX";
|
|
var body = message.Length > 3 ? message.Substring(3) : message;
|
|
Append(tag, body);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 清空日志。
|
|
/// </summary>
|
|
public void Clear()
|
|
{
|
|
m_lines.Clear();
|
|
RefreshText();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 保存日志到本地。
|
|
/// </summary>
|
|
public void Save()
|
|
{
|
|
var dir = Path.Combine(Application.persistentDataPath, "FaceModuleLogs");
|
|
Directory.CreateDirectory(dir);
|
|
File.WriteAllLines(Path.Combine(dir, DateTime.Now.ToString("yyyyMMdd_HHmmss") + ".log"), m_lines);
|
|
Info("日志已保存: " + dir);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 执行 Append 方法。
|
|
/// </summary>
|
|
private void Append(string tag, string message)
|
|
{
|
|
m_lines.Add($"[{DateTime.Now:HH:mm:ss.fff}][{tag}] {message}");
|
|
if (m_lines.Count > 1000)
|
|
{
|
|
m_lines.RemoveAt(0);
|
|
}
|
|
|
|
RefreshText();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 执行 RefreshText 方法。
|
|
/// </summary>
|
|
private void RefreshText()
|
|
{
|
|
if (m_logText != null)
|
|
{
|
|
m_logText.text = string.Join("\n", m_lines);
|
|
}
|
|
|
|
if (m_scrollRect != null && (m_autoScrollToggle == null || m_autoScrollToggle.isOn))
|
|
{
|
|
Canvas.ForceUpdateCanvases();
|
|
m_scrollRect.verticalNormalizedPosition = 0f;
|
|
}
|
|
}
|
|
|
|
private void UnbindManagerEvents()
|
|
{
|
|
if (m_manager == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
m_manager.OnRawLog -= Raw;
|
|
if (m_errorHandler != null)
|
|
{
|
|
m_manager.OnError -= m_errorHandler;
|
|
}
|
|
|
|
if (m_readyHandler != null)
|
|
{
|
|
m_manager.OnReady -= m_readyHandler;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Loading / 进度提示。
|
|
/// </summary>
|
|
public sealed class FaceModuleLoadingView : MonoBehaviour
|
|
{
|
|
// Inspector 绑定字段。
|
|
[SerializeField] private Text m_messageText;
|
|
// Inspector 绑定字段。
|
|
[SerializeField] private Slider m_progressSlider;
|
|
|
|
/// <summary>
|
|
/// 绑定控件。
|
|
/// </summary>
|
|
public void Bind(Text messageText, Slider progressSlider)
|
|
{
|
|
m_messageText = messageText;
|
|
m_progressSlider = progressSlider;
|
|
Hide();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 显示 Loading。
|
|
/// </summary>
|
|
public void Show(string message)
|
|
{
|
|
gameObject.SetActive(true);
|
|
if (m_messageText != null) m_messageText.text = message;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 设置进度。
|
|
/// </summary>
|
|
public void SetProgress(float value)
|
|
{
|
|
if (m_progressSlider != null) m_progressSlider.value = Mathf.Clamp01(value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 隐藏 Loading。
|
|
/// </summary>
|
|
public void Hide()
|
|
{
|
|
gameObject.SetActive(false);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 二次确认弹窗。
|
|
/// </summary>
|
|
public sealed class FaceModuleConfirmDialog : MonoBehaviour
|
|
{
|
|
// Inspector 绑定字段。
|
|
[SerializeField] private Text m_messageText;
|
|
// Inspector 绑定字段。
|
|
[SerializeField] private Button m_confirmButton;
|
|
// Inspector 绑定字段。
|
|
[SerializeField] private Button m_cancelButton;
|
|
// 内部状态字段。
|
|
private Action m_onConfirm;
|
|
|
|
/// <summary>
|
|
/// 绑定控件。
|
|
/// </summary>
|
|
public void Bind(Text messageText, Button confirmButton, Button cancelButton)
|
|
{
|
|
m_messageText = messageText;
|
|
m_confirmButton = confirmButton;
|
|
m_cancelButton = cancelButton;
|
|
m_confirmButton.onClick.AddListener(Confirm);
|
|
m_cancelButton.onClick.AddListener(Hide);
|
|
Hide();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 显示确认框。
|
|
/// </summary>
|
|
public void Show(string message, Action onConfirm)
|
|
{
|
|
m_onConfirm = onConfirm;
|
|
if (m_messageText != null) m_messageText.text = message;
|
|
gameObject.SetActive(true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 隐藏确认框。
|
|
/// </summary>
|
|
public void Hide()
|
|
{
|
|
gameObject.SetActive(false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 执行 Confirm 方法。
|
|
/// </summary>
|
|
private void Confirm()
|
|
{
|
|
Hide();
|
|
m_onConfirm?.Invoke();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 运行时 UI 构建辅助器。
|
|
/// </summary>
|
|
public sealed class FaceModuleUiBuilder
|
|
{
|
|
// 内部状态字段。
|
|
private readonly Font m_font;
|
|
|
|
/// <summary>
|
|
/// 执行 FaceModuleUiBuilder 方法。
|
|
/// </summary>
|
|
public FaceModuleUiBuilder(Font font)
|
|
{
|
|
m_font = font ?? Resources.GetBuiltinResource<Font>("Arial.ttf");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 执行 Text 方法。
|
|
/// </summary>
|
|
public Text Text(Transform parent, string text, int size = 22, TextAnchor anchor = TextAnchor.MiddleLeft)
|
|
{
|
|
var go = Create("Text", parent);
|
|
var component = go.AddComponent<Text>();
|
|
component.font = m_font;
|
|
component.text = text;
|
|
component.fontSize = size;
|
|
component.color = new Color(0.08f, 0.09f, 0.1f);
|
|
component.alignment = anchor;
|
|
component.horizontalOverflow = HorizontalWrapMode.Wrap;
|
|
component.verticalOverflow = VerticalWrapMode.Overflow;
|
|
return component;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 执行 Button 方法。
|
|
/// </summary>
|
|
public Button Button(Transform parent, string label, UnityEngine.Events.UnityAction onClick)
|
|
{
|
|
var go = Create("Button_" + label, parent);
|
|
var image = go.AddComponent<Image>();
|
|
image.color = new Color(0.13f, 0.28f, 0.47f);
|
|
var button = go.AddComponent<Button>();
|
|
button.targetGraphic = image;
|
|
button.onClick.AddListener(onClick);
|
|
var text = Text(go.transform, label, 20, TextAnchor.MiddleCenter);
|
|
text.color = Color.white;
|
|
Stretch(text.rectTransform);
|
|
var layout = go.AddComponent<LayoutElement>();
|
|
layout.preferredHeight = 42;
|
|
layout.minHeight = 36;
|
|
return button;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 执行 Input 方法。
|
|
/// </summary>
|
|
public InputField Input(Transform parent, string placeholder, string value = "")
|
|
{
|
|
var go = Create("Input_" + placeholder, parent);
|
|
var image = go.AddComponent<Image>();
|
|
image.color = Color.white;
|
|
var input = go.AddComponent<InputField>();
|
|
var text = Text(go.transform, value, 20);
|
|
text.color = Color.black;
|
|
Stretch(text.rectTransform, 10, 10, 4, 4);
|
|
input.textComponent = text;
|
|
var place = Text(go.transform, placeholder, 20);
|
|
place.color = new Color(0.45f, 0.48f, 0.52f);
|
|
Stretch(place.rectTransform, 10, 10, 4, 4);
|
|
input.placeholder = place;
|
|
var layout = go.AddComponent<LayoutElement>();
|
|
layout.preferredHeight = 38;
|
|
return input;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 执行 Toggle 方法。
|
|
/// </summary>
|
|
public Toggle Toggle(Transform parent, string label, bool value)
|
|
{
|
|
var go = Create("Toggle_" + label, parent);
|
|
var toggle = go.AddComponent<Toggle>();
|
|
var row = go.AddComponent<HorizontalLayoutGroup>();
|
|
row.spacing = 8;
|
|
row.childControlWidth = false;
|
|
row.childControlHeight = true;
|
|
var box = Create("CheckmarkBox", go.transform);
|
|
var boxImage = box.AddComponent<Image>();
|
|
boxImage.color = Color.white;
|
|
var boxLayout = box.AddComponent<LayoutElement>();
|
|
boxLayout.preferredWidth = 28;
|
|
boxLayout.preferredHeight = 28;
|
|
var mark = Create("Checkmark", box.transform);
|
|
var markImage = mark.AddComponent<Image>();
|
|
markImage.color = new Color(0.1f, 0.5f, 0.25f);
|
|
Stretch(markImage.rectTransform, 5, 5, 5, 5);
|
|
toggle.targetGraphic = boxImage;
|
|
toggle.graphic = markImage;
|
|
toggle.isOn = value;
|
|
Text(go.transform, label, 20);
|
|
return toggle;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 执行 Dropdown 方法。
|
|
/// </summary>
|
|
public Dropdown Dropdown(Transform parent, IList<string> options)
|
|
{
|
|
var go = Create("Dropdown", parent);
|
|
var image = go.AddComponent<Image>();
|
|
image.color = Color.white;
|
|
var dropdown = go.AddComponent<Dropdown>();
|
|
dropdown.captionText = Text(go.transform, options.Count > 0 ? options[0] : string.Empty, 20);
|
|
Stretch(dropdown.captionText.rectTransform, 10, 32, 4, 4);
|
|
BuildDropdownTemplate(dropdown, go.transform);
|
|
dropdown.options.Clear();
|
|
foreach (var option in options)
|
|
{
|
|
dropdown.options.Add(new Dropdown.OptionData(option));
|
|
}
|
|
|
|
var layout = go.AddComponent<LayoutElement>();
|
|
layout.preferredHeight = 38;
|
|
return dropdown;
|
|
}
|
|
|
|
private void BuildDropdownTemplate(Dropdown dropdown, Transform parent)
|
|
{
|
|
var template = Create("Template", parent);
|
|
template.SetActive(false);
|
|
var templateImage = template.AddComponent<Image>();
|
|
templateImage.color = Color.white;
|
|
var templateRect = template.GetComponent<RectTransform>();
|
|
templateRect.anchorMin = new Vector2(0, 0);
|
|
templateRect.anchorMax = new Vector2(1, 0);
|
|
templateRect.pivot = new Vector2(0.5f, 1);
|
|
templateRect.anchoredPosition = new Vector2(0, 2);
|
|
templateRect.sizeDelta = new Vector2(0, 180);
|
|
|
|
var scroll = template.AddComponent<ScrollRect>();
|
|
scroll.horizontal = false;
|
|
|
|
var viewport = Create("Viewport", template.transform);
|
|
viewport.AddComponent<Image>().color = Color.white;
|
|
var mask = viewport.AddComponent<Mask>();
|
|
mask.showMaskGraphic = false;
|
|
Stretch(viewport.GetComponent<RectTransform>());
|
|
|
|
var content = Create("Content", viewport.transform);
|
|
var contentRect = content.GetComponent<RectTransform>();
|
|
contentRect.anchorMin = new Vector2(0, 1);
|
|
contentRect.anchorMax = new Vector2(1, 1);
|
|
contentRect.pivot = new Vector2(0.5f, 1);
|
|
contentRect.anchoredPosition = Vector2.zero;
|
|
contentRect.sizeDelta = new Vector2(0, 0);
|
|
var contentLayout = content.AddComponent<VerticalLayoutGroup>();
|
|
contentLayout.childControlWidth = true;
|
|
contentLayout.childControlHeight = true;
|
|
contentLayout.childForceExpandWidth = true;
|
|
contentLayout.childForceExpandHeight = false;
|
|
content.AddComponent<ContentSizeFitter>().verticalFit = ContentSizeFitter.FitMode.PreferredSize;
|
|
|
|
var item = Create("Item", content.transform);
|
|
var itemToggle = item.AddComponent<Toggle>();
|
|
item.AddComponent<Image>().color = new Color(0.96f, 0.97f, 0.98f);
|
|
item.AddComponent<LayoutElement>().preferredHeight = 32;
|
|
|
|
var checkmark = Create("Item Checkmark", item.transform).AddComponent<Image>();
|
|
checkmark.color = new Color(0.1f, 0.5f, 0.25f);
|
|
var checkmarkRect = checkmark.rectTransform;
|
|
checkmarkRect.anchorMin = new Vector2(0, 0.5f);
|
|
checkmarkRect.anchorMax = new Vector2(0, 0.5f);
|
|
checkmarkRect.sizeDelta = new Vector2(18, 18);
|
|
checkmarkRect.anchoredPosition = new Vector2(14, 0);
|
|
|
|
var itemLabel = Text(item.transform, "Option", 18);
|
|
Stretch(itemLabel.rectTransform, 32, 8, 4, 4);
|
|
itemToggle.targetGraphic = item.GetComponent<Image>();
|
|
itemToggle.graphic = checkmark;
|
|
|
|
scroll.viewport = viewport.GetComponent<RectTransform>();
|
|
scroll.content = contentRect;
|
|
dropdown.template = templateRect;
|
|
dropdown.itemText = itemLabel;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 执行 Slider 方法。
|
|
/// </summary>
|
|
public Slider Slider(Transform parent)
|
|
{
|
|
var go = Create("Slider", parent);
|
|
var slider = go.AddComponent<Slider>();
|
|
slider.minValue = 0;
|
|
slider.maxValue = 1;
|
|
var bg = Create("Background", go.transform).AddComponent<Image>();
|
|
bg.color = new Color(0.8f, 0.82f, 0.85f);
|
|
Stretch(bg.rectTransform, 0, 0, 10, 10);
|
|
var fill = Create("Fill", go.transform).AddComponent<Image>();
|
|
fill.color = new Color(0.1f, 0.55f, 0.35f);
|
|
Stretch(fill.rectTransform, 0, 0, 10, 10);
|
|
slider.fillRect = fill.rectTransform;
|
|
var layout = go.AddComponent<LayoutElement>();
|
|
layout.preferredHeight = 28;
|
|
return slider;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 执行 Section 方法。
|
|
/// </summary>
|
|
public GameObject Section(Transform parent, string title)
|
|
{
|
|
var section = Create("Section_" + title, parent);
|
|
var layout = section.AddComponent<VerticalLayoutGroup>();
|
|
layout.padding = new RectOffset(12, 12, 10, 10);
|
|
layout.spacing = 8;
|
|
layout.childControlWidth = true;
|
|
layout.childForceExpandWidth = true;
|
|
section.AddComponent<Image>().color = new Color(0.94f, 0.96f, 0.98f);
|
|
Text(section.transform, title, 24);
|
|
return section;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 执行 Row 方法。
|
|
/// </summary>
|
|
public GameObject Row(Transform parent)
|
|
{
|
|
var row = Create("Row", parent);
|
|
var layout = row.AddComponent<HorizontalLayoutGroup>();
|
|
layout.spacing = 8;
|
|
layout.childControlHeight = true;
|
|
layout.childControlWidth = true;
|
|
return row;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 执行 ScrollView 方法。
|
|
/// </summary>
|
|
public ScrollRect ScrollView(Transform parent, out Text contentText)
|
|
{
|
|
var root = Create("ScrollView", parent);
|
|
root.AddComponent<Image>().color = Color.white;
|
|
var viewport = Create("Viewport", root.transform);
|
|
var mask = viewport.AddComponent<Mask>();
|
|
mask.showMaskGraphic = false;
|
|
viewport.AddComponent<Image>().color = Color.white;
|
|
Stretch(viewport.GetComponent<RectTransform>());
|
|
var content = Create("Content", viewport.transform);
|
|
var contentRect = content.GetComponent<RectTransform>();
|
|
contentRect.anchorMin = new Vector2(0, 1);
|
|
contentRect.anchorMax = new Vector2(1, 1);
|
|
contentRect.pivot = new Vector2(0.5f, 1);
|
|
contentText = Text(content.transform, string.Empty, 18, TextAnchor.UpperLeft);
|
|
contentText.rectTransform.sizeDelta = new Vector2(0, 800);
|
|
Stretch(contentText.rectTransform);
|
|
var scroll = root.AddComponent<ScrollRect>();
|
|
scroll.viewport = viewport.GetComponent<RectTransform>();
|
|
scroll.content = contentRect;
|
|
scroll.horizontal = false;
|
|
var layout = root.AddComponent<LayoutElement>();
|
|
layout.preferredHeight = 180;
|
|
return scroll;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 执行 Create 方法。
|
|
/// </summary>
|
|
public static GameObject Create(string name, Transform parent)
|
|
{
|
|
var go = new GameObject(name, typeof(RectTransform));
|
|
go.transform.SetParent(parent, false);
|
|
return go;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 执行 Stretch 方法。
|
|
/// </summary>
|
|
public static void Stretch(RectTransform rect, float left = 0, float right = 0, float top = 0, float bottom = 0)
|
|
{
|
|
rect.anchorMin = Vector2.zero;
|
|
rect.anchorMax = Vector2.one;
|
|
rect.offsetMin = new Vector2(left, bottom);
|
|
rect.offsetMax = new Vector2(-right, -top);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 主面板,负责页面初始化和切换。
|
|
/// </summary>
|
|
public sealed class FaceModuleMainPanel : MonoBehaviour
|
|
{
|
|
// Inspector 绑定字段。
|
|
[SerializeField] private FaceModuleManager m_manager;
|
|
// Inspector 绑定字段。
|
|
[SerializeField] private FaceModuleStatusBar m_statusBar;
|
|
// Inspector 绑定字段。
|
|
[SerializeField] private FaceModuleNavigationPanel m_navigationPanel;
|
|
// Inspector 绑定字段。
|
|
[SerializeField] private FaceModuleLogPanel m_logPanel;
|
|
// Inspector 绑定字段。
|
|
[SerializeField] private FaceModuleLoadingView m_loadingView;
|
|
private readonly Dictionary<string, FaceModulePageBase> m_pages = new Dictionary<string, FaceModulePageBase>();
|
|
|
|
/// <summary>
|
|
/// 初始化主面板。
|
|
/// </summary>
|
|
public void Init(FaceModuleManager manager)
|
|
{
|
|
m_manager = manager;
|
|
m_statusBar?.Init(m_manager);
|
|
m_logPanel?.Init(m_manager);
|
|
m_navigationPanel?.Init(this);
|
|
|
|
foreach (var pair in m_pages)
|
|
{
|
|
pair.Value.Init(m_manager, m_logPanel, m_loadingView);
|
|
pair.Value.Hide();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 绑定公共组件。
|
|
/// </summary>
|
|
public void Bind(FaceModuleStatusBar statusBar, FaceModuleNavigationPanel navigationPanel, FaceModuleLogPanel logPanel, FaceModuleLoadingView loadingView)
|
|
{
|
|
m_statusBar = statusBar;
|
|
m_navigationPanel = navigationPanel;
|
|
m_logPanel = logPanel;
|
|
m_loadingView = loadingView;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 注册页面。
|
|
/// </summary>
|
|
public void RegisterPage(string pageName, string label, FaceModulePageBase page, FaceModuleUiBuilder builder)
|
|
{
|
|
m_pages[pageName] = page;
|
|
m_navigationPanel.AddNavigation(pageName, label, builder);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 显示指定页面。
|
|
/// </summary>
|
|
public void ShowPage(string pageName)
|
|
{
|
|
foreach (var pair in m_pages)
|
|
{
|
|
if (pair.Key == pageName)
|
|
{
|
|
pair.Value.Show();
|
|
}
|
|
else
|
|
{
|
|
pair.Value.Hide();
|
|
}
|
|
}
|
|
|
|
m_logPanel?.Info("切换页面: " + pageName);
|
|
}
|
|
}
|
|
|
|
}
|