using Aisu.SIM1.Face.Core;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace Aisu.SIM1.Face.Samples.FaceDetectionDemo
{
///
/// 人脸检测 Demo 场景 UI 动态构建器。
///
public sealed class FaceDetectionDemoBootstrap : MonoBehaviour
{
private Font m_font; // UI 字体。
private void Awake()
{
Build();
}
///
/// 构建 MVP 测试页面。
///
public void Build()
{
m_font = Resources.GetBuiltinResource("Arial.ttf");
EnsureEventSystem();
var manager = FindObjectOfType() ?? new GameObject("FaceModuleManager").AddComponent();
var camera = gameObject.AddComponent();
var logger = gameObject.AddComponent();
var userList = gameObject.AddComponent();
var controller = gameObject.AddComponent();
var canvasGo = Create("FaceDetectionDemoCanvas", transform);
var canvas = canvasGo.AddComponent();
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
canvasGo.AddComponent();
var scaler = canvasGo.AddComponent();
scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
scaler.referenceResolution = new Vector2(1280, 720);
var root = Create("Root", canvasGo.transform);
Stretch(root.GetComponent());
var rootLayout = root.AddComponent();
rootLayout.padding = new RectOffset(16, 16, 16, 16);
rootLayout.spacing = 10;
rootLayout.childControlWidth = true;
rootLayout.childForceExpandWidth = true;
var title = Text(root.transform, "SI-M1 Face Detection Demo", 28, TextAnchor.MiddleLeft);
title.color = new Color(0.05f, 0.07f, 0.1f);
var statusText = Text(root.transform, "摄像头未打开 | 模组未连接", 20, TextAnchor.MiddleLeft);
statusText.color = new Color(0.12f, 0.18f, 0.24f);
var body = Create("Body", root.transform);
body.AddComponent().flexibleHeight = 1f;
var bodyLayout = body.AddComponent();
bodyLayout.spacing = 12;
bodyLayout.childControlHeight = true;
bodyLayout.childForceExpandHeight = true;
var left = Panel(body.transform, "LeftPanel", new Color(0.95f, 0.97f, 1f));
left.AddComponent().flexibleWidth = 1f;
var leftLayout = left.AddComponent();
leftLayout.padding = new RectOffset(10, 10, 10, 10);
leftLayout.spacing = 8;
leftLayout.childControlWidth = true;
leftLayout.childForceExpandWidth = true;
var right = Panel(body.transform, "RightPanel", new Color(0.98f, 0.98f, 0.96f));
right.AddComponent().preferredWidth = 430;
var rightLayout = right.AddComponent();
rightLayout.padding = new RectOffset(10, 10, 10, 10);
rightLayout.spacing = 8;
rightLayout.childControlWidth = true;
rightLayout.childForceExpandWidth = true;
var cameraDropdown = Dropdown(left.transform, "未发现摄像头");
var cameraRow = Row(left.transform);
var refreshCameraButton = Button(cameraRow.transform, "刷新摄像头");
var openCameraButton = Button(cameraRow.transform, "打开摄像头");
var closeCameraButton = Button(cameraRow.transform, "关闭摄像头");
var previewFrame = Panel(left.transform, "CameraPreview", Color.black);
previewFrame.AddComponent().flexibleHeight = 1f;
var preview = Create("Preview", previewFrame.transform).AddComponent();
preview.color = Color.white;
var previewRect = preview.GetComponent();
previewRect.anchorMin = Vector2.zero;
previewRect.anchorMax = Vector2.one;
previewRect.offsetMin = Vector2.zero;
previewRect.offsetMax = Vector2.zero;
var aspect = preview.gameObject.AddComponent();
aspect.aspectMode = AspectRatioFitter.AspectMode.FitInParent;
Text(right.transform, "串口 / 模组", 22, TextAnchor.MiddleLeft);
var serialDropdown = Dropdown(right.transform, "手动输入串口");
var serialInput = Input(right.transform, "串口名,例如 COM3 或 /dev/ttyUSB0");
var baudInput = Input(right.transform, "115200");
var serialRow = Row(right.transform);
var refreshSerialButton = Button(serialRow.transform, "刷新串口");
var connectButton = Button(serialRow.transform, "连接模组");
var disconnectButton = Button(serialRow.transform, "断开模组");
Text(right.transform, "人脸操作", 22, TextAnchor.MiddleLeft);
var userNameInput = Input(right.transform, "User");
var adminToggle = Toggle(right.transform, "管理员", false);
var faceRow = Row(right.transform);
var enrollButton = Button(faceRow.transform, "录入人脸");
var verifyButton = Button(faceRow.transform, "检测人脸");
var resultText = Text(right.transform, "结果: -", 20, TextAnchor.UpperLeft);
resultText.gameObject.AddComponent().preferredHeight = 58;
Text(right.transform, "人脸数据", 22, TextAnchor.MiddleLeft);
var userButtonRow = Row(right.transform);
var refreshUsersButton = Button(userButtonRow.transform, "刷新用户列表");
var userScroll = ScrollView(right.transform, out var userContent);
userScroll.GetComponent().preferredHeight = 170;
Text(root.transform, "操作日志", 22, TextAnchor.MiddleLeft);
var logRow = Row(root.transform);
var clearLogButton = Button(logRow.transform, "清空日志");
var logScroll = ScrollView(root.transform, out var logContent);
logScroll.GetComponent().preferredHeight = 150;
var logText = Text(logContent, string.Empty, 18, TextAnchor.UpperLeft);
logText.font = m_font;
logText.color = Color.white;
logText.horizontalOverflow = HorizontalWrapMode.Wrap;
logText.verticalOverflow = VerticalWrapMode.Overflow;
logText.gameObject.AddComponent().flexibleWidth = 1f;
logContent.gameObject.GetComponent().color = new Color(0.07f, 0.08f, 0.09f);
camera.Bind(preview, aspect, logger);
logger.Bind(logText, logScroll);
userList.Bind(userContent, m_font, controller.DeleteUser);
controller.Bind(manager, camera, logger, userList, cameraDropdown, serialDropdown, serialInput, baudInput, userNameInput, adminToggle, statusText, resultText);
refreshCameraButton.onClick.AddListener(controller.RefreshCameras);
openCameraButton.onClick.AddListener(controller.OpenCamera);
closeCameraButton.onClick.AddListener(controller.CloseCamera);
refreshSerialButton.onClick.AddListener(controller.RefreshSerialPorts);
connectButton.onClick.AddListener(controller.ConnectModule);
disconnectButton.onClick.AddListener(controller.DisconnectModule);
enrollButton.onClick.AddListener(controller.EnrollFace);
verifyButton.onClick.AddListener(controller.VerifyFace);
refreshUsersButton.onClick.AddListener(controller.RefreshUsers);
clearLogButton.onClick.AddListener(logger.Clear);
}
private static void EnsureEventSystem()
{
if (FindObjectOfType() != null)
{
return;
}
var eventSystem = new GameObject("EventSystem");
eventSystem.AddComponent();
eventSystem.AddComponent();
}
private GameObject Panel(Transform parent, string name, Color color)
{
var go = Create(name, parent);
go.AddComponent().color = color;
return go;
}
private GameObject Row(Transform parent)
{
var row = Create("Row", parent);
var layout = row.AddComponent();
layout.spacing = 8;
layout.childControlWidth = true;
layout.childForceExpandWidth = true;
row.AddComponent().preferredHeight = 44;
return row;
}
private Button Button(Transform parent, string text)
{
var go = Create(text, parent);
go.AddComponent().color = new Color(0.78f, 0.86f, 0.95f);
var button = go.AddComponent();
go.AddComponent().preferredHeight = 42;
var label = Text(go.transform, text, 20, TextAnchor.MiddleCenter);
Stretch(label.GetComponent());
return button;
}
private InputField Input(Transform parent, string placeholder)
{
var go = Create("InputField", parent);
go.AddComponent().color = Color.white;
go.AddComponent().preferredHeight = 42;
var input = go.AddComponent();
var text = Text(go.transform, string.Empty, 20, TextAnchor.MiddleLeft);
var textRect = text.GetComponent();
Stretch(textRect);
textRect.offsetMin = new Vector2(10, 0);
textRect.offsetMax = new Vector2(-10, 0);
input.textComponent = text;
var placeholderText = Text(go.transform, placeholder, 20, TextAnchor.MiddleLeft);
placeholderText.color = new Color(0.45f, 0.48f, 0.52f);
var placeholderRect = placeholderText.GetComponent();
Stretch(placeholderRect);
placeholderRect.offsetMin = new Vector2(10, 0);
placeholderRect.offsetMax = new Vector2(-10, 0);
input.placeholder = placeholderText;
return input;
}
private Toggle Toggle(Transform parent, string label, bool value)
{
var row = Row(parent);
row.GetComponent().childForceExpandWidth = false;
var toggleGo = Create("Toggle", row.transform);
toggleGo.AddComponent().preferredWidth = 34;
var toggle = toggleGo.AddComponent();
var background = Create("Background", toggleGo.transform).AddComponent();
background.color = Color.white;
Stretch(background.GetComponent());
var checkmark = Create("Checkmark", background.transform).AddComponent();
checkmark.color = new Color(0.18f, 0.45f, 0.85f);
var checkRect = checkmark.GetComponent();
checkRect.anchorMin = new Vector2(0.2f, 0.2f);
checkRect.anchorMax = new Vector2(0.8f, 0.8f);
checkRect.offsetMin = Vector2.zero;
checkRect.offsetMax = Vector2.zero;
toggle.targetGraphic = background;
toggle.graphic = checkmark;
toggle.isOn = value;
Text(row.transform, label, 20, TextAnchor.MiddleLeft).gameObject.AddComponent().flexibleWidth = 1f;
return toggle;
}
private Dropdown Dropdown(Transform parent, string initial)
{
var go = Create("Dropdown", parent);
var image = go.AddComponent();
image.color = Color.white;
go.AddComponent().preferredHeight = 42;
var dropdown = go.AddComponent();
dropdown.targetGraphic = image;
var label = Text(go.transform, initial, 20, TextAnchor.MiddleLeft);
var labelRect = label.GetComponent();
Stretch(labelRect);
labelRect.offsetMin = new Vector2(10, 0);
labelRect.offsetMax = new Vector2(-30, 0);
var arrow = Text(go.transform, "v", 18, TextAnchor.MiddleCenter);
var arrowRect = arrow.GetComponent();
arrowRect.anchorMin = new Vector2(1f, 0f);
arrowRect.anchorMax = Vector2.one;
arrowRect.pivot = new Vector2(1f, 0.5f);
arrowRect.sizeDelta = new Vector2(30, 0);
arrowRect.anchoredPosition = Vector2.zero;
dropdown.template = CreateDropdownTemplate(go.transform, out var itemText);
dropdown.captionText = label;
dropdown.itemText = itemText;
dropdown.options.Add(new Dropdown.OptionData(initial));
dropdown.value = 0;
return dropdown;
}
private RectTransform CreateDropdownTemplate(Transform parent, out Text itemText)
{
var template = Panel(parent, "Template", Color.white);
template.SetActive(false);
var templateRect = template.GetComponent();
templateRect.anchorMin = new Vector2(0f, 0f);
templateRect.anchorMax = new Vector2(1f, 0f);
templateRect.pivot = new Vector2(0.5f, 1f);
templateRect.sizeDelta = new Vector2(0f, 180f);
templateRect.anchoredPosition = new Vector2(0f, 2f);
var scrollRect = template.AddComponent();
scrollRect.horizontal = false;
var viewport = Panel(template.transform, "Viewport", new Color(1f, 1f, 1f, 0.98f));
viewport.AddComponent().showMaskGraphic = false;
Stretch(viewport.GetComponent());
var contentGo = Create("Content", viewport.transform);
var contentRect = contentGo.GetComponent();
contentRect.anchorMin = new Vector2(0f, 1f);
contentRect.anchorMax = Vector2.one;
contentRect.pivot = new Vector2(0.5f, 1f);
contentRect.offsetMin = Vector2.zero;
contentRect.offsetMax = Vector2.zero;
contentGo.AddComponent().childForceExpandWidth = true;
contentGo.AddComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize;
var itemGo = Create("Item", contentGo.transform);
var itemToggle = itemGo.AddComponent();
itemGo.AddComponent().preferredHeight = 36;
var itemBackground = itemGo.AddComponent();
itemBackground.color = Color.white;
itemToggle.targetGraphic = itemBackground;
var checkmark = Text(itemGo.transform, "✓", 18, TextAnchor.MiddleCenter);
var checkRect = checkmark.GetComponent();
checkRect.anchorMin = new Vector2(0f, 0f);
checkRect.anchorMax = new Vector2(0f, 1f);
checkRect.pivot = new Vector2(0f, 0.5f);
checkRect.sizeDelta = new Vector2(28f, 0f);
checkRect.anchoredPosition = Vector2.zero;
itemToggle.graphic = checkmark;
var itemLabel = Text(itemGo.transform, "Option", 18, TextAnchor.MiddleLeft);
var itemLabelRect = itemLabel.GetComponent();
Stretch(itemLabelRect);
itemLabelRect.offsetMin = new Vector2(32f, 0f);
itemLabelRect.offsetMax = new Vector2(-8f, 0f);
scrollRect.viewport = viewport.GetComponent();
scrollRect.content = contentRect;
itemText = itemLabel;
return templateRect;
}
private ScrollRect ScrollView(Transform parent, out Transform content)
{
var root = Panel(parent, "ScrollView", new Color(0.12f, 0.13f, 0.15f));
var scrollRect = root.AddComponent();
scrollRect.horizontal = false;
root.AddComponent().preferredHeight = 160;
var viewport = Panel(root.transform, "Viewport", new Color(0.12f, 0.13f, 0.15f));
viewport.AddComponent().showMaskGraphic = false;
Stretch(viewport.GetComponent());
var contentGo = Panel(viewport.transform, "Content", new Color(0.12f, 0.13f, 0.15f));
content = contentGo.transform;
var contentRect = contentGo.GetComponent();
contentRect.anchorMin = new Vector2(0, 1);
contentRect.anchorMax = new Vector2(1, 1);
contentRect.pivot = new Vector2(0.5f, 1f);
contentRect.offsetMin = new Vector2(8, 0);
contentRect.offsetMax = new Vector2(-8, 0);
contentGo.AddComponent().spacing = 6;
contentGo.AddComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize;
scrollRect.viewport = viewport.GetComponent();
scrollRect.content = contentRect;
return scrollRect;
}
private Text Text(Transform parent, string value, int size, TextAnchor alignment)
{
var go = Create("Text", parent);
var text = go.AddComponent();
text.font = m_font;
text.fontSize = size;
text.color = Color.black;
text.alignment = alignment;
text.text = value;
text.horizontalOverflow = HorizontalWrapMode.Wrap;
text.verticalOverflow = VerticalWrapMode.Overflow;
return text;
}
private static GameObject Create(string name, Transform parent)
{
var go = new GameObject(name, typeof(RectTransform));
go.transform.SetParent(parent, false);
return go;
}
private static void Stretch(RectTransform rect)
{
rect.anchorMin = Vector2.zero;
rect.anchorMax = Vector2.one;
rect.offsetMin = Vector2.zero;
rect.offsetMax = Vector2.zero;
}
}
}