Files
gplan-2/tools/gen_theme_icons.py
T
DKChan af2d834e78 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
2026-10-13 21:39:01 +08:00

121 lines
5.2 KiB
Python
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.
#!/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()