From d7d2fec5368b7dfa627c1d2011a21e5caf15c5d1 Mon Sep 17 00:00:00 2001
From: ZHU MINCHAO <48154958+MinchaoZhu@users.noreply.github.com>
Date: Wed, 4 Nov 2026 09:57:18 +0800
Subject: [PATCH 1/8] ui: extract PledgerSkin common visual components and
refactor WalletWindow
---
src/Decentralized/PledgerSkin.cs | 255 ++++++++++++++++++++++++++++++
src/Decentralized/WalletWindow.cs | 79 ++-------
2 files changed, 265 insertions(+), 69 deletions(-)
create mode 100644 src/Decentralized/PledgerSkin.cs
diff --git a/src/Decentralized/PledgerSkin.cs b/src/Decentralized/PledgerSkin.cs
new file mode 100644
index 0000000..bbcfd01
--- /dev/null
+++ b/src/Decentralized/PledgerSkin.cs
@@ -0,0 +1,255 @@
+using System;
+using System.Collections.Generic;
+using TMPro;
+using UnityEngine;
+using UnityEngine.UI;
+
+namespace Decentralized
+{
+ ///
+ /// PLEDGER 硬件风格公共皮肤与组件:
+ /// - 机身:自画像素九宫格 ui_hw_shell(圆角石墨,左上受光边、右下背光边),3× 像素放大
+ /// - 刻字:游戏 machinery 窗口的标题栏与分隔线设为透明;标题当机身刻字(灰色粗体、字距 18、居中)
+ /// - 屏幕:黑色边框 ui_hw_bezel 包一块浅色墨水屏 ui_hw_paper(#D8DAD0),左右对称居中
+ /// - 文字:深墨色 Ink,次要信息灰 _dim,警示暗红 _note
+ /// - 按钮:黑底白字触控键 Key / KeyText;选中的项反色(白底黑框黑字加粗)加准星角标 ui_hw_sel,不用 [方括号]
+ /// - 数据表:列右对齐,整行可点,选中行整行反色(黑底白字)
+ /// - 滚动区:背景透明露出墨水屏底色,滚动条滑块为墨色
+ ///
+ public static class PledgerSkin
+ {
+ public static readonly Color Ink = new Color(0.12f, 0.12f, 0.14f);
+ public static readonly Color Key = new Color(0.16f, 0.16f, 0.19f);
+ public static readonly Color KeyText = new Color(0.93f, 0.93f, 0.90f);
+ public static readonly Color Dim = new Color(0.38f, 0.39f, 0.40f);
+ public static readonly Color Alert = new Color(0.62f, 0.16f, 0.12f);
+ public static readonly Color Title = new Color(0.62f, 0.64f, 0.70f);
+ public static readonly Color Paper = new Color(0.847f, 0.855f, 0.816f); // #D8DAD0
+ public static readonly Color RowNormal = new Color(0f, 0f, 0f, 0.04f);
+ public static readonly Color Transparent = new Color(0f, 0f, 0f, 0f);
+ public static readonly Color Separator = new Color(0.12f, 0.12f, 0.14f, 0.18f);
+
+ private static readonly Dictionary Sliced = new Dictionary();
+
+ /// 把本 mod 的像素图做成九宫格贴到 Image 上:边角 border 像素不拉伸,一个像素放大成 3 个界面单位。
+ public static void Slice(Image img, string key, int border)
+ {
+ var src = Items.ModSprite(key);
+ if (img == null || src == null) return;
+ if (!Sliced.TryGetValue(key, out var sp))
+ {
+ src.texture.filterMode = FilterMode.Point;
+ sp = Sprite.Create(src.texture, src.rect, new Vector2(0.5f, 0.5f), src.pixelsPerUnit, 0, SpriteMeshType.FullRect, new Vector4(border, border, border, border));
+ Sliced[key] = sp;
+ }
+ img.sprite = sp;
+ img.type = Image.Type.Sliced;
+ img.pixelsPerUnitMultiplier = 100f / (3f * sp.pixelsPerUnit);
+ img.color = Color.white;
+ }
+
+ /// 屏上的选择按钮(币种、档位、子页):选中的一个画成白底黑框黑字、四角带准星角标。
+ public static void Pick(CustomUIBuilder b, string text, bool selected, Action onClick, ICollection picked)
+ {
+ b.AddButton(text, onClick);
+ if (selected && picked != null) picked.Add(b.Last.root);
+ }
+
+ /// 数据表行定义:整行高亮与点击回调。
+ public sealed class RowDef
+ {
+ public GameObject Root;
+ public Image Image;
+ public bool Selected;
+ public Action OnClick;
+ }
+
+ /// 数据表的一行:列右对齐(第一列默认左对齐),整行可点击,选中行反色。
+ public static void TableRow(CustomUIBuilder b, float totalW, float height, float[] widths, TextAnchor[] aligns, string[] texts, bool selected, Action onClick, ICollection rows, ICollection clear)
+ {
+ b.BeginPanel(totalW, height, TextAnchor.MiddleLeft, 6f, 0f);
+ var root = b.Last.root;
+ var img = b.Last.image;
+ if (rows != null) rows.Add(new RowDef { Root = root, Image = img, Selected = selected, OnClick = onClick });
+ b.BeginRow(0f, TextAnchor.MiddleLeft);
+ for (int i = 0; i < widths.Length; i++)
+ {
+ float w = widths[i];
+ var align = (aligns != null && i < aligns.Length) ? aligns[i] : (i == 0 ? TextAnchor.MiddleLeft : TextAnchor.MiddleRight);
+ b.BeginPanel(w, height - 6f, align, 0f, 0f);
+ if (clear != null) clear.Add(b.Last.image);
+ b.AddLabel(i < texts.Length ? texts[i] : "");
+ b.End();
+ }
+ b.End();
+ b.End();
+ }
+
+ /// 数据表表头:与表行同对齐方式,加粗,无底色。
+ public static void TableHeader(CustomUIBuilder b, float totalW, float height, float[] widths, TextAnchor[] aligns, string[] headers, ICollection clear)
+ {
+ b.BeginPanel(totalW, height, TextAnchor.MiddleLeft, 6f, 0f);
+ if (clear != null) clear.Add(b.Last.image);
+ b.BeginRow(0f, TextAnchor.MiddleLeft);
+ for (int i = 0; i < widths.Length; i++)
+ {
+ float w = widths[i];
+ var align = (aligns != null && i < aligns.Length) ? aligns[i] : (i == 0 ? TextAnchor.MiddleLeft : TextAnchor.MiddleRight);
+ b.BeginPanel(w, height - 6f, align, 0f, 0f);
+ if (clear != null) clear.Add(b.Last.image);
+ b.AddLabel(i < headers.Length ? "" + headers[i] + "" : "");
+ b.End();
+ }
+ b.End();
+ b.End();
+ }
+
+ /// 应用整套 PLEDGER 皮肤:机身、边框、墨水屏、刻字、文字颜色、触控键、滚动条与数据表行。
+ public static void Apply(CustomUIWindow win, RectTransform screen, Image bezel, Image paper, ICollection picked, ICollection clear, IEnumerable rows = null)
+ {
+ if (win?.root == null) return;
+ Slice(win.root.GetComponent(), "ui_hw_shell", 10);
+ if (bezel != null) Slice(bezel, "ui_hw_bezel", 5);
+ if (paper != null) Slice(paper, "ui_hw_paper", 3);
+
+ // 标题栏和分隔线透明,标题当机身刻字
+ foreach (var name in new[] { "header", "rule" })
+ {
+ var img = win.root.transform.Find(name)?.GetComponent();
+ if (img != null) img.color = Transparent;
+ }
+ if (win.frame?.titleText != null)
+ {
+ var t = win.frame.titleText;
+ t.color = Title;
+ t.fontStyle = FontStyles.Bold;
+ t.characterSpacing = 18f;
+ t.fontSize = 16f;
+ t.horizontalAlignment = HorizontalAlignmentOptions.Center;
+ }
+ var content = win.frame?.contentRoot?.GetComponent();
+ if (content != null) content.childAlignment = TextAnchor.UpperCenter;
+
+ if (screen == null) return;
+
+ // 收集所有表格行 root,避免按钮样式覆盖整行
+ var rowRoots = new HashSet();
+ if (rows != null)
+ {
+ foreach (var r in rows)
+ if (r?.Root != null) rowRoots.Add(r.Root);
+ }
+
+ // 屏上文字:note 暗红,dim 灰,其余深墨色
+ foreach (var t in screen.GetComponentsInChildren(true))
+ {
+ if (t.GetComponentInParent