主题与配色
tkfluent 的主题体现在三个层次:窗口级深浅模式、全局主色调、 单个组件的样式。三者可以独立使用。
一、深浅模式
同一个界面在两套配色下的样子(FluThemeManager 切换,绘制层不改一行):
mode='light';右:mode='dark'单个组件
每个组件在构造时接受 mode:
tkflu.FluButton(root, text="浅色", mode="light")
tkflu.FluButton(root, text="深色", mode="dark")
整个窗口
FluWindow 也有 mode,但它不会自动递归改子组件。
批量切换请用 FluThemeManager:
import tkflu
root = tkflu.FluWindow(mode="light")
thememanager = tkflu.FluThemeManager(window=root, mode="light")
tkflu.FluButton(root, text="切换主题", command=thememanager.toggle).pack()
root.mainloop()
或者配合开关组件:
toggle = tkflu.FluToggleButton(root, text="深色模式")
toggle.dconfigure(command=lambda: tkflu.toggle_theme(toggle, thememanager))
FluThemeManager.mode() 会走完窗口的整棵控件树(不分层级、自动去重),
把每个持有配色的组件都切到目标模式。组件挂多深都不影响——
嵌套面板、菜单栏里的菜单项、列表 / 导航栏都会被覆盖到,
不需要你手写 update_children() 之类的向下传播。
二、全局主色调
强调色(style="accent" 的按钮、开关等)取的是一个全局主色调,
提供 6 个预设:
import tkflu
tkflu.blue_primary_color() # 默认
tkflu.red_primary_color()
tkflu.orange_primary_color()
tkflu.yellow_primary_color()
tkflu.green_primary_color()
tkflu.purple_primary_color()
也可以自己指定一对颜色(浅色模式用第一个,深色模式用第二个):
from tkflu import set_primary_color
set_primary_color(("#005fb8", "#60cdff"))
改完要重绘
主色调是读取时取值,已经画好的组件不会自动变色。
改完之后需要重新 theme() 或 _draw(),最简单是重启界面:
tkflu.purple_primary_color()
# 已存在的强调色按钮需要重画才会生效
button.theme(mode="light", style="accent")
三、单组件样式
FluButton 有三种样式:
tkflu.FluButton(root, text="standard") # 默认:浅色描边
tkflu.FluButton(root, text="accent", style="accent") # 强调色填充
tkflu.FluButton(root, text="menu", style="menu") # 无边框菜单项
FluFrame 有两种:
tkflu.FluFrame(root, style="standard") # 普通面板
tkflu.FluFrame(root, style="popupmenu") # 弹出菜单用的面板
四、组件的四种状态
每个交互组件的配色由 (mode, style, state) 决定,其中 state 有四档:
| 状态 | 触发时机 |
|---|---|
rest |
常态 |
hover |
鼠标悬停 |
pressed |
按下 |
disabled |
dconfigure(state="disabled") |
想改某个状态的配色,可以覆盖组件自己的状态字典:
button = tkflu.FluButton(root, text="自定义悬停色")
button.dconfigure(hover={
"back_color": "#ffe0e0",
"back_opacity": 1,
"border_color": "#ff0000",
"border_color_opacity": 1,
"border_color2": None,
"border_color2_opacity": None,
"border_width": 1,
"radius": 6,
"text_color": "#a00000",
})
button._draw()
五、过渡动画
主题切换的渐变过渡由两个全局参数控制:
from tkflu import set_animation_steps, set_animation_step_time
set_animation_steps(5) # 过渡帧数
set_animation_step_time(20) # 每帧间隔(毫秒)
两者都设为 0(默认值)即关闭过渡。
所有组件走同一条时间轴
一次换肤里,整棵树共用一条时间轴:每一帧把同一个进度 t(0 → 1)
写给所有组件,然后统一重绘。因此颜色是"一起变"的,不会出现
"这个按钮已经变完、旁边那个还没动"。
这条时间轴由 theme_transition 驱动,
也可以直接调:
from tkflu import run_theme_transition
run_theme_transition(root, "dark") # 整棵子树一起过渡
run_theme_transition(root, "dark", steps=0) # 只要结果,不要动画
run_theme_transition(root, "dark", easing="linear")
FluThemeManager.mode() 还可以按次覆盖全局设置:
thememanager.mode("dark", animation_steps=8, animation_step_time=16, easing="ease_out")
thememanager.mode("dark", animation_steps=0) # 这一次不要动画
缓动曲线
默认是 ease_in_out(两头慢、中间快),比匀速自然得多。可选:
from tkflu import easing_names, set_theme_easing
print(easing_names())
# ['ease_in', 'ease_in_out', 'ease_out', 'ease_out_back', 'linear']
set_theme_easing("ease_out") # 快起慢落
帧数会按渲染开销自动裁剪
主题过渡的每一帧都要重绘整棵树,所以"能画几帧"完全取决于渲染引擎:
默认的 tksvg 下一屏 20 来个组件重绘一次要 100~200ms,而 skia 只要十几毫秒。
因此实际帧数由预算决定,而不是硬按 steps × step_time 的时间表去排:
- 库先抽几个组件量一次重绘开销,估算整棵树要多久;
- 再把帧数降到能塞进预算的数量——宁可少几帧,也要每一帧都完整、同步;
- 第一帧实测之后还会再收紧一次,所以抽样估偏也不会拖长。
预算默认 600ms,可以调:
from tkflu import set_transition_budget
set_transition_budget(900) # 想要更长更顺的过渡
set_transition_budget(0) # 不限制(渲染慢时会真的变慢)
想要丝滑就换渲染引擎
过渡是否顺滑,主要取决于一次全树重绘要多久。
在 tksvg 上加帧数收效甚微,换个栅格引擎立竿见影:
import tkflu
tkflu.set_renderer("skia") # 或 "pillow"(零额外依赖)
详见 渲染引擎。
mode() 不会阻塞
FluThemeManager.mode() 不会在返回前把过渡跑完,也不会去跑事件循环
(早期版本在每个组件后面调了一次 update(),组件一多就会"卡两秒然后瞬间变色")。
它只做两件事:静默地把目标配色写进各组件的 attributes,
然后把重绘交给事件循环。所以从点击到界面响应之间的延迟,只有几十毫秒。
过渡对象本身也能用:
transition = thememanager.mode("dark")
transition.finished # 画完了吗
transition.frames_painted # 实际画了几帧
transition.describe() # 一句话说明这次过渡实际发生了什么
transition.cancel() # 中断(restore=False 可以保留中间色接着切)
连点"切换主题"不会叠加帧队列:新的过渡会就地接管旧的, 并以当前中间色作为新起点,画面是连续的。
六、设计规范在哪
组件"长什么样"集中在 tkflu.designs 包里,每个模块返回一份普通的 dict:
from tkflu.designs.button import button
button("light", "accent", "hover")
# {'back_color': '#005fb8', 'back_opacity': '0.9', ...}
所以换皮肤不需要改组件代码,改这些函数的返回值即可。 每个模块的取值说明见 设计规范 API。