Files
gplan-2/game/presentation/views/ui_kit.gd
T
DKChan c08a901d2d 暂停层组A:只读覆盖层本体+badge工厂件收编+营地钥石徽迁移(迭代17卡③T1)
- ui_kit.gd 新增 KeystoneBadge 徽章工厂件(Q10 甲裁决收编):钥石徽形制参数化
  (烛光黄底 CANDLE+木描边 CAMP_HEADER_BAND+icon_rect 守卫装载缺位纯文本回退+INK_ON_GOLD
  深墨字,value_label/set_text 面),消费方=营地覆盖层+暂停层——零新素材零新色值,值全取现行 token
- camp_overlay_view.gd 钥石徽本地拼装(原 :79-92 panel_flat+icon_rect+label 三段)迁
  UiKit.keystone_badge 工厂调用,形制参数逐项同原装配=观感零变化,消单点重复
- 新增 presentation/views/pause_overlay.gd:局内暂停层·只读——标题区+钥石余额行(badge 工厂件)
  +属性锻造三行已购等级/道具工坊六行解锁态(文案判据同营地柜台:退役>封存>已解锁>未解锁)
  /天赋树已购概览(已购节点等级+Fork 激活侧聚合文案,空态句)+「继续」关闭钮(代拍板:紧凑 90
  返回钮形制沿用设置面先例);全部经 UiKit 工厂拼装(modal_shell/panel_skinned/label/button/
  keystone_badge)零裸色值零自建样式(R1);零 tween 零 AnimationPlayer=零动效直开直关(R5/P4 甲)
- main.gd 接线:PauseOverlay 实例化+登记 modal_stack 登记册设置层同档(确认弹窗之前——P2 甲,
  登记序=树序=z 序纪律段同步换代)+closed 接 _on_pause_closed(模态栈单口收口)+
  _pause_data() 快照投影(R4:实时读 MetaProgression 访问器 keystone_balance/forge_level/
  workshop_status/tree_camp_rows/tree_fork_groups,行序复用营地屏同序〔MetaTable.FORGE_ATTRS〕,
  快照仅单次开层生命周期、每次开层重建)+_open_pause_layer() 开层口(_open_modal 单口)
- 纯呈现零数值改动=零 MC;核心层零改动(meta_progression/save_manager 未触碰)
- 轻闸:冒烟 783/0 + ui_struct_probe PASS(基线对拍 T0:783/0 零漂移)
2026-10-06 21:41:14 +08:00

757 lines
42 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# VERTICAL SLICE - NOT FOR PRODUCTION
# Date: 2026-09-11(2026-09-20 样式收敛批:全站色板与 StyleBox 工厂单点化)
# 呈现层共享小部件工厂 + 全站取色/描边唯一出口——各视图禁再内联 Color8/StyleBoxFlat
# 字面量;美术批换皮只改本文件(值 = 收敛时各视图现值快照,本批零视觉变化)。
class_name UiKit
extends RefCounted
# ── 色板(语义族;值 = 2026-09-20 收敛时现值) ──
const INK := Color8(51, 38, 27) # 墨族——正文/数字(对格底 ≥4.5:1)
const INK_COLD := Color8(19, 46, 54) # 青墨 1-3 #132E36(数字墨三档·冷=稀疏——样张 v2 定稿 2026-09-28;亮瓦自适应深墨族档兼色牌后备路径)
const INK_OCHRE := Color8(60, 37, 12) # 赭墨 4-5 #3C250C(数字墨三档·中;亮瓦自适应深墨族档兼色牌后备路径)
const INK_RUST := Color8(74, 30, 20) # 锈墨 6-8 #4A1E14(数字墨三档·暖=密集;亮瓦自适应深墨族档兼色牌后备路径)
# ── 亮墨三档(圣经 v3.5 冻结④ 2026-09-30——数字直画已开格底:色温承袭密度语义,冷亮/暖白/金亮;
# 实测对比 6.0–6.3:1(A3a);金亮低饱和避奖励金语义;色牌后备组用深墨族上牌〔12:1〕。
# 2026-10-01 修订:亮墨族限暗纹理板(dungeon),亮筋纹板按 _digit_ink_mode 自适应切深墨三档)──
const INK_L_COLD := Color8(216, 236, 240) # 冷亮 1-3 #D8ECF0
const INK_L_WARM := Color8(240, 226, 200) # 暖白 4-5 #F0E2C8(关卡屏 WARM 同族)
const INK_L_GOLD := Color8(248, 227, 168) # 金亮 6-8 #F8E3A8
const INK_LABEL := Color8(46, 34, 24) # 标签深墨
const INK_TITLE := Color8(30, 26, 22) # 亮底标题墨
const INK_ROUTE := Color8(24, 20, 16) # 节点图最深墨
const INK_ON_GOLD := Color8(60, 44, 20) # 金底上的深墨
const PANEL_DATA := Color8(233, 217, 184) # 面板底·数据框(AP 条/状态槽)#E9D9B8(P7 乙 2026-10-06——demo 唯一来源对齐,Δ≤6/255)
const PANEL_BORDER := Color8(120, 110, 88) # 面板细线框;兼熄灭点阵(DOT_OFF 别名)#786E58(P7 乙同上)
const GOLD := Color8(240, 168, 40) # 奖励金
const GOLD_DEEP := Color8(176, 122, 20) # 深金(金框描边/达意金)
const GOLD_INK := Color8(116, 80, 11) # 深金墨(意图蓄力徽标墨——PANEL_DATA 垫牌上 4.9:1,UI 设计对齐批 N1 2026-10-06)
const GOLD_LIT := Color8(230, 190, 90) # 亮金(候选高亮)
const GOLD_DIM := Color8(140, 116, 60) # 暗金(已过节点)
const GOLD_DONE := Color8(104, 86, 44) # 已过节点暗金·关卡画面呈现态(关卡画面样张 v1 冻结新值 #68562C——旧 GOLD_DIM #8C743C 对双色墨均 <4.5:1 对比硬指标逼守;GOLD_DIM 他用不动)
const SEAL_BLACK := Color8(0, 0, 0) # 封印黑 #000000——封印(插旗)宣告色(2026-09-28
# 用户拍板旗朱→封印紫→封印黑:紫色与微光蓝族色相冲突,黑色无色相天然避开禁入带
# 妨害紫 281° 各留 12-14° 间距;候选值样张批冻结;旗朱 #C63D22 退役)
const RED_DEEP := Color8(150, 60, 48) # 深朱(敌卡框/敌像)
const RED_MINE := Color8(150, 44, 36) # 引爆雷字朱(已开雷格"雷"字)
const RED_FORBID := Color8(120, 60, 50) # 禁止框朱
const RED_ALERT := Color8(200, 60, 48) # 报错朱
const HERO_BLUE := Color8(46, 134, 222) # 小人蓝 #2E86DE(样张 v2 定稿 2026-09-28——圣经 §4.1 候选值冻结;旧 #2E6E8E 收敛期快照退役)
const HERO_BLUE_DEEP := Color8(30, 90, 150) # 小人蓝同族深档(意图增益徽标墨——PANEL_DATA 垫牌上 4.8:1;命名同 CONTACT_GLOW_DEEP 规则,UI 设计对齐批 N1 2026-10-06)
const BOSS_PURPLE := Color8(143, 47, 106) # 紫赤(Boss 专属)
const DEBUFF_PURPLE := Color8(122, 66, 148) # 妨害紫(意图 ☠)
# ── 类型色五族(UI 设计对齐批 N4c 2026-10-06——值 = demo --tg-* 冻结抄录〔任务书 §2.2 抄录表〕;
# 前四族 = 实机现值搬运、第五族群伤 demo 值补族——实机原完全缺位,消费面 = tg_band() 同源判定)──
const TG_BUFF := Color8(194, 178, 100) # 通用增益/默认带
const TG_HEAL := Color8(102, 164, 87) # 回复
const TG_MAGIC := Color8(138, 114, 196) # 魔法
const TG_PIERCE := Color8(87, 160, 160) # 穿甲
const TG_AOE := Color8(226, 86, 118) # 群伤/AOE #E25676(demo --tg-aoe)
# 类型色带分族判定(N4c/Q4 甲——三选一与商店货架同源同判:id/kind 小写子串匹配,默认=通用增益带。
# E5 回滚 2026-10-06:审计实证 demo「工兵爆破」卡=紫带(magic 族)——blast 归回 TG_MAGIC,
# TG_AOE 只接 aoe 键(spark_aoe 消费面保留);max_hp 含 hp 归回复族)
static func tg_band(id_lower: String) -> Color:
if id_lower.contains("heal") or id_lower.contains("hp"):
return TG_HEAL
if id_lower.contains("aoe"):
return TG_AOE
if id_lower.contains("blast") or id_lower.contains("magic"):
return TG_MAGIC
if id_lower.contains("pierce"):
return TG_PIERCE
return TG_BUFF
# ── 呈现散件裸色搬运(UI 设计对齐批 N4a 2026-10-06——原值搬运零观感;本节立后 ui_kit 之外
# 呈现层裸 Color8 = 0,守卫 = ui_contrast_probe 静态段)──
const INK_HINT := Color8(110, 69, 52) # 提示/警示暖棕墨(设置警示文/地图底栏组标)
const INK_ON_DEEP := Color8(246, 238, 218) # 深底标题亮墨(主菜单标题/营地柜台等级章字)
const TITLE_PEDESTAL := Color8(90, 74, 56) # 标题屏底座石台面(椭圆 17 底座;描边=BG_ROOT)
const MAP_BAR_PLATE := Color8(240, 228, 200) # 关卡地图底栏亮板
const MAP_VDIV := Color8(201, 184, 142) # 关卡地图底栏竖分隔线(1px)
const HINT_STRIP_EDGE := Color8(74, 62, 48) # 棋盘底提示条描边(半透明罩层板边)
const RAP_DOT_CORE_WARM := Color8(248, 226, 168) # AP 点阵亮心(烛光黄族)
const RAP_DOT_LIT_BLUE := Color8(105, 161, 232) # RAP 点阵亮面(小人蓝族)
const RAP_DOT_CORE_BLUE := Color8(156, 194, 240) # RAP 点阵亮心(小人蓝族)
const E_FACADE_FOOT_COL := Color8(52, 42, 32) # 墙脚阴线(方向稿同色——原 board_cell_unit 局部)
const FOG_MEMORY_PLATE := Color8(58, 58, 58) # 记忆板 #3A3A3A(§6.5——原 board_cell_unit 局部)
const FOG_MEMORY_INK := Color8(196, 196, 196) # 记忆墨 #C4C4C4(对记忆板 ≈6.3:1——原局部)
const FOG_OUTLINE := Color8(30, 24, 18) # 雾面轮廓墨(原 board_cell_unit _draw 局部 oc)
const ICON_TINT_ON_GOLD := Color(24.0 / 240.0, 20.0 / 226.0, 16.0 / 200.0) # 当前金底图标墨 = WARM 分量乘法调制(双色墨制——原 level_map_screen 局部)
const DUSK := Color8(90, 108, 140) # 暮蓝(全作唯一暮蓝档——失败终局)
const DIM_NIGHT := Color8(24, 18, 12) # 终局压暗底 #18120C(demo 结算仪式暗纱——胜利侧 dim 基色)
const WARM := Color8(240, 226, 200) # 暖白(暗底正文)
const WARM_SOFT := Color8(232, 213, 168) # 暖白次级(说明文)#E8D5A8(M0 换代——灰米 #C8BCA6 退役,配色总纲「米色去灰」)
const WARM_FAINT := Color8(205, 184, 140) # 暖白弱化(帮助文)#CDB88C(M0 换代,同上)
const STONE := Color8(120, 110, 98) # 灰石(通道/箭头/失效)
const STONE_FAINT := Color8(140, 124, 100) # 灰石弱化(占位提示文)
const STONE_DEEP := Color8(85, 77, 65) # 灰石深化(「已用」槽位墨——PANEL_DATA 槽牌 5.6:1 / 石皮 5.0:1,UI 设计对齐批 N1 2026-10-06;旧 STONE_FAINT 对亮底双向 <4.5:1)
const DARK_UPCOMING := Color8(64, 58, 52) # 未达暗字(底/文同值)
const BRICK := Color8(201, 175, 131) # 棋盘·未开掩尘地砖 #C9AF83
const FLOOR := Color8(179, 154, 120) # 棋盘·已开裸土 #B39A78
const CONTACT_GLOW := Color8(214, 236, 255) # 可挖域接触带微光(蓝族浅一档·近白蓝——art-bible §3.2 v2.8 定稿;
# 旧绿相 DIG_EDGE 随画法切换退役 2026-09-26 批)
const CONTACT_GLOW_DEEP := Color8(150, 190, 230) # 接触带呼吸暗端(lerp 目标)
const DIGIT_PLATE := Color8(245, 234, 211) # 数字色牌(素色暖纸白直角牌——art-bible §4.1;数字一律垫牌,
# 可读不依赖格底——原则二;2026-09-26 呈现修复批落码)
const DIGIT_PLATE_EDGE := Color8(210, 198, 172) # 色牌牌缘线
const SLOT_BACKDROP := Color8(62, 54, 44) # B 区状态图标槽实底(深墨棕——白图标对比承载)#3E362C(P7 乙 2026-10-06 对齐)
const BG_ROOT := Color8(58, 46, 34) # 屏幕根底 #3A2E22(UI 落码批 M0 2026-10-04——配色总纲「提暖提亮禁近黑」,旧近黑 #1E1914 退役)
const BG_OVERLAY := Color8(54, 43, 32) # 模态遮罩底 #362B20(同批换代,旧 #1C1612 退役)
const BG_BRIGHT := Color8(69, 55, 35) # 暖亮档底 #453723(M0 新增——标题/营地/商店等暖亮屏根底)
const CANDLE := Color8(246, 198, 103) # 烛光黄 #F6C667(M0 新增——主操作色/候选节点/钥石徽章底;
# 与 §4.1 奖励金 #F0A828 分轨——圣经 §7.2 琥珀双岗注记)
const DIM := Color(0.08, 0.06, 0.05) # 遮罩基色(用时乘 alpha)
const TOAST_BG := Color(0.94, 0.91, 0.84, 0.95) # toast 面底
const TIP_BG := Color(0.16, 0.12, 0.09, 0.96) # tooltip 深底
# 呈现杂色档(T4 裸色收口批 2026-10-02——各视图裸 Color8 字面量单点化;值 = 收口时各来源行现值快照)
const STAGE_BG := Color8(62, 52, 42) # B 区舞台暗底(方向稿 render_battle_screen——battle_stage_view 面板底)
const BEAM_LIGHT := Color8(255, 236, 200) # B 区顶光柱暖光(用时乘 alpha 渐隐——battle_stage_view 光柱层)
const STAND_STONE := Color8(150, 130, 100) # 站桩石板地台·板身(standee_body 方向稿取色)
const STAND_STONE_EDGE := Color8(90, 76, 58) # 站桩石板地台·描边
const STAND_STONE_RIM := Color8(196, 172, 132) # 站桩石板地台·顶缘受光线(board_cell_unit 口沿高光已并档至此——清扫批 2026-10-02)
const CAMP_HEADER_BAND := Color8(110, 69, 38) # 营地柜台头带深棕 #6E4526(4-camp.png 方向稿取色——P7 乙 2026-10-06 对齐 demo --wood-deep)
const CAMP_ROW_PLATE := Color8(240, 230, 206) # 营地行板暖米(同上)
const CAMP_BADGE_PANEL := Color8(40, 32, 24) # 营地钥石徽暗底(同上)
const HP_BAR_BACK := Color8(40, 34, 32) # 迷你血条深底 #282220(关卡画面样张 v1 冻结——P7 乙 2026-10-06 对齐;兼 N1 血数垫牌)
# BBCode 十六进制(RichTextLabel 用;与上同值)
const HEX_GOLD := "f0a828"
const HEX_INK := "33261b"
const HEX_ON_GOLD := "3a2a08"
# 结算行/叙事行语义档(T4 收口批——battle_log_view 行格式化与 narrator 横幅行的裸 hex 单点化;
# 名随呈现语义、值 = 2026-09 起各行现值快照,色值逐字节不变)
const HEX_LOG_ROUND := "9a8a72" # 轮次前缀/零值行暖灰石
const HEX_LOG_GAIN := "e0b860" # 收益金(GP 袋/商店行/败判定/节点横幅)
const HEX_LOG_LOSS := "c47c7c" # 损失红(GP 流失/负效果)
const HEX_LOG_HEAL := "7cc47c" # 治疗绿(效果正值/治疗行/恢复点行)
const HEX_LOG_DMG_NUM := "d9402a" # 伤害数字朱(放大加色——数字即信息主角)
const HEX_LOG_DMG_KILL := "e07050" # 击杀行正文橙红
const HEX_LOG_DMG_BODY := "e0a050" # 伤害行正文橙
const HEX_LOG_VOIDED := "b08060" # 未生效行暗橙(⚠ 划线)
const HEX_LOG_CAST := "b09a72" # 施放行暖石(✦ 具名技能)
const HEX_LOG_DECAY := "8a9ab0" # 状态衰减蓝灰
const HEX_LOG_WIN := "80d080" # 胜利判定绿(模拟报告通关率同档)
const HEX_ROUTE_NOTE := "d0c090" # 奖励点预告暗金(narrator 行)
const HEX_SEED_NOTE := "c8a860" # 种子/开场行暗金(narrator 行)
# 语义别名(同值异名——语义站点用别名,值仍单点)
const DOT_OFF := PANEL_BORDER
# ── 减动效(battle.md §Transitions 内建承诺:开启时时长减半;结算逐行兑现为信息载体降速不跳过)──
# 唯一入口 = 设置面开关(迭代 8 卡④ 转正;切片期调整板开关已随其退役,调整板本体 14.5 收进调试浮窗)
static var motion_scale := 1.0 # 1.0 常态 / 0.5 减动效
static var victory_fx_style := "crest" # 胜利演出风格(P-C5 两套并存——"crest"碎砖拼纹章[默认冻结件]/"dust"尘金粒子;切换=调试浮窗,会话级)
# 全部呈现层 tween 时长经本函数包装——新增动效一律 UiKit.motion(秒),禁再写裸时长字面量
static func motion(dur: float) -> float:
return dur * motion_scale
# ── 单位/特效纹理路径单点(棋盘小人贴图批 2026-09-25)──
# 素材 PNG 不入 git(用户裁定)——装载必须经 load_tex(exists 守卫),缺位返回 null 走各消费点
# 的字形/无演出回退路径;禁 preload(素材缺位树上直接断言炸脚本,冒烟安全红线)
const TEX_HERO_IDLE := "res://assets/art/character/character_hero_idle.png"
const TEX_HERO_WALK := "res://assets/art/character/character_hero_walk.png"
const TEX_HERO_PORTRAIT := "res://assets/art/character/character_hero_portrait.png" # B 区站桩立绘·normal 态(48px 档,迭代 6 T3 换装=new-normal 源)
const TEX_HERO_ATTACK := "res://assets/art/character/character_hero_attack.png" # B 区站桩立绘·attack 态(迭代 6 T3 三态切换)
const TEX_HERO_HIT := "res://assets/art/character/character_hero_hit.png" # B 区站桩立绘·hit 态(迭代 6 T3 三态切换)
# 敌种立绘(B 区站桩 2026-09-26 槽接线批)——文件键 = 数据表主键 enemy_id(圣经 §8.3 命名规范),
# 键集 = art-bible §4.6.4 八敌种;素材批入库前全部缺位(load_tex→null→字形回退),入库即显示零代码换装
const TEX_ENEMY_GOBLIN := "res://assets/art/character/character_enemy_goblin.png" # 地精
const TEX_ENEMY_BUGBEAR := "res://assets/art/character/character_enemy_bugbear.png" # 熊地精
const TEX_ENEMY_OGRE_CHIEF := "res://assets/art/character/character_enemy_ogre_chief.png" # 食人魔酋长
const TEX_ENEMY_GIANT_SPIDER := "res://assets/art/character/character_enemy_giant_spider.png" # 巨蜘蛛
const TEX_ENEMY_DRIDER := "res://assets/art/character/character_enemy_drider.png" # 蛛化卓尔精灵
const TEX_ENEMY_DUERGAR := "res://assets/art/character/character_enemy_duergar.png" # 灰矮人
const TEX_ENEMY_HOOK_HORROR := "res://assets/art/character/character_enemy_hook_horror.png" # 钩爪恐魔
const TEX_ENEMY_DUERGAR_KING := "res://assets/art/character/character_enemy_duergar_king.png" # 灰矮人石君王
# 框材组纹理槽(样张 v2 冻结批 T2 2026-09-28——♻ 复用件量化改色入包,产物 = tools/quantize_ui_skin.py,
# 全 CC0;缺位 = load_tex null → 程序化回退原路径,冒烟安全)
const TEX_PANEL_STONE := "res://assets/art/ui/panel_stone.png" # 石板亮面板九宫格 48×48
const TEX_BUTTON_STONE := "res://assets/art/ui/button_stone.png" # 按钮九宫格·常态(可按的砖)
const TEX_BUTTON_STONE_PRESSED := "res://assets/art/ui/button_stone_pressed.png" # 按钮九宫格·按压(下沉一档)
const TEX_BACKDROP_STONE := "res://assets/art/ui/backdrop_stone.png" # 石纹底噪 256×256 无缝
const TEX_BORDER_STONE := "res://assets/art/ui/border_stone.png" # 仪式石框(空心角括框)
const TEX_ICON_KEYSTONE := "res://assets/art/ui/icon_keystone.png" # 钥石图标(奖励金)
const TEX_ICON_FORGE := "res://assets/art/ui/icon_forge.png" # 柜台一·属性锻造
const TEX_ICON_WORKSHOP := "res://assets/art/ui/icon_workshop.png" # 柜台二·道具工坊
const TEX_ICON_ATLAS := "res://assets/art/ui/icon_atlas.png" # 柜台三·奖励格图鉴
# 节点图标五键(关卡画面样张 v1 冻结批 M-2 迭代 4 T2——20px 烘焙 1:1 消费,WARM 单色;
# 对象键 = node_type 文件侧延伸 §8.3;缺位 = load_tex null → 字形回退)
const TEX_NODE_BATTLE := "res://assets/art/ui/node_battle.png" # 节点图标·战斗房
const TEX_NODE_REWARD := "res://assets/art/ui/node_reward.png" # 节点图标·奖励房
const TEX_NODE_REST := "res://assets/art/ui/node_rest.png" # 节点图标·恢复房
const TEX_NODE_BOSS := "res://assets/art/ui/node_boss.png" # 节点图标·Boss 房
const TEX_NODE_SHOP := "res://assets/art/ui/node_shop.png" # 节点图标·商店房
# 手绘功能图标十键(UI 落码批 M1 2026-10-04——20×20 像素 1:1/整数倍消费〔§8.2 甲类纪律〕;
# 源=demo 原型 src 盘上件搬运;缺位 = load_tex null → 字形回退不炸)
const TEX_ICON_SWORD := "res://assets/art/ui/icon_sword.png" # 图标·攻击系(锻造行/攻击强化卡)
const TEX_ICON_SHIELD := "res://assets/art/ui/icon_shield.png" # 图标·护甲系
const TEX_ICON_HEART := "res://assets/art/ui/icon_heart.png" # 图标·生命系
const TEX_ICON_SHOVEL := "res://assets/art/ui/icon_shovel.png" # 图标·铲(挖掘系商品/锻造)
const TEX_ICON_MEDKIT := "res://assets/art/ui/icon_medkit.png" # 图标·医疗包
const TEX_ICON_POUCH := "res://assets/art/ui/icon_pouch.png" # 图标·弹药袋/行动点
const TEX_ICON_BLAST := "res://assets/art/ui/icon_blast.png" # 图标·工兵爆破(秘宝)
const TEX_ICON_COINBAG := "res://assets/art/ui/icon_coinbag.png" # 图标·金币袋
const TEX_ICON_KEYSHARD := "res://assets/art/ui/icon_keyshard.png" # 图标·钥石残片
const TEX_ICON_REFRESH := "res://assets/art/ui/icon_refresh.png" # 图标·刷新(货架/候选)
# 素材纹理守卫装载(exists 检查避开缺位树上 ResourceLoader 报错刷屏——冒烟/无素材检出静默回退)
static func load_tex(path: String) -> Texture2D:
if ResourceLoader.exists(path):
return load(path)
return null
# ── 屏底氛围层(UI 设计对齐批 T5 2026-10-06——demo .tilebg opacity .14/.vignette/.beams 对齐;
# 旧「石纹全屏满铺不透」退役——demo 石纹只作弱质感,聚焦靠暗角)──
const BACKDROP_TILE_ALPHA := 0.14 # demo .tilebg{opacity:.14}
const BG_MAP_TOP := Color8(74, 59, 42) # 地图屏渐变顶 #4A3B2A(demo #v-map 180deg 0%;低点 40% = BG_ROOT #3A2E22)
# 石纹弱质感全屏片(0.14;缺素材返回 null = 调用侧回退平色)
static func backdrop_tile() -> TextureRect:
var bd_tex := load_tex(TEX_BACKDROP_STONE)
if bd_tex == null:
return null
var bd := TextureRect.new()
bd.texture = bd_tex
bd.stretch_mode = TextureRect.STRETCH_TILE
bd.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
bd.modulate = Color(1, 1, 1, BACKDROP_TILE_ALPHA)
bd.set_anchors_preset(Control.PRESET_FULL_RECT)
bd.mouse_filter = Control.MOUSE_FILTER_IGNORE
return bd
# 径向暗角(demo .vignette——ellipse 90%×80% at 50% 42%,透明 58% → 黑 0.30 满角)
static func vignette_rect() -> TextureRect:
var g := Gradient.new()
g.offsets = PackedFloat32Array([0.0, 0.58, 1.0])
g.colors = PackedColorArray([Color(0, 0, 0, 0), Color(0, 0, 0, 0), Color(0, 0, 0, 0.30)])
var t := GradientTexture2D.new()
t.gradient = g
t.fill = GradientTexture2D.FILL_RADIAL
t.fill_from = Vector2(0.5, 0.42)
t.fill_to = Vector2(1.4, 0.42)
t.width = 256
t.height = 256
var tr := TextureRect.new()
tr.texture = t
tr.stretch_mode = TextureRect.STRETCH_SCALE
tr.set_anchors_preset(Control.PRESET_FULL_RECT)
tr.mouse_filter = Control.MOUSE_FILTER_IGNORE
return tr
# 斜光带(demo #v-start .beams——repeating-linear-gradient 102deg,暖白 5% 窄带 240-340/560px;
# 程序纹理循环带,确定性生成零随机)
static func beams_rect() -> TextureRect:
var img := Image.create(560, 560, false, Image.FORMAT_RGBA8)
var ca := cos(0.2094) # 12°(102deg 带轴的垂直倾角)
var sa := sin(0.2094)
for y in 560:
for x in 560:
var d := fposmod(x * ca + y * sa, 560.0)
if d >= 240.0 and d < 340.0:
img.set_pixel(x, y, Color(1.0, 236.0 / 255.0, 200.0 / 255.0, 0.05))
var tr := TextureRect.new()
tr.texture = ImageTexture.create_from_image(img)
tr.stretch_mode = TextureRect.STRETCH_TILE
tr.set_anchors_preset(Control.PRESET_FULL_RECT)
tr.mouse_filter = Control.MOUSE_FILTER_IGNORE
return tr
# 竖向渐变底(demo #v-map linear-gradient(180deg, top 0%, low 40%)——stop 后恒 low)
static func gradient_base(top: Color, low: Color, stop := 0.4) -> TextureRect:
var g := Gradient.new()
g.offsets = PackedFloat32Array([0.0, stop])
g.colors = PackedColorArray([top, low])
var t := GradientTexture2D.new()
t.gradient = g
t.fill_from = Vector2(0.5, 0.0)
t.fill_to = Vector2(0.5, 1.0)
t.width = 64
t.height = 256
var tr := TextureRect.new()
tr.texture = t
tr.stretch_mode = TextureRect.STRETCH_SCALE
tr.set_anchors_preset(Control.PRESET_FULL_RECT)
tr.mouse_filter = Control.MOUSE_FILTER_IGNORE
return tr
# CJK 随包字体(2026-09-27 用户拍板 Fusion Pixel OFL——CJK 覆盖+授权干净;命名遵 art-bible §8.3
# 全小写 snake_case 域前缀)。守卫装载同 load_tex 纪律:缺位返回 null(引擎回退字体接手,不炸)。
const FONT_UI := "res://assets/fonts/ui_font_fusion_pixel_zh_hans.ttf"
static func load_font(path: String) -> Font:
if ResourceLoader.exists(path):
var res: Resource = load(path)
return res as Font
return null
# ── 字号档位表(UI 设计对齐批 P3 甲 2026-10-06——WB G1 六档;全站 UiKit.label/字号 override 只许取
# 本表,裸数字字号退役。归档映射:10/11/12→CAPTION、13/14→HINT、15/16→BODY、17/18/20→SUB、
# 21/22/24/26→TITLE、32/40→H1(就近归档);棋盘数字公式 maxi(18, ceil(格边×0.5)) 与站桩血数
# 11px 条带内读数豁免不入表(art-bible §7.4)──
const FS_CAPTION := 12 # 装饰 caption/分区小字/微计数(§7.4 豁免下限)
const FS_HINT := 14 # 提示/tooltip 下限档
const FS_BODY := 16 # 正文/明细下限档
const FS_SUB := 18 # 副标题/小组标题(=棋盘数字下限同值)
const FS_TITLE := 24 # 屏标题/横幅/价格
const FS_H1 := 34 # 主标题/演算暴击
# ── StyleBox 工厂 ──
# margin = (左, 上, 右, 下);border_w = 0 即无边框(根底/遮罩用)
# 圆角率单点(UI 设计对齐批 P5 甲 2026-10-06——容器/按钮全站唯一圆角值;数据框/数字牌/AP 点阵/准线/血条
# =0 直角不变;底座椭圆 17/结算金印 200/状态脉冲环 100 圆形 = §7.1 明示例外保留。取值=代拍板 5,可推翻)
const RADIUS_OBJECT := 5
# 九宫格切片(P4 乙 2026-10-06——panel 石皮 8→16 与 demo .plate9 slice16 对齐;button 维持 8、仪式框维持 12)
const PANEL_SLICE := 16.0
static func panel_flat(bg: Color, border: Color, corner: int = 0, border_w: int = 1, margin: Vector4 = Vector4.ZERO) -> StyleBoxFlat:
var sb := StyleBoxFlat.new()
sb.bg_color = bg
sb.set_corner_radius_all(corner)
sb.set_border_width_all(border_w)
sb.border_color = border
sb.content_margin_left = margin.x
sb.content_margin_top = margin.y
sb.content_margin_right = margin.z
sb.content_margin_bottom = margin.w
return sb
# color 缺省哨兵 = alpha<0(真实用色不取负 alpha)——传色即一次到位(旧「label() 后跟
# add_theme_color_override」两行式收单行);省略时保持旧默认:bold=INK_LABEL / 非 bold=主题缺省
static func label(text: String, size: int, bold: bool, color: Color = Color(-1, -1, -1, -1)) -> Label:
var l := Label.new()
l.text = text
l.add_theme_font_size_override("font_size", size)
if color.a >= 0.0:
l.add_theme_color_override("font_color", color)
elif bold:
l.add_theme_color_override("font_color", INK_LABEL)
return l
# ── 模态壳工厂(T4 收口批 2026-10-02——各 overlay 手搓 dim+Center+VBox/Panel 骨架单点化)──
# dim = 全屏半透明遮罩(全屏锚定纪律同既有各 overlay);center 全屏锚定居中;with_panel = 内容包
# PanelContainer(panel_sb 缺省 = 主题默认板——settlement_review 同款);box = 内容列
#(min_size/separation 参数化,min_size ZERO = 不设)。返回 Dictionary{dim, center, panel, box}
#——探针直读的私有件由调用方按名摘引,结构同名同层级不变。
static func modal_dim(parent: Control, dim_color: Color) -> ColorRect:
var dim := ColorRect.new()
dim.color = dim_color
dim.set_anchors_preset(Control.PRESET_FULL_RECT)
parent.add_child(dim)
return dim
static func modal_shell(parent: Control, dim_color: Color, min_size := Vector2.ZERO, separation := 12,
with_panel := false, panel_sb: StyleBox = null) -> Dictionary:
var dim := modal_dim(parent, dim_color)
var center := CenterContainer.new()
center.set_anchors_preset(Control.PRESET_FULL_RECT)
dim.add_child(center)
var panel: PanelContainer = null
var host: Control = center
if with_panel:
panel = PanelContainer.new()
if panel_sb != null:
panel.add_theme_stylebox_override("panel", panel_sb)
center.add_child(panel)
host = panel
var box := VBoxContainer.new()
if min_size != Vector2.ZERO:
box.custom_minimum_size = min_size
box.add_theme_constant_override("separation", separation)
host.add_child(box)
return {"dim": dim, "center": center, "panel": panel, "box": box}
# ── 全屏页壳(UI 设计对齐批 T6/T7 2026-10-06——demo #v-shop/#v-bonus 全屏页形态落地:
# 06 在册 D2 商店全屏化/D1 竖卡 232×352。暖底+石纹弱质感+暗角+56px 通栏顶条+内容列;
# 商店=暖亮底、三选一=根底,两屏同构差异仅底色与顶条工具件)──
const PAGE_TOP_H := 56.0
static func page_shell(host: Control, bright := true) -> Dictionary:
var page := Control.new()
page.set_anchors_preset(Control.PRESET_FULL_RECT)
host.add_child(page)
var base := ColorRect.new()
base.color = BG_BRIGHT if bright else BG_ROOT
base.set_anchors_preset(Control.PRESET_FULL_RECT)
base.mouse_filter = Control.MOUSE_FILTER_STOP # 页底挡点击——模态语义由组合根输入锁兜底
page.add_child(base)
var tile := backdrop_tile()
if tile != null:
page.add_child(tile)
page.add_child(vignette_rect())
var top := PanelContainer.new()
top.set_anchors_preset(Control.PRESET_TOP_WIDE)
top.custom_minimum_size = Vector2(0, PAGE_TOP_H)
top.add_theme_stylebox_override("panel",
panel_flat(Color8(38, 29, 20), Color8(85, 72, 58), 0, 1, Vector4(26, 6, 26, 6)))
page.add_child(top)
var top_row := HBoxContainer.new()
top_row.add_theme_constant_override("separation", 12)
top.add_child(top_row)
var box := VBoxContainer.new()
box.set_anchors_preset(Control.PRESET_FULL_RECT)
box.offset_top = PAGE_TOP_H + 20
box.offset_left = 26
box.offset_right = -26
box.offset_bottom = -22
box.add_theme_constant_override("separation", 10)
page.add_child(box)
return {"page": page, "base": base, "top_row": top_row, "box": box}
# 属性百分比合成(main._debug_hero_override 与调试浮窗生效值预览共用单点——两处同式实现收口)
static func scaled(base: int, pct: int) -> int:
return maxi(1, int(round(float(base) * (100.0 + float(pct)) / 100.0)))
# 宽度纪律(ui-struct-probe 批 T3——全站按钮最小宽度基准 216+SIZE_FILL:VBox 列内拉满列宽等宽,
# HBox 行内不抢余宽(EXPAND_FILL 会把行撑爆——营地底条 1616px 实证);角落小件/紧凑位传 0 豁免)
# 钮高纪律(2026-10-06 用户令「对齐好、不要那么细」——标题屏主钮 26px 对常规钮 38px 不齐的治根):
# 全站常规钮统一最小高 40(主操作钮/危险钮经 button() 同高对齐,压扁形态从此不存在);
# 豁免域与宽度纪律同款——min_w=0 紧凑位(HUD 顶条小钮/角标)不抬行高
const BUTTON_MIN_H := 40.0
static func button(text: String, min_w := 216.0) -> Button:
var b := Button.new()
b.text = text
if min_w > 0.0:
b.custom_minimum_size = Vector2(min_w, BUTTON_MIN_H)
b.size_flags_horizontal = Control.SIZE_FILL
_apply_button_skin(b)
return b
# ── 框材纹理工厂(样张 v2 冻结批 T2——§7.1 容器穿戏服、数据素面;缺位回退 flat/主题默认) ──
# 九宫格 StyleBoxTexture(texture_margin 8px——quantize 脚本同值);tint 供消费点微调色调
static func _tex_sb(tex: Texture2D, margin: float, tint := Color.WHITE) -> StyleBoxTexture:
var sb := StyleBoxTexture.new()
sb.texture = tex
sb.texture_margin_left = margin
sb.texture_margin_top = margin
sb.texture_margin_right = margin
sb.texture_margin_bottom = margin
sb.modulate_color = tint
return sb
# 石板亮面板(B 区舞台/C 区主面板/图例——容器穿戏服;数据框仍 panel_flat 素面 §7.1)
# Q1 丙(2026-10-06):content margin 低于切片的分量自动抬到切片——切片加大到 16 后石框纹理不吃内容
# (T0 实查 9 消费面 7 处 margin<16;工厂单点 clamp,调用点零改)
static func panel_skinned(margin: Vector4 = Vector4.ZERO, tint := Color.WHITE) -> StyleBox:
var m := Vector4(maxf(margin.x, PANEL_SLICE), maxf(margin.y, PANEL_SLICE),
maxf(margin.z, PANEL_SLICE), maxf(margin.w, PANEL_SLICE))
var tex := load_tex(TEX_PANEL_STONE)
if tex == null:
return panel_flat(PANEL_DATA, PANEL_BORDER, RADIUS_OBJECT, 1, m)
var sb := _tex_sb(tex, PANEL_SLICE, tint)
sb.content_margin_left = m.x
sb.content_margin_top = m.y
sb.content_margin_right = m.z
sb.content_margin_bottom = m.w
return sb
# 仪式石框(空心角括框——营地容器框;缺位回退暗面板)
static func frame_skinned(margin: Vector4 = Vector4.ZERO) -> StyleBox:
var tex := load_tex(TEX_BORDER_STONE)
if tex == null:
return panel_flat(BG_OVERLAY, PANEL_BORDER,RADIUS_OBJECT, 1, margin)
var sb := _tex_sb(tex, 12.0)
sb.content_margin_left = margin.x
sb.content_margin_top = margin.y
sb.content_margin_right = margin.z
sb.content_margin_bottom = margin.w
return sb
# 图标槽(营地钥石/柜台件——缺位返回 null,消费点不挂 TextureRect 即回退纯文本)
static func icon_rect(tex_path: String, px: float) -> TextureRect:
var t := load_tex(tex_path)
if t == null:
return null
var tr := TextureRect.new()
tr.texture = t
tr.custom_minimum_size = Vector2(px, px)
tr.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
tr.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
tr.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
return tr
# 钥石徽(badge 工厂件——Q10 甲收编 2026-10-06:原营地顶栏钥石徽本地拼装〔camp_overlay_view〕
# 参数化收编,消费方 = 营地覆盖层 + 暂停层钥石余额行(迭代 17 卡③)。形制 = 烛光黄底+木色描边
# +钥石图标(icon_rect 守卫装载,缺位=纯文本回退)+深墨字;零新素材零新色值——值全取现行 token)
class KeystoneBadge:
extends PanelContainer
var value_label: Label
func _init(text: String, icon_path: String, icon_px: float) -> void:
add_theme_stylebox_override("panel",
UiKit.panel_flat(UiKit.CANDLE, UiKit.CAMP_HEADER_BAND, UiKit.RADIUS_OBJECT, 1, Vector4(14, 8, 14, 8)))
var row := HBoxContainer.new()
row.add_theme_constant_override("separation", 6)
add_child(row)
if icon_path != "":
var ic := UiKit.icon_rect(icon_path, icon_px)
if ic != null:
row.add_child(ic)
value_label = UiKit.label(text, UiKit.FS_SUB, true, UiKit.INK_ON_GOLD)
row.add_child(value_label)
func set_text(t: String) -> void:
value_label.text = t
static func keystone_badge(text: String, icon_path := TEX_ICON_KEYSTONE, icon_px := 32.0) -> KeystoneBadge:
return KeystoneBadge.new(text, icon_path, icon_px)
# 按钮皮(常态/按压两态+墨字——缺位 = 引擎默认主题,素材批前逐位一致)
static func _apply_button_skin(b: Button) -> void:
var tex_n := load_tex(TEX_BUTTON_STONE)
if tex_n == null:
return
var tex_p := load_tex(TEX_BUTTON_STONE_PRESSED)
b.add_theme_stylebox_override("normal", _tex_sb(tex_n, 8.0))
b.add_theme_stylebox_override("hover", _tex_sb(tex_n, 8.0))
if tex_p != null:
b.add_theme_stylebox_override("pressed", _tex_sb(tex_p, 8.0))
b.add_theme_stylebox_override("disabled", _tex_sb(tex_p, 8.0))
b.add_theme_color_override("font_color", INK)
b.add_theme_color_override("font_hover_color", INK)
b.add_theme_color_override("font_pressed_color", INK)
b.add_theme_color_override("font_focus_color", INK)
b.add_theme_color_override("font_disabled_color", Color(INK, 0.45))
# ── UI 落码批 M10 公共件(2026-10-04——bcard 竖卡工厂/拨杆开关/样式滑杆/动词按钮/tooltip 主题;
# 消费方 = M2 标题/M3 营地/M4 设置/M6 商店/M7 三选一。单点纪律同 §7.1:新件只加不改旧件)──
# 动词按钮(图标+动词+悬停说明三件套——M2 标题屏按钮组/通用;icon 缺位=纯文字不炸)
static func verb_button(text: String, icon_path: String, tip := "") -> Button:
var b := button(text)
var tex := load_tex(icon_path)
if tex != null:
b.icon = tex
b.add_theme_constant_override("icon_max_width", 20)
b.expand_icon = false
if tip != "":
b.tooltip_text = tip
return b
# 主操作钮(烛光黄平涂+木描边——非奖励金;配色总纲「主按钮禁亮金」;缺 panel_stone 依赖=纯色可靠)
static func primary_button(text: String, tip := "") -> Button:
var b := button(text)
var sb := panel_flat(CANDLE, Color8(110, 69, 52),RADIUS_OBJECT, 2, Vector4(10, 2, 10, 2))
b.add_theme_stylebox_override("normal", sb)
b.add_theme_stylebox_override("hover", panel_flat(CANDLE.lightened(0.06), Color8(110, 69, 52),RADIUS_OBJECT, 2, Vector4(10, 2, 10, 2)))
b.add_theme_stylebox_override("pressed", panel_flat(Color8(214, 172, 88), Color8(110, 69, 52),RADIUS_OBJECT, 2, Vector4(10, 2, 10, 2)))
b.add_theme_color_override("font_color", Color8(58, 42, 8))
b.add_theme_color_override("font_hover_color", Color8(58, 42, 8))
b.add_theme_color_override("font_pressed_color", Color8(58, 42, 8))
b.add_theme_color_override("font_focus_color", Color8(58, 42, 8))
if tip != "":
b.tooltip_text = tip
return b
# 危险钮(深红平涂——放弃/删除类;破坏性确认位置纪律=不得与常规确认同位)
static func danger_button(text: String, tip := "", min_w := 216.0) -> Button:
var b := button(text, min_w)
var sb := panel_flat(Color8(180, 78, 54), Color8(110, 42, 28),RADIUS_OBJECT, 2, Vector4(10, 2, 10, 2))
b.add_theme_stylebox_override("normal", sb)
b.add_theme_stylebox_override("hover", panel_flat(Color8(196, 90, 62), Color8(110, 42, 28),RADIUS_OBJECT, 2, Vector4(10, 2, 10, 2)))
b.add_theme_color_override("font_color", Color8(255, 244, 224))
b.add_theme_color_override("font_hover_color", Color8(255, 244, 224))
b.add_theme_color_override("font_pressed_color", Color8(255, 244, 224))
if tip != "":
b.tooltip_text = tip
return b
# 卡顶类型色带·Bayer 有序抖动(UI 设计对齐批 P8 甲 2026-10-06——bcard band 平涂 ColorRect 退役;
# demo CSS 平滑渐变/blur 违反像素纪律,改 4×4 Bayer 有序抖动台阶:α 由深至 0,2px 元胞逐行降覆盖;
# 纯静态绘制零动效(减动效无涉);Bayer 阶数=实现细节白名单项,取 4×4)
class DitherBand:
extends Control
var band: Color
func _init(c: Color) -> void:
band = c
custom_minimum_size = Vector2(0, 8) # 8px = 4×4 Bayer 全周期(原平涂条 6px)
func _draw() -> void:
var bayer := [[0, 8, 2, 10], [12, 4, 14, 6], [3, 11, 1, 9], [15, 7, 13, 5]]
var cell := 2.0
var rows := int(ceil(custom_minimum_size.y / cell))
var cols := int(ceil(size.x / cell))
for ry in rows:
var cover := 1.0 - float(ry) / float(rows) # 顶行全覆盖 → 底行趋零
for rx in cols:
if float(bayer[ry % 4][rx % 4]) / 16.0 < cover:
draw_rect(Rect2(float(rx) * cell, float(ry) * cell, cell, cell), band, true)
# bcard 竖卡工厂(商店/三选一同构卡——M6/M7 共用;M7.1 差异仅=价签 vs ✓选择章)
# set_selected 经 mark/样式换装
class BCard:
extends PanelContainer
var mark: Label
var icon_tr: TextureRect
var name_l: Label
var desc_l: Label
var price_l: Label
var foot_l: Label
var body: VBoxContainer # 内容列(消费方经 add_footer 插件到底注前——M6 购买钮挂位)
var _sb_normal: StyleBox
var _sb_sel: StyleBox
func _init(title: String, desc: String, band: Color, icon_path: String, price := "", footnote := "") -> void:
var tex := UiKit.load_tex(UiKit.TEX_PANEL_STONE)
_sb_normal = UiKit.panel_skinned(Vector4(12, 10, 12, 10)) if tex != null else UiKit.panel_flat(PANEL_DATA, PANEL_BORDER,RADIUS_OBJECT, 1, Vector4(12, 10, 12, 10)) # 回退=数据板单点(P7 乙后同值,字面量退役)
_sb_sel = UiKit.panel_flat(PANEL_DATA, Color8(138, 90, 52), RADIUS_OBJECT, 2, Vector4(12, 10, 12, 10)) # 选中底=数据板单点(P7 乙后同值)
add_theme_stylebox_override("panel", _sb_normal)
custom_minimum_size = Vector2(232, 352) # D1(UI 设计对齐批 T6 2026-10-06——demo .bcard 232×352;原 216×300)
size_flags_vertical = Control.SIZE_SHRINK_CENTER # 行容器不纵向拉伸(卡恒 352 高居中——T7 实测 HBox fill 拉到 ~536 修)
var box := VBoxContainer.new()
box.add_theme_constant_override("separation", 8)
add_child(box)
body = box
var band_r := DitherBand.new(band) # P8 甲:卡顶类型色带=Bayer 有序抖动带(随各 TG_* 逐卡着色)
box.add_child(band_r)
mark = UiKit.label("✓",FS_BODY, true, Color8(58, 42, 8))
mark.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
mark.visible = false
box.add_child(mark)
# 图标槽(demo .ic 76×76 槽底+图标 60=20px 源 3 整数倍——P6 甲④ 2× 档升 3× 随 D1 对齐)
var slot := PanelContainer.new()
slot.add_theme_stylebox_override("panel", UiKit.panel_flat(SLOT_BACKDROP, PANEL_BORDER, 0, 1))
slot.custom_minimum_size = Vector2(76, 76)
slot.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
box.add_child(slot)
icon_tr = TextureRect.new()
var itex := UiKit.load_tex(icon_path)
if itex != null:
icon_tr.texture = itex
icon_tr.custom_minimum_size = Vector2(60, 60)
icon_tr.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
icon_tr.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
icon_tr.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
slot.add_child(icon_tr)
name_l = UiKit.label(title,FS_SUB, true, INK_TITLE)
name_l.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
box.add_child(name_l)
desc_l = UiKit.label(desc,FS_HINT, false, Color8(74, 58, 40))
desc_l.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
desc_l.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
box.add_child(desc_l)
var spacer := Control.new()
spacer.size_flags_vertical = Control.SIZE_EXPAND_FILL
box.add_child(spacer)
price_l = UiKit.label(price,FS_TITLE, true, GOLD)
price_l.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
price_l.visible = price != ""
box.add_child(price_l)
foot_l = UiKit.label(footnote,FS_CAPTION, false, Color8(110, 69, 52))
foot_l.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
foot_l.visible = footnote != ""
box.add_child(foot_l)
func set_selected(v: bool) -> void:
add_theme_stylebox_override("panel", _sb_sel if v else _sb_normal)
mark.visible = v
func add_footer(c: Control) -> void:
body.add_child(c)
body.move_child(c, body.get_child_index(foot_l))
static func bcard(title: String, desc: String, band: Color, icon_path: String, price := "", footnote := "") -> BCard:
return BCard.new(title, desc, band, icon_path, price, footnote)
# 拨杆开关(设置屏——46×22 滑柄形;状态字随行由消费方摆;会话级/持久由消费方接 SettingsStore)
class SwitchToggle:
extends Button
var _knob: ColorRect
func _init(initial: bool) -> void:
toggle_mode = true
button_pressed = initial
custom_minimum_size = Vector2(46, 22)
var track := UiKit.panel_flat(Color8(184, 165, 130), Color8(120, 110, 88),RADIUS_OBJECT, 2)
add_theme_stylebox_override("normal", track)
add_theme_stylebox_override("hover", track)
add_theme_stylebox_override("pressed", track)
_knob = ColorRect.new()
_knob.color = Color8(58, 42, 8)
_knob.mouse_filter = Control.MOUSE_FILTER_IGNORE
add_child(_knob)
toggled.connect(func(_p: bool) -> void: _place())
_place()
func _place() -> void:
_knob.position = Vector2(25, 3) if button_pressed else Vector2(3, 3)
_knob.size = Vector2(18, 16)
static func switch_toggle(initial: bool) -> SwitchToggle:
return SwitchToggle.new(initial)
# 样式滑杆(设置屏音量——轨道暖石+默认柄;范围/读数由消费方接)
static func styled_slider(value: float, minv := 0.0, maxv := 100.0) -> HSlider:
var s := HSlider.new()
s.min_value = minv
s.max_value = maxv
s.value = value
s.custom_minimum_size = Vector2(200, 24)
# 槽轨/填充实高(2026-10-06 判官实checkpoint:HSlider 槽=按 stylebox min 高绘制——零 margin
# StyleBoxFlat min 高 0-2px=轨道整条不可见;6px 上下 content margin=12px 实高)
s.add_theme_stylebox_override("slider",
panel_flat(Color8(184, 165, 130), PANEL_BORDER, 0, 1, Vector4(0, 6, 0, 6)))
s.add_theme_stylebox_override("grabber_area",
panel_flat(Color8(122, 82, 50), Color8(0, 0, 0, 0), 0, 0, Vector4(0, 6, 0, 6)))
return s
# tooltip 全站主题(深底多行素牌——§7.2 文字可读硬指标;消费方=任一祖先 Control.theme 挂一次全树生效)
static var _tip_theme: Theme = null
static func tooltip_theme() -> Theme:
if _tip_theme != null:
return _tip_theme
var th := Theme.new()
th.set_stylebox("panel", "TooltipPanel",
panel_flat(Color(0.09, 0.07, 0.05, 0.96), Color8(90, 80, 68), 0, 1, Vector4(8, 6, 8, 6)))
th.set_color("font_color", "TooltipLabel", WARM)
th.set_font_size("font_size", "TooltipLabel", 14)
_tip_theme = th
return _tip_theme