feat(迭代25): UI 全局主题批 T0-T2——全局主题资源挂载+两处局部做法退役+冒烟 V1-V6 断言
- T0: game/assets/theme/main_theme.tres 挂 project.godot gui/theme/custom—— 15 类型全覆盖(V/HScrollBar·ScrollContainer·H/VSlider·TooltipPanel/Label· LineEdit·SpinBox·H/VSeparator)+预盖 CheckBox/CheckButton/OptionButton/ PopupMenu 弹层;default_font 显式指现役同字体(V6 中文字形防御); 位图生成脚本 tools/gen_theme_icons.py(9 张纯色/几何小位图→assets/art/ui/gen/, 按 .gitignore 政策本地生成不入库);色值溯源表 game/assets/theme/README.md - T1: ui_kit.gd 删 tooltip_theme()/_tip_theme 与 styled_slider()、ui_assembler.gd 删挂载行(旧实现同批删除);settings_overlay 几何内联、debug_overlay 双轨消隐 - T2: 冒烟 _smoke_global_theme() 38 断言(V1 类型覆盖+Button/focus 双红线+ V2 冻结值 5.49:1+V3 预盖证词+V4 退役零残留+V6 字体防御) - 引擎事实(4.7.2 源码+默认主题链双查):CheckButton 与 CheckBox 同套 checked/unchecked 图标名;Slider 无 grabber_pressed 槽;ScrollBar 柄=StyleBox
This commit is contained in:
1 parent
5f8acd0f0d
commit
af2d834e78
9 files changed
+527
-33
No files matched your search
@@ -0,0 +1,59 @@
|
||||
# game/assets/theme/ —— UI 全局主题资源(迭代 25 · UI 全局主题批)
|
||||
|
||||
- 主文件:`main_theme.tres`(Godot 4 Theme 资源,挂 `project.godot` 的 `gui/theme/custom`)
|
||||
- 生成脚本:`tools/gen_theme_icons.py`(可重跑,幂等)→ 小位图落 `../art/ui/gen/`
|
||||
- 依据:需求卡片 `design/research/需求卡片-UI全局主题批-2026-10-13.md` v1.2(2026-10-13)
|
||||
|
||||
本目录不含内联注释(.tres 注释语法不赌),全部溯源信息以本表为准。**色值一律取自
|
||||
`game/presentation/views/ui_kit.gd` 既有常量,零新增色值**(迭代 25 契约:不新增美术素材)。
|
||||
|
||||
## 色值溯源表(字面量 ↔ ui_kit 常量)
|
||||
|
||||
| ui_kit 常量 | 值 | 本主题用途 |
|
||||
|---|---|---|
|
||||
| BG_OVERLAY | #362B20 α0.35 | 滚动条槽(V/H) |
|
||||
| PANEL_BORDER | #786E58 | 滚动条柄·常、滑轨槽描边、LineEdit/PopupMenu 边、分隔线 α0.6、勾选框边、滑轨柄描边、SpinBox 箭头底系 |
|
||||
| STONE_FAINT | #8C7C64 | 滚动条柄·悬、SpinBox 箭头底·悬(α0.6,白名单#5) |
|
||||
| STONE_DEEP | #554D41 | 滚动条柄·按、滑轨柄·废、勾选框·废、SpinBox 箭头底·按、LineEdit 占位字色 |
|
||||
| BG_ROOT | #3A2E22 | 滑轨已填充段 grabber_area(+highlight) |
|
||||
| 暖石(无常量,ui_kit.gd:743 字面量) | #B8A582 | 滑轨槽 slider(承 styled_slider 现值) |
|
||||
| CANDLE | #F6C667 | 滑轨柄·常 |
|
||||
| CANDLE 提亮档(lightened≈12%,≈#F7E08C) | — | 滑轨柄·悬(白名单#5;Godot lightened 同式) |
|
||||
| GOLD_DEEP | #B07A14 | 滑轨柄·按——**Godot 4.7 Slider 无 grabber_pressed 图标槽**(实证:引擎源码+默认主题链双查),值留档未落位 |
|
||||
| WARM | #F0E2C8 | TooltipLabel 字色(承 tooltip_theme() 现值) |
|
||||
| tooltip 面板底/边(ui_kit.gd:757-759 现值) | Color(0.09,0.07,0.05,0.96) / #5A5044 | TooltipPanel |
|
||||
| PANEL_DATA | #E9D9B8 | LineEdit 底、勾选框空框底、PopupMenu 面板底 |
|
||||
| INK | #33261B | LineEdit/PopupMenu/OptionButton 字色、SpinBox 箭头、勾选框对勾 |
|
||||
| INK α0.45 | — | OptionButton 废字色(同 _apply_button_skin 惯例) |
|
||||
| GOLD_LIT | #E6BE5A | PopupMenu 条目 hover 底 |
|
||||
| INK_ON_GOLD | #3C2C14 | PopupMenu 条目 hover 字色 |
|
||||
| RADIUS_OBJECT | 5 | LineEdit 圆角(同数据框惯例 ui_kit.gd:476) |
|
||||
| 默认字体 | res://assets/fonts/ui_font_fusion_pixel_zh_hans.ttf | 主题 `default_font`(与 project.godot `theme/custom_font` 同文件——v1.2 防御条款,防挂主题后中文字形缺失) |
|
||||
|
||||
## 词条落位总览
|
||||
|
||||
| 类型 | 词条 | 载体 |
|
||||
|---|---|---|
|
||||
| VScrollBar / HScrollBar | scroll / grabber / grabber_highlight / grabber_pressed | StyleBoxFlat 纯色(**Godot 4 滚动条柄是 StyleBox 非 Icon**,无需位图;content margin 4 = 8px 粗细,柄最小 8px) |
|
||||
| ScrollContainer | panel | StyleBoxFlat 全透明(默认即 Empty 不可见,显式接管) |
|
||||
| HSlider / VSlider | slider / grabber_area / grabber_area_highlight / grabber / grabber_highlight / grabber_disabled | StyleBoxFlat + 生成位图(12×12 方柄;VSlider 边距为 H 的旋转换位) |
|
||||
| TooltipPanel / TooltipLabel | panel / font_color / font_size | 承 tooltip_theme() 现值原样迁入 |
|
||||
| LineEdit | normal / font_color / font_placeholder_color | StyleBoxFlat + 色词条(**占位色词条名 = `font_placeholder_color`**,4.7.2 实证;`font_color_placeholder` 不存在) |
|
||||
| SpinBox | updown / up·down_background_hovered·pressed | **Godot 4 SpinBox 走 `updown` 合并图标槽**(定义了它就不再画 up/down 分件,引擎源码实证);hover/pressed 箭头底默认主题可见,按白名单#5 覆盖 |
|
||||
| HSeparator / VSeparator | separator | StyleBoxLine(PANEL_BORDER α0.6 单线,1px) |
|
||||
| CheckBox / CheckButton | unchecked / checked / unchecked_disabled / checked_disabled | **两类共用同一套图标名**(checked/unchecked,4.7.2 源码实证——卡片 v1.2 注③「CheckButton 用 on/off」不成立,已按引擎事实落位并呈报追认) |
|
||||
| OptionButton | normal / hover / pressed / disabled / font_* / arrow | button_stone 九宫格(8px 边距,同 _apply_button_skin 观感)+ INK 字 + 生成箭头 |
|
||||
| PopupMenu | panel / hover / font_color / font_hover_color / font_size | StyleBoxFlat(预盖件,含弹层) |
|
||||
|
||||
## 红线遵守记录
|
||||
|
||||
- Button 类型词条**零接触**(五态矩阵归 06 第一层第二条);OptionButton 为独立主题类型(预盖件)。
|
||||
- focus 族词条**零定义**(scroll_focus / LineEdit focus / font_focus_color 等一律不写)。
|
||||
- 全局纹理过滤未动(`rendering/textures/canvas_textures/default_texture_filter` 归 06 第一层第三条);
|
||||
Godot 4.7.2 导入参数已无逐贴图 filter 标志(现存 art/ui/*.import 实证),小位图均为原生尺寸消费,无滤波伪影。
|
||||
- default_font_size **未设**(全站字号走 UiKit.FS_* 逐实例 override)。
|
||||
|
||||
## 专项观察点(v1.2 增补,归用户目检)
|
||||
|
||||
1. 滑轨柄 CANDLE 色是否比「关闭」按钮抢眼(违反焦点预算嫌疑)——若是走 L2 微调(候选 STONE 灰石系)。
|
||||
2. 滚动条槽 BG_OVERLAY α0.35 叠 BG_ROOT 上是否可见——若否走 L2 微调(候选提不透明度或 SLOT_BACKDROP #3E362C)。
|
||||
@@ -0,0 +1,222 @@
|
||||
[gd_resource type="Theme" format=3]
|
||||
|
||||
[ext_resource type="FontFile" path="res://assets/fonts/ui_font_fusion_pixel_zh_hans.ttf" id="1_font"]
|
||||
[ext_resource type="Texture2D" path="res://assets/art/ui/button_stone.png" id="2_btn"]
|
||||
[ext_resource type="Texture2D" path="res://assets/art/ui/button_stone_pressed.png" id="3_btnp"]
|
||||
[ext_resource type="Texture2D" path="res://assets/art/ui/gen/slider_grabber.png" id="4_sg"]
|
||||
[ext_resource type="Texture2D" path="res://assets/art/ui/gen/slider_grabber_highlight.png" id="5_sgh"]
|
||||
[ext_resource type="Texture2D" path="res://assets/art/ui/gen/slider_grabber_disabled.png" id="6_sgd"]
|
||||
[ext_resource type="Texture2D" path="res://assets/art/ui/gen/spinbox_updown.png" id="7_spu"]
|
||||
[ext_resource type="Texture2D" path="res://assets/art/ui/gen/option_arrow.png" id="8_oa"]
|
||||
[ext_resource type="Texture2D" path="res://assets/art/ui/gen/checkbox_unchecked.png" id="9_cbu"]
|
||||
[ext_resource type="Texture2D" path="res://assets/art/ui/gen/checkbox_checked.png" id="10_cbc"]
|
||||
[ext_resource type="Texture2D" path="res://assets/art/ui/gen/checkbox_unchecked_disabled.png" id="11_cbud"]
|
||||
[ext_resource type="Texture2D" path="res://assets/art/ui/gen/checkbox_checked_disabled.png" id="12_cbcd"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="SBF_vscroll_track"]
|
||||
content_margin_left = 4.0
|
||||
content_margin_right = 4.0
|
||||
bg_color = Color(0.211765, 0.168627, 0.12549, 0.35)
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="SBF_vscroll_grab"]
|
||||
content_margin_top = 4.0
|
||||
content_margin_bottom = 4.0
|
||||
bg_color = Color(0.470588, 0.431373, 0.345098, 1)
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="SBF_vscroll_grab_h"]
|
||||
content_margin_top = 4.0
|
||||
content_margin_bottom = 4.0
|
||||
bg_color = Color(0.54902, 0.486275, 0.392157, 1)
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="SBF_vscroll_grab_p"]
|
||||
content_margin_top = 4.0
|
||||
content_margin_bottom = 4.0
|
||||
bg_color = Color(0.333333, 0.301961, 0.254902, 1)
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="SBF_hscroll_track"]
|
||||
content_margin_top = 4.0
|
||||
content_margin_bottom = 4.0
|
||||
bg_color = Color(0.211765, 0.168627, 0.12549, 0.35)
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="SBF_hscroll_grab"]
|
||||
content_margin_left = 4.0
|
||||
content_margin_right = 4.0
|
||||
bg_color = Color(0.470588, 0.431373, 0.345098, 1)
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="SBF_hscroll_grab_h"]
|
||||
content_margin_left = 4.0
|
||||
content_margin_right = 4.0
|
||||
bg_color = Color(0.54902, 0.486275, 0.392157, 1)
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="SBF_hscroll_grab_p"]
|
||||
content_margin_left = 4.0
|
||||
content_margin_right = 4.0
|
||||
bg_color = Color(0.333333, 0.301961, 0.254902, 1)
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="SBF_sc_panel"]
|
||||
bg_color = Color(0, 0, 0, 0)
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="SBF_hslider_track"]
|
||||
content_margin_top = 6.0
|
||||
content_margin_bottom = 6.0
|
||||
bg_color = Color(0.721569, 0.647059, 0.509804, 1)
|
||||
border_color = Color(0.470588, 0.431373, 0.345098, 1)
|
||||
border_width_left = 1
|
||||
border_width_top = 1
|
||||
border_width_right = 1
|
||||
border_width_bottom = 1
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="SBF_hslider_fill"]
|
||||
content_margin_top = 6.0
|
||||
content_margin_bottom = 6.0
|
||||
bg_color = Color(0.227451, 0.180392, 0.133333, 1)
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="SBF_vslider_track"]
|
||||
content_margin_left = 6.0
|
||||
content_margin_right = 6.0
|
||||
bg_color = Color(0.721569, 0.647059, 0.509804, 1)
|
||||
border_color = Color(0.470588, 0.431373, 0.345098, 1)
|
||||
border_width_left = 1
|
||||
border_width_top = 1
|
||||
border_width_right = 1
|
||||
border_width_bottom = 1
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="SBF_vslider_fill"]
|
||||
content_margin_left = 6.0
|
||||
content_margin_right = 6.0
|
||||
bg_color = Color(0.227451, 0.180392, 0.133333, 1)
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="SBF_tooltip"]
|
||||
content_margin_left = 8.0
|
||||
content_margin_top = 6.0
|
||||
content_margin_right = 8.0
|
||||
content_margin_bottom = 6.0
|
||||
bg_color = Color(0.09, 0.07, 0.05, 0.96)
|
||||
border_color = Color(0.352941, 0.313725, 0.266667, 1)
|
||||
border_width_left = 1
|
||||
border_width_top = 1
|
||||
border_width_right = 1
|
||||
border_width_bottom = 1
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="SBF_lineedit"]
|
||||
content_margin_left = 6.0
|
||||
content_margin_top = 3.0
|
||||
content_margin_right = 6.0
|
||||
content_margin_bottom = 3.0
|
||||
bg_color = Color(0.913725, 0.85098, 0.721569, 1)
|
||||
border_color = Color(0.470588, 0.431373, 0.345098, 1)
|
||||
border_width_left = 1
|
||||
border_width_top = 1
|
||||
border_width_right = 1
|
||||
border_width_bottom = 1
|
||||
corner_radius_top_left = 5
|
||||
corner_radius_top_right = 5
|
||||
corner_radius_bottom_right = 5
|
||||
corner_radius_bottom_left = 5
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="SBF_spin_btn_h"]
|
||||
bg_color = Color(0.54902, 0.486275, 0.392157, 0.6)
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="SBF_spin_btn_p"]
|
||||
bg_color = Color(0.333333, 0.301961, 0.254902, 1)
|
||||
|
||||
[sub_resource type="StyleBoxLine" id="SBLine_hsep"]
|
||||
color = Color(0.470588, 0.431373, 0.345098, 0.6)
|
||||
thickness = 1
|
||||
|
||||
[sub_resource type="StyleBoxLine" id="SBLine_vsep"]
|
||||
color = Color(0.470588, 0.431373, 0.345098, 0.6)
|
||||
thickness = 1
|
||||
vertical = true
|
||||
|
||||
[sub_resource type="StyleBoxTexture" id="SBTex_btn"]
|
||||
texture = ExtResource("2_btn")
|
||||
texture_margin_left = 8.0
|
||||
texture_margin_top = 8.0
|
||||
texture_margin_right = 8.0
|
||||
texture_margin_bottom = 8.0
|
||||
|
||||
[sub_resource type="StyleBoxTexture" id="SBTex_btnp"]
|
||||
texture = ExtResource("3_btnp")
|
||||
texture_margin_left = 8.0
|
||||
texture_margin_top = 8.0
|
||||
texture_margin_right = 8.0
|
||||
texture_margin_bottom = 8.0
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="SBF_popup_panel"]
|
||||
content_margin_left = 8.0
|
||||
content_margin_top = 6.0
|
||||
content_margin_right = 8.0
|
||||
content_margin_bottom = 6.0
|
||||
bg_color = Color(0.913725, 0.85098, 0.721569, 1)
|
||||
border_color = Color(0.470588, 0.431373, 0.345098, 1)
|
||||
border_width_left = 1
|
||||
border_width_top = 1
|
||||
border_width_right = 1
|
||||
border_width_bottom = 1
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="SBF_popup_hover"]
|
||||
content_margin_left = 6.0
|
||||
content_margin_top = 2.0
|
||||
content_margin_right = 6.0
|
||||
content_margin_bottom = 2.0
|
||||
bg_color = Color(0.901961, 0.745098, 0.352941, 1)
|
||||
|
||||
[resource]
|
||||
default_font = ExtResource("1_font")
|
||||
VScrollBar/styles/scroll = SubResource("SBF_vscroll_track")
|
||||
VScrollBar/styles/grabber = SubResource("SBF_vscroll_grab")
|
||||
VScrollBar/styles/grabber_highlight = SubResource("SBF_vscroll_grab_h")
|
||||
VScrollBar/styles/grabber_pressed = SubResource("SBF_vscroll_grab_p")
|
||||
HScrollBar/styles/scroll = SubResource("SBF_hscroll_track")
|
||||
HScrollBar/styles/grabber = SubResource("SBF_hscroll_grab")
|
||||
HScrollBar/styles/grabber_highlight = SubResource("SBF_hscroll_grab_h")
|
||||
HScrollBar/styles/grabber_pressed = SubResource("SBF_hscroll_grab_p")
|
||||
ScrollContainer/styles/panel = SubResource("SBF_sc_panel")
|
||||
HSlider/styles/slider = SubResource("SBF_hslider_track")
|
||||
HSlider/styles/grabber_area = SubResource("SBF_hslider_fill")
|
||||
HSlider/styles/grabber_area_highlight = SubResource("SBF_hslider_fill")
|
||||
HSlider/icons/grabber = ExtResource("4_sg")
|
||||
HSlider/icons/grabber_highlight = ExtResource("5_sgh")
|
||||
HSlider/icons/grabber_disabled = ExtResource("6_sgd")
|
||||
VSlider/styles/slider = SubResource("SBF_vslider_track")
|
||||
VSlider/styles/grabber_area = SubResource("SBF_vslider_fill")
|
||||
VSlider/styles/grabber_area_highlight = SubResource("SBF_vslider_fill")
|
||||
VSlider/icons/grabber = ExtResource("4_sg")
|
||||
VSlider/icons/grabber_highlight = ExtResource("5_sgh")
|
||||
VSlider/icons/grabber_disabled = ExtResource("6_sgd")
|
||||
TooltipPanel/styles/panel = SubResource("SBF_tooltip")
|
||||
TooltipLabel/colors/font_color = Color(0.941176, 0.886275, 0.784314, 1)
|
||||
TooltipLabel/font_sizes/font_size = 14
|
||||
LineEdit/styles/normal = SubResource("SBF_lineedit")
|
||||
LineEdit/colors/font_color = Color(0.2, 0.14902, 0.105882, 1)
|
||||
LineEdit/colors/font_placeholder_color = Color(0.333333, 0.301961, 0.254902, 1)
|
||||
SpinBox/icons/updown = ExtResource("7_spu")
|
||||
SpinBox/styles/up_background_hovered = SubResource("SBF_spin_btn_h")
|
||||
SpinBox/styles/up_background_pressed = SubResource("SBF_spin_btn_p")
|
||||
SpinBox/styles/down_background_hovered = SubResource("SBF_spin_btn_h")
|
||||
SpinBox/styles/down_background_pressed = SubResource("SBF_spin_btn_p")
|
||||
HSeparator/styles/separator = SubResource("SBLine_hsep")
|
||||
VSeparator/styles/separator = SubResource("SBLine_vsep")
|
||||
CheckBox/icons/unchecked = ExtResource("9_cbu")
|
||||
CheckBox/icons/checked = ExtResource("10_cbc")
|
||||
CheckBox/icons/unchecked_disabled = ExtResource("11_cbud")
|
||||
CheckBox/icons/checked_disabled = ExtResource("12_cbcd")
|
||||
CheckButton/icons/unchecked = ExtResource("9_cbu")
|
||||
CheckButton/icons/checked = ExtResource("10_cbc")
|
||||
CheckButton/icons/unchecked_disabled = ExtResource("11_cbud")
|
||||
CheckButton/icons/checked_disabled = ExtResource("12_cbcd")
|
||||
OptionButton/styles/normal = SubResource("SBTex_btn")
|
||||
OptionButton/styles/hover = SubResource("SBTex_btn")
|
||||
OptionButton/styles/pressed = SubResource("SBTex_btnp")
|
||||
OptionButton/styles/disabled = SubResource("SBTex_btnp")
|
||||
OptionButton/colors/font_color = Color(0.2, 0.14902, 0.105882, 1)
|
||||
OptionButton/colors/font_hover_color = Color(0.2, 0.14902, 0.105882, 1)
|
||||
OptionButton/colors/font_pressed_color = Color(0.2, 0.14902, 0.105882, 1)
|
||||
OptionButton/colors/font_disabled_color = Color(0.2, 0.14902, 0.105882, 0.45)
|
||||
OptionButton/icons/arrow = ExtResource("8_oa")
|
||||
PopupMenu/styles/panel = SubResource("SBF_popup_panel")
|
||||
PopupMenu/styles/hover = SubResource("SBF_popup_hover")
|
||||
PopupMenu/colors/font_color = Color(0.2, 0.14902, 0.105882, 1)
|
||||
PopupMenu/colors/font_hover_color = Color(0.235294, 0.172549, 0.078431, 1)
|
||||
PopupMenu/font_sizes/font_size = 16
|
||||
@@ -99,10 +99,10 @@ static func build_overlays(host: Control, db) -> Dictionary:
|
||||
# UI 落码批 M2(2026-10-04):单列 VBox→双列(左操作 56% + 右舞台);按钮动词化+悬停解释
|
||||
#(解释全迁 tooltip,G5);lvbtn 升主操作钮(烛光黄 CANDLE);右列=立绘看台。**玩法速览牌已退役
|
||||
#(2026-10-06 用户令:界面禁玩法文字墙——教学内容归 Beta 帮助手册〔教学引导定稿批 P4 丙〕,历史文案在 git)**;
|
||||
# root 挂 UiKit.tooltip_theme() 全树生效。返回键集不变,组合根接线零改)
|
||||
# tooltip 主题归全局主题单点(迭代 25 2026-10-13——旧 root 挂程序化 tooltip 主题的做法退役)。
|
||||
# 返回键集不变,组合根接线零改)
|
||||
static func build_start_overlay(host: Control, db = null) -> Dictionary:
|
||||
var overlay := overlay_bg(host, UiKit.BG_BRIGHT)
|
||||
overlay.theme = UiKit.tooltip_theme()
|
||||
# 石纹弱质感+斜光带+暗角(UI 对齐批 T5 2026-10-06——demo .tilebg 0.14/.beams/.vignette 三层;
|
||||
# 旧满铺石纹不透退役。缺位不挂即回退平色)
|
||||
var bd := UiKit.backdrop_tile()
|
||||
|
||||
@@ -278,7 +278,7 @@ func _slider_row(box: VBoxContainer, title: String) -> Dictionary:
|
||||
s.min_value = 0
|
||||
s.max_value = 300
|
||||
s.step = 5
|
||||
s.custom_minimum_size = Vector2(320, 24)
|
||||
s.custom_minimum_size = Vector2(320, 24) # 皮肤走全局主题(迭代 25:与设置屏音量滑轨同一构建方式,消双轨)
|
||||
row.add_child(s)
|
||||
var v := UiKit.label("+0%",UiKit.FS_BODY, true)
|
||||
v.custom_minimum_size = Vector2(70, 0)
|
||||
|
||||
@@ -56,7 +56,11 @@ func _build() -> void:
|
||||
var vol_lab := UiKit.label("主音量",UiKit.FS_BODY, false, UiKit.INK_LABEL)
|
||||
vol_lab.custom_minimum_size = Vector2(140, 0) # T8 label 列定宽(两列对齐)
|
||||
vol_row.add_child(vol_lab)
|
||||
_volume = UiKit.styled_slider(100.0, 0.0, 100.0)
|
||||
_volume = HSlider.new()
|
||||
_volume.min_value = 0.0
|
||||
_volume.max_value = 100.0
|
||||
_volume.value = 100.0
|
||||
_volume.custom_minimum_size = Vector2(200, 24) # 几何/量程内联(迭代 25:样式滑杆工厂退役,皮肤走全局主题)
|
||||
_volume.step = 1
|
||||
_volume.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
_volume.value_changed.connect(_on_volume_changed)
|
||||
|
||||
@@ -731,32 +731,7 @@ 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
|
||||
# 样式滑杆/tooltip 主题两处局部做法已退役(迭代 25 UI 全局主题批 2026-10-13——词条迁
|
||||
# game/assets/theme/main_theme.tres 全局主题单点:滑轨 slider/grabber_area/grabber、
|
||||
# TooltipPanel/TooltipLabel;旧实现同批删除,禁新旧并存。消费方几何内联:
|
||||
# settings_overlay 音量滑轨 / debug_overlay _slider_row)
|
||||
@@ -28,6 +28,7 @@ window/stretch/mode="canvas_items"
|
||||
[gui]
|
||||
|
||||
theme/custom_font="res://assets/fonts/ui_font_fusion_pixel_zh_hans.ttf"
|
||||
theme/custom="res://assets/theme/main_theme.tres"
|
||||
|
||||
[rendering]
|
||||
|
||||
|
||||
@@ -25,6 +25,19 @@ func check(cond: bool, label: String, quiet: bool = false) -> void:
|
||||
print(" [FAIL] " + label)
|
||||
|
||||
|
||||
# WCAG 相对亮度/对比度(迭代 25 UI 全局主题批 V2——滑轨两段对比度断言用)
|
||||
func _theme_rel_luminance(c: Color) -> float:
|
||||
var lin := func(ch: float) -> float:
|
||||
return ch / 12.92 if ch <= 0.03928 else pow((ch + 0.055) / 1.055, 2.4)
|
||||
return 0.2126 * lin.call(c.r) + 0.7152 * lin.call(c.g) + 0.0722 * lin.call(c.b)
|
||||
|
||||
|
||||
func _theme_contrast(a: Color, b: Color) -> float:
|
||||
var la := _theme_rel_luminance(a)
|
||||
var lb := _theme_rel_luminance(b)
|
||||
return (maxf(la, lb) + 0.05) / (minf(la, lb) + 0.05)
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
print("=== Dungeon Sweeper 垂直切片冒烟 ===")
|
||||
var db = ConfigDBScript.new()
|
||||
@@ -93,6 +106,7 @@ func _run_core_smoke(db, rng) -> void:
|
||||
_smoke_damage_types()
|
||||
_smoke_playtest_logger()
|
||||
_smoke_battle_log_view()
|
||||
_smoke_global_theme()
|
||||
_smoke_encounter_roll()
|
||||
_smoke_attack_chain_fresh_pool()
|
||||
_smoke_clear_reward_chain()
|
||||
@@ -5343,6 +5357,105 @@ func _smoke_battle_log_view() -> void:
|
||||
check(view.parsed_text().length() > before, "行型 %s 格式化落地(解析文本增长)" % String(row_type))
|
||||
|
||||
|
||||
# ---- UI 全局主题冒烟(迭代 25 UI 全局主题批 2026-10-13——需求卡片 §7 验收清单 V1-V6)----
|
||||
# V1 挂载覆盖 / V2 滑轨两段修复 / V3 预盖件运行时证词 / V4 换代退役零残留 /
|
||||
# V5 回归(本冒烟全绿即回归网)/ V6 中文字体防御(挂主题后 default_font 顶掉 custom_font 也不丢字形)
|
||||
func _smoke_global_theme() -> void:
|
||||
print("--- UI 全局主题(main_theme.tres 挂载与词条断言)---")
|
||||
var th := ThemeDB.get_project_theme()
|
||||
check(th != null, "V1 项目主题挂载(project.godot gui/theme/custom → main_theme.tres)")
|
||||
if th == null:
|
||||
return
|
||||
# V1:类型集覆盖 §4「做」清单全部类型(含 PopupMenu 弹层预盖)
|
||||
var need_types := ["VScrollBar", "HScrollBar", "ScrollContainer", "HSlider", "VSlider",
|
||||
"TooltipPanel", "TooltipLabel", "LineEdit", "SpinBox", "HSeparator", "VSeparator",
|
||||
"CheckBox", "CheckButton", "OptionButton", "PopupMenu"]
|
||||
var type_set := th.get_type_list()
|
||||
for t in need_types:
|
||||
check(type_set.has(t), "V1 主题类型覆盖 %s" % t)
|
||||
# V1 红线:Button 类型词条零接触(五态矩阵归 06 第一层第二条)
|
||||
check(not type_set.has("Button"), "V1 红线 Button 类型词条零接触")
|
||||
# V1 红线:focus 族词条一律零定义(扫本主题自有条目,不走默认主题链)
|
||||
var focus_hits := ""
|
||||
for t in type_set:
|
||||
for n in (th.get_stylebox_list(t) + th.get_icon_list(t) + th.get_color_list(t)
|
||||
+ th.get_font_list(t) + th.get_font_size_list(t) + th.get_constant_list(t)):
|
||||
if "focus" in String(n):
|
||||
focus_hits += "%s/%s " % [t, n]
|
||||
check(focus_hits == "", "V1 红线 focus 族词条零定义%s" % ("" if focus_hits == "" else "(违例:" + focus_hits + ")"))
|
||||
# V1:关键词条抽查在位(实例走主题链解析)
|
||||
var vsb := VScrollBar.new()
|
||||
check(vsb.has_theme_stylebox("scroll") and vsb.has_theme_stylebox("grabber")
|
||||
and vsb.has_theme_stylebox("grabber_highlight") and vsb.has_theme_stylebox("grabber_pressed"),
|
||||
"V1 滚动条槽/柄三态词条在位")
|
||||
check(not th.has_stylebox("scroll_focus", "VScrollBar") and not th.has_stylebox("scroll_focus", "HScrollBar"),
|
||||
"V1 滚动条 scroll_focus 未定义(红线)")
|
||||
var hsl := HSlider.new()
|
||||
check(hsl.has_theme_stylebox("slider") and hsl.has_theme_stylebox("grabber_area")
|
||||
and hsl.has_theme_stylebox("grabber_area_highlight")
|
||||
and hsl.has_theme_icon("grabber") and hsl.has_theme_icon("grabber_highlight")
|
||||
and hsl.has_theme_icon("grabber_disabled"), "V1 滑轨槽/填充/柄词条在位")
|
||||
var le := LineEdit.new()
|
||||
check(le.has_theme_stylebox("normal") and le.has_theme_color("font_color")
|
||||
and le.has_theme_color("font_placeholder_color"), "V1 LineEdit 词条在位(含占位色 font_placeholder_color)")
|
||||
var sp := SpinBox.new()
|
||||
check(sp.has_theme_icon("updown"), "V1 SpinBox 箭头词条在位(updown 合并槽)")
|
||||
var hsep := HSeparator.new()
|
||||
var vsep := VSeparator.new()
|
||||
check(hsep.has_theme_stylebox("separator") and vsep.has_theme_stylebox("separator"), "V1 分隔线词条在位")
|
||||
var tpanel := PanelContainer.new()
|
||||
var tlabel := Label.new()
|
||||
check(tpanel.has_theme_stylebox("panel"), "V1 TooltipPanel 词条在位")
|
||||
check(tlabel.has_theme_color("font_color") and tlabel.has_theme_font_size("font_size"), "V1 TooltipLabel 词条在位")
|
||||
# V2:滑轨修复——已填充段 BG_ROOT 对槽暖石 ≈5.5:1(冻结值锚定)
|
||||
var fill_sb: StyleBox = hsl.get_theme_stylebox("grabber_area")
|
||||
var track_sb: StyleBox = hsl.get_theme_stylebox("slider")
|
||||
if fill_sb is StyleBoxFlat and track_sb is StyleBoxFlat:
|
||||
var fill_c: Color = (fill_sb as StyleBoxFlat).bg_color
|
||||
var track_c: Color = (track_sb as StyleBoxFlat).bg_color
|
||||
check(fill_c.is_equal_approx(Color8(58, 46, 34)) and track_c.is_equal_approx(Color8(184, 165, 130)),
|
||||
"V2 冻结值锚定(已填充段=BG_ROOT #3A2E22,槽=暖石 #B8A582)")
|
||||
var ratio := _theme_contrast(fill_c, track_c)
|
||||
check(ratio >= 4.5, "V2 滑轨两段对比 %.2f:1 ≥4.5:1(拖到中点一眼可辨)" % ratio)
|
||||
else:
|
||||
check(false, "V2 滑轨 stylebox 类型均为 StyleBoxFlat")
|
||||
# V3:预盖件运行时证词(临时实例化读词条——无实役控件的在役证据)
|
||||
var cbx := CheckBox.new()
|
||||
check(cbx.get_theme_icon("checked") != null and cbx.get_theme_icon("unchecked") != null
|
||||
and cbx.get_theme_icon("checked_disabled") != null and cbx.get_theme_icon("unchecked_disabled") != null,
|
||||
"V3 CheckBox 四态图标非空")
|
||||
var cbt := CheckButton.new()
|
||||
check(cbt.get_theme_icon("checked") != null and cbt.get_theme_icon("unchecked") != null
|
||||
and cbt.get_theme_icon("checked_disabled") != null and cbt.get_theme_icon("unchecked_disabled") != null,
|
||||
"V3 CheckButton 四态图标非空(与 CheckBox 同套 checked/unchecked 名,4.7 源码实证)")
|
||||
var pm := PopupMenu.new()
|
||||
check(pm.has_theme_stylebox("panel") and pm.has_theme_stylebox("hover")
|
||||
and pm.has_theme_font_size("font_size"), "V3 PopupMenu 词条存在(弹层预盖)")
|
||||
var ob := OptionButton.new()
|
||||
check(ob.has_theme_stylebox("normal") and ob.has_theme_stylebox("hover")
|
||||
and ob.has_theme_stylebox("pressed") and ob.has_theme_color("font_color"),
|
||||
"V3 OptionButton 词条存在(预盖)")
|
||||
# V4:换代退役——源码零残留(剥注释行后扫;ui_kit 两函数+挂载行+滑轨工厂)
|
||||
for path in ["res://presentation/views/ui_kit.gd", "res://presentation/ui_assembler.gd",
|
||||
"res://presentation/views/settings_overlay.gd", "res://presentation/views/debug_overlay.gd"]:
|
||||
var src: String = FileAccess.get_file_as_string(path)
|
||||
var bad := ""
|
||||
for ln in src.split("\n", false):
|
||||
var s := ln.strip_edges()
|
||||
if s.begins_with("#"):
|
||||
continue
|
||||
if "styled_slider" in s or "tooltip_theme" in s or "_tip_theme" in s or "overlay.theme" in s:
|
||||
bad += s + " "
|
||||
check(bad == "", "V4 退役实现零残留(%s)%s" % [path, "" if bad == "" else "违例:" + bad])
|
||||
# V6:中文字体防御——default_font 非空且 Label 实取字体非空
|
||||
check(th.default_font != null, "V6 主题 default_font 非空(挂主题顶掉 custom_font 也不丢中文字形)")
|
||||
var lbl := Label.new()
|
||||
lbl.text = "测试"
|
||||
check(lbl.get_theme_font("font") != null, "V6 Label 实取字体非空(中文字形防御实证)")
|
||||
vsb.free(); hsl.free(); le.free(); sp.free(); hsep.free(); vsep.free()
|
||||
tpanel.free(); tlabel.free(); cbx.free(); cbt.free(); pm.free(); ob.free(); lbl.free()
|
||||
|
||||
|
||||
# ---- 存档序列化冒烟(SaveManager——读写往返/新档合法/余额拒花/锻造出域拒/高版本拒读禁写/
|
||||
# 损坏兜底/迁移链/域界干净/摘要 cap/摘要集成/生涯终身计数域 v6)。测试文件统一 user://smoke_save_*.json 隔离,
|
||||
# 段首清场+段尾清理——绝不碰 user://save.json(红线)。
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""UI 全局主题批小位图生成脚本(迭代 25 T0,2026-10-13)。
|
||||
|
||||
产出(→ game/assets/art/ui/gen/,纯色/几何、像素风):
|
||||
slider_grabber.png 滑轨柄·常 12×12 CANDLE #F6C667 + PANEL_BORDER #786E58 1px 描边
|
||||
slider_grabber_highlight.png 滑轨柄·悬 12×12 CANDLE 提亮档(lightened≈12%) + 同描边
|
||||
slider_grabber_disabled.png 滑轨柄·废 12×12 STONE_DEEP #554D41 + 同描边
|
||||
spinbox_updown.png SpinBox 箭头 16×16 INK #33261B ▲▼ 合一(Godot 4 SpinBox 走 updown 合并图标槽)
|
||||
option_arrow.png OptionButton 箭头 8×8 INK 向下三角
|
||||
checkbox_unchecked.png 勾选框·空 14×14 PANEL_DATA #E9D9B8 + PANEL_BORDER 边
|
||||
checkbox_checked.png 勾选框·选 14×14 同底边 + INK 对勾
|
||||
checkbox_unchecked_disabled.png 勾选框·空·废 14×14 同底 + STONE_DEEP 边
|
||||
checkbox_checked_disabled.png 勾选框·选·废 14×14 同底边 + STONE_DEEP 对勾
|
||||
|
||||
色值全部取自 game/presentation/views/ui_kit.gd 既有常量(溯源表见 game/assets/theme/README.md)——
|
||||
不新增美术素材、不动美术圣经 §7(迭代 25 契约)。滚动条柄不走位图:Godot 4 ScrollBar 的
|
||||
grabber 是 StyleBox(非 Icon),纯色 StyleBoxFlat 直接承载(见 main_theme.tres)。
|
||||
|
||||
♻ 可重跑:本脚本幂等,重复执行覆盖产出;改色值只改本脚本顶部常量后重跑 + 重导。
|
||||
用法: python tools/gen_theme_icons.py
|
||||
"""
|
||||
import os
|
||||
|
||||
from PIL import Image
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
OUT = os.path.join(ROOT, "game", "assets", "art", "ui", "gen")
|
||||
|
||||
# ── 色值(↔ ui_kit.gd 常量,冻结)────────────────────────────────────────────
|
||||
INK = (51, 38, 27) # INK #33261B
|
||||
PANEL_DATA = (233, 217, 184) # PANEL_DATA #E9D9B8
|
||||
PANEL_BORDER = (120, 110, 88) # PANEL_BORDER #786E58
|
||||
STONE_DEEP = (85, 77, 65) # STONE_DEEP #554D41
|
||||
CANDLE = (246, 198, 103) # CANDLE #F6C667
|
||||
|
||||
|
||||
def lighten(c, amount=0.12):
|
||||
"""复刻 Godot Color.lightened:每通道 + (255-通道)×amount(sRGB 存储值域)"""
|
||||
return tuple(min(255, round(v + (255 - v) * amount)) for v in c)
|
||||
|
||||
|
||||
def save(img, name):
|
||||
path = os.path.join(OUT, name)
|
||||
img.save(path)
|
||||
print("wrote %s (%dx%d)" % (name, img.width, img.height))
|
||||
|
||||
|
||||
def slider_grabber(fill):
|
||||
"""12×12 方柄:fill 实心 + PANEL_BORDER 1px 描边"""
|
||||
img = Image.new("RGBA", (12, 12), (0, 0, 0, 0))
|
||||
for x in range(12):
|
||||
for y in range(12):
|
||||
edge = x in (0, 11) or y in (0, 11)
|
||||
img.putpixel((x, y), PANEL_BORDER + (255,) if edge else fill + (255,))
|
||||
return img
|
||||
|
||||
|
||||
def spinbox_updown():
|
||||
"""16×16 ▲▼ 合一:上三角 y2-7、下三角 y9-14,各宽 12(INK)"""
|
||||
img = Image.new("RGBA", (16, 16), (0, 0, 0, 0))
|
||||
# 上三角:顶行中心 2px(x7-8),第 6 行满宽 x2-13
|
||||
for row in range(6):
|
||||
half = 1 + row # 第 row 行左右各扩 1
|
||||
for x in range(8 - half, 8 + half):
|
||||
img.putpixel((x, 2 + row), INK + (255,))
|
||||
# 下三角:镜像(第 row 行满宽在底)
|
||||
for row in range(6):
|
||||
half = 6 - row
|
||||
for x in range(8 - half, 8 + half):
|
||||
img.putpixel((x, 9 + row), INK + (255,))
|
||||
return img
|
||||
|
||||
|
||||
def option_arrow():
|
||||
"""8×8 向下三角(OptionButton 箭头)"""
|
||||
img = Image.new("RGBA", (8, 8), (0, 0, 0, 0))
|
||||
for row in range(5):
|
||||
half = row + 1
|
||||
for x in range(4 - half, 4 + half - 1):
|
||||
img.putpixel((x, 1 + row), INK + (255,))
|
||||
return img
|
||||
|
||||
|
||||
def checkbox(border, mark):
|
||||
"""14×14 勾选框:PANEL_DATA 底 + 1px 边;mark 非 None 时画 2px 对勾"""
|
||||
img = Image.new("RGBA", (14, 14), (0, 0, 0, 0))
|
||||
for x in range(14):
|
||||
for y in range(14):
|
||||
edge = x in (0, 13) or y in (0, 13)
|
||||
img.putpixel((x, y), border + (255,) if edge else PANEL_DATA + (255,))
|
||||
if mark is not None:
|
||||
# 对勾:(3,7)→(6,10)→(11,3),2px 粗(内区 1..12)
|
||||
seg = [(3, 7), (4, 7), (4, 8), (5, 8), (5, 9), (6, 9),
|
||||
(6, 10), (7, 10), (7, 11), (8, 11), (8, 10), (9, 10),
|
||||
(9, 9), (10, 9), (10, 8), (11, 8),
|
||||
(11, 7), (10, 7), (10, 6), (9, 6), (9, 5), (8, 5),
|
||||
(8, 4), (7, 4), (7, 3), (6, 3)]
|
||||
for (x, y) in seg:
|
||||
if 1 <= x <= 12 and 1 <= y <= 12:
|
||||
img.putpixel((x, y), mark + (255,))
|
||||
return img
|
||||
|
||||
|
||||
def main():
|
||||
os.makedirs(OUT, exist_ok=True)
|
||||
save(slider_grabber(CANDLE), "slider_grabber.png")
|
||||
save(slider_grabber(lighten(CANDLE)), "slider_grabber_highlight.png")
|
||||
save(slider_grabber(STONE_DEEP), "slider_grabber_disabled.png")
|
||||
save(spinbox_updown(), "spinbox_updown.png")
|
||||
save(option_arrow(), "option_arrow.png")
|
||||
save(checkbox(PANEL_BORDER, None), "checkbox_unchecked.png")
|
||||
save(checkbox(PANEL_BORDER, INK), "checkbox_checked.png")
|
||||
save(checkbox(STONE_DEEP, None), "checkbox_unchecked_disabled.png")
|
||||
save(checkbox(STONE_DEEP, STONE_DEEP), "checkbox_checked_disabled.png")
|
||||
print("done → %s" % OUT)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user