跳转至

tkflu

Fluent设计的tkinter组件库(模板)


作者:XiangQinxi

贡献者:totowang-hhh

FluGradient

源代码位于: tkflu\designs\gradient.py
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
class FluGradient:
    def hex_to_rgb(self, h: str):
        h = h.lstrip("#")
        return tuple(int(h[i : i + 2], 16) for i in (0, 2, 4))

    def rgb_to_hex(self, rgb):
        return f"#{rgb[0]:02x}{rgb[1]:02x}{rgb[2]:02x}"

    def generate_rgb2hex(self, start_color, end_color, steps):
        """生成颜色渐变序列"""
        import numpy as np

        gradient = []
        for t in np.linspace(0, 1, steps):
            r = int(start_color[0] + (end_color[0] - start_color[0]) * t)
            g = int(start_color[1] + (end_color[1] - start_color[1]) * t)
            b = int(start_color[2] + (end_color[2] - start_color[2]) * t)
            gradient.append(f"#{r:02x}{g:02x}{b:02x}")
        return gradient

    def generate_hex2hex(self, start_hex, end_hex, steps):
        """
        专为HEX颜色设计的渐变生成器
        :param start_hex: 起始颜色 HEX格式 (如 "#FF0000")
        :param end_hex: 结束颜色 HEX格式 (如 "#0000FF")
        :param steps: 渐变步数
        :return: HEX格式的颜色列表
        """

        # 去除#号并转换为RGB元组

        # RGB转HEX

        rgb_start = self.hex_to_rgb(start_hex)
        rgb_end = self.hex_to_rgb(end_hex)
        import numpy as np

        gradient = []
        if steps is None:
            return None
        for t in np.linspace(0, 1, steps):
            # 计算每个通道的中间值
            r = int(rgb_start[0] + (rgb_end[0] - rgb_start[0]) * t)
            g = int(rgb_start[1] + (rgb_end[1] - rgb_start[1]) * t)
            b = int(rgb_start[2] + (rgb_end[2] - rgb_start[2]) * t)

            # 确保值在0-255范围内并转换为HEX
            gradient.append(
                self.rgb_to_hex(
                    (max(0, min(255, r)), max(0, min(255, g)), max(0, min(255, b)))
                )
            )

        return gradient

generate_hex2hex(start_hex, end_hex, steps)

专为HEX颜色设计的渐变生成器 :param start_hex: 起始颜色 HEX格式 (如 "#FF0000") :param end_hex: 结束颜色 HEX格式 (如 "#0000FF") :param steps: 渐变步数 :return: HEX格式的颜色列表

源代码位于: tkflu\designs\gradient.py
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
def generate_hex2hex(self, start_hex, end_hex, steps):
    """
    专为HEX颜色设计的渐变生成器
    :param start_hex: 起始颜色 HEX格式 (如 "#FF0000")
    :param end_hex: 结束颜色 HEX格式 (如 "#0000FF")
    :param steps: 渐变步数
    :return: HEX格式的颜色列表
    """

    # 去除#号并转换为RGB元组

    # RGB转HEX

    rgb_start = self.hex_to_rgb(start_hex)
    rgb_end = self.hex_to_rgb(end_hex)
    import numpy as np

    gradient = []
    if steps is None:
        return None
    for t in np.linspace(0, 1, steps):
        # 计算每个通道的中间值
        r = int(rgb_start[0] + (rgb_end[0] - rgb_start[0]) * t)
        g = int(rgb_start[1] + (rgb_end[1] - rgb_start[1]) * t)
        b = int(rgb_start[2] + (rgb_end[2] - rgb_start[2]) * t)

        # 确保值在0-255范围内并转换为HEX
        gradient.append(
            self.rgb_to_hex(
                (max(0, min(255, r)), max(0, min(255, g)), max(0, min(255, b)))
            )
        )

    return gradient

generate_rgb2hex(start_color, end_color, steps)

生成颜色渐变序列

源代码位于: tkflu\designs\gradient.py
14
15
16
17
18
19
20
21
22
23
24
def generate_rgb2hex(self, start_color, end_color, steps):
    """生成颜色渐变序列"""
    import numpy as np

    gradient = []
    for t in np.linspace(0, 1, steps):
        r = int(start_color[0] + (end_color[0] - start_color[0]) * t)
        g = int(start_color[1] + (end_color[1] - start_color[1]) * t)
        b = int(start_color[2] + (end_color[2] - start_color[2]) * t)
        gradient.append(f"#{r:02x}{g:02x}{b:02x}")
    return gradient

FluRenderer

Bases: object

面向对象风格的渲染器开关(保留旧 API)。

::

renderer = FluRenderer()
renderer.renderer("skia")      # 设置
renderer.renderer()            # 读取,-> 2
源代码位于: tkflu\designs\renderer.py
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
class FluRenderer(object):
    """面向对象风格的渲染器开关(保留旧 API)。

    ::

        renderer = FluRenderer()
        renderer.renderer("skia")      # 设置
        renderer.renderer()            # 读取,-> 2
    """

    def renderer(self, way=None):
        """读写当前渲染器。

        :param way: ``None`` 表示只读;否则等价于 :func:`set_renderer`
        :returns: 读取时返回编号,设置时返回 ``None``
        """
        if way is None:
            return get_renderer()
        set_renderer(way)
        return None

renderer(way=None)

读写当前渲染器。

:param way: None 表示只读;否则等价于 :func:set_renderer :returns: 读取时返回编号,设置时返回 None

源代码位于: tkflu\designs\renderer.py
256
257
258
259
260
261
262
263
264
265
def renderer(self, way=None):
    """读写当前渲染器。

    :param way: ``None`` 表示只读;否则等价于 :func:`set_renderer`
    :returns: 读取时返回编号,设置时返回 ``None``
    """
    if way is None:
        return get_renderer()
    set_renderer(way)
    return None

SegoeFluentIcons(size=9, weight='normal', *args, **kwargs)

加载一份 Segoe Fluent Icons 字体对象。

:param kwargs: 可含 master(多解释器场景必须传,理由同 :func:SegoeFont)

源代码位于: tkflu\designs\fonts\__init__.py
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
def SegoeFluentIcons(size=9, weight="normal", *args, **kwargs):
    """加载一份 Segoe Fluent Icons 字体对象。

    :param kwargs: 可含 ``master``(多解释器场景必须传,理由同 :func:`SegoeFont`)
    """
    from _tkinter import TclError

    master = kwargs.pop("master", None)

    try:
        from tkextrafont import Font

        font = Font(
            root=master,
            file=segoefluenticons,
            size=size,
            family="Segoe Fluent Icons",
            *args,
            **kwargs
        )
    except TclError:
        try:
            from tkinter.font import Font

            font = Font(
                root=master,
                size=size,
                family="Segoe Fluent Icons",
                *args,
                **kwargs
            )
        except TclError:
            from tkinter.font import nametofont

            font = nametofont("TkDefaultFont")
            font.configure(size=size, weight=weight)
    return font

SegoeFont(size=9, weight='normal', master=None)

加载一份 Segoe UI 字体对象。

:param size: 字号 :param weight: 字重("normal" / "bold") :param master: 目标 Tk 控件;多解释器场景必须传。

.. warning:: 不传 master 时,tkextrafont / tkinter.font 都会把字体注册到 tkinter._default_root 所属的那个解释器上。同一个进程里开了第二个 FluWindow 时,第二个解释器并不认识这个名字,Tk 会静默退回 系统兜底字体(实测变成"宋体 18"),文字又大又难看。 组件层统一通过 :func:tkflu.defs.set_default_font 传入自己的 master。

源代码位于: tkflu\designs\fonts\__init__.py
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
def SegoeFont(size=9, weight="normal", master=None):
    """加载一份 Segoe UI 字体对象。

    :param size: 字号
    :param weight: 字重(``"normal"`` / ``"bold"``)
    :param master: 目标 Tk 控件;**多解释器场景必须传**。

    .. warning::
       不传 ``master`` 时,``tkextrafont`` / ``tkinter.font`` 都会把字体注册到
       ``tkinter._default_root`` 所属的那个解释器上。同一个进程里开了第二个
       ``FluWindow`` 时,第二个解释器并不认识这个名字,Tk 会**静默退回**
       系统兜底字体(实测变成"宋体 18"),文字又大又难看。
       组件层统一通过 :func:`tkflu.defs.set_default_font` 传入自己的 ``master``。
    """
    from _tkinter import TclError

    try:
        from tkextrafont import Font

        font = Font(file=segoefont, size=size, family="Segoe UI", root=master)
    except TclError:
        try:
            from tkinter.font import Font

            font = Font(root=master, size=size, family="Segoe UI", weight=weight)
        except TclError:
            from tkinter.font import nametofont

            font = nametofont("TkDefaultFont")
            font.configure(size=size, weight=weight)
    return font

call_command(callback, *args)

按回调实际能接受的参数个数调用它。

组件回调的签名历来不统一:FluButton 的 command 不接受参数, 而列表 / 导航类组件的回调天然想知道"选中了哪一项"。如果直接 callback(index, item),所有只写 lambda: ... 的老代码都会 TypeError;反过来直接 callback(),想拿到选中项的人又拿不到。

这里检查一次签名,取两者都能接受的最大参数个数——既不让老写法崩, 也不强迫新写法用 *args。

:param callback: 可调用对象;为 None 时直接返回 None :param args: 候选参数(按位置顺序) :returns: 回调的返回值

::

call_command(lambda: print("hi"))              # -> hi(忽略多出来的参数)
call_command(lambda i: print(i), 3)            # -> 3
call_command(lambda *a: print(a), 1, 2)        # -> (1, 2)

.. note:: 签名无法获取时(部分 C 实现的内建可调用对象)退化为"不传参数"。

源代码位于: tkflu\defs.py
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
def call_command(callback, *args):
    """按回调**实际能接受**的参数个数调用它。

    组件回调的签名历来不统一:``FluButton`` 的 ``command`` 不接受参数,
    而列表 / 导航类组件的回调天然想知道"选中了哪一项"。如果直接
    ``callback(index, item)``,所有只写 ``lambda: ...`` 的老代码都会
    ``TypeError``;反过来直接 ``callback()``,想拿到选中项的人又拿不到。

    这里检查一次签名,取两者都能接受的最大参数个数——既不让老写法崩,
    也不强迫新写法用 ``*args``。

    :param callback: 可调用对象;为 ``None`` 时直接返回 ``None``
    :param args: 候选参数(按位置顺序)
    :returns: 回调的返回值

    ::

        call_command(lambda: print("hi"))              # -> hi(忽略多出来的参数)
        call_command(lambda i: print(i), 3)            # -> 3
        call_command(lambda *a: print(a), 1, 2)        # -> (1, 2)

    .. note::
       签名无法获取时(部分 C 实现的内建可调用对象)退化为"不传参数"。
    """
    if callback is None:
        return None

    import inspect

    try:
        parameters = list(inspect.signature(callback).parameters.values())
    except (TypeError, ValueError):
        return callback()

    if any(p.kind is p.VAR_POSITIONAL for p in parameters):
        return callback(*args)

    positional = sum(
        1
        for p in parameters
        if p.kind in (p.POSITIONAL_ONLY, p.POSITIONAL_OR_KEYWORD)
    )
    return callback(*args[:positional])

dark()

深色主题图标的文件路径(历史 API,见模块文档)。

源代码位于: tkflu\icons.py
 98
 99
100
def dark() -> str:
    """深色主题图标的文件路径(历史 API,见模块文档)。"""
    return _materialize("dark")

easing_function(name)

按名字取缓动函数;名字不认识时退回默认曲线而不是抛异常。

理由和 :meth:tkflu.button.FluButton.theme 里那个"未知 style 退回默认值" 的约定一致:一次换肤不该因为一个拼错的曲线名整趟崩掉。

:param name: 曲线名,或一个可调用对象(原样返回)

源代码位于: tkflu\designs\animation.py
 98
 99
100
101
102
103
104
105
106
107
108
109
110
def easing_function(name: Optional[str]):
    """按名字取缓动函数;名字不认识时**退回默认曲线**而不是抛异常。

    理由和 :meth:`tkflu.button.FluButton.theme` 里那个"未知 style 退回默认值"
    的约定一致:一次换肤不该因为一个拼错的曲线名整趟崩掉。

    :param name: 曲线名,或一个可调用对象(原样返回)
    """
    if callable(name):
        return name
    if not name:
        return EASINGS[DEFAULT_EASING]
    return EASINGS.get(str(name).lower(), EASINGS[DEFAULT_EASING])

easing_names()

所有可用的缓动名字(排序后)。

源代码位于: tkflu\designs\animation.py
93
94
95
def easing_names():
    """所有可用的缓动名字(排序后)。"""
    return sorted(EASINGS)

get_renderer()

当前渲染器编号(保持历史 API)。

:returns: 0–4;组件里判"是不是 Wand"用 get_renderer() == 1

源代码位于: tkflu\designs\renderer.py
207
208
209
210
211
212
def get_renderer() -> int:
    """当前渲染器编号(保持历史 API)。

    :returns: ``0``–``4``;组件里判"是不是 Wand"用 ``get_renderer() == 1``
    """
    return int(environ.get(_ENV_KEY, "0"))

get_renderer_name()

当前引擎名,比编号更直观。

源代码位于: tkflu\designs\renderer.py
215
216
217
def get_renderer_name() -> str:
    """当前引擎名,比编号更直观。"""
    return get_engine_name()

get_theme_easing()

当前主题过渡的缓动曲线名。

源代码位于: tkflu\designs\animation.py
118
119
120
def get_theme_easing() -> str:
    """当前主题过渡的缓动曲线名。"""
    return environ.get("tkfluent.theme_easing", DEFAULT_EASING)

icon()

默认应用图标的文件路径(历史 API,见模块文档)。

源代码位于: tkflu\icons.py
88
89
90
def icon() -> str:
    """默认应用图标的文件路径(历史 API,见模块文档)。"""
    return _materialize("icon")

icon_data(name='light')

返回图标的 base64 数据(不落盘)。

源代码位于: tkflu\icons.py
68
69
70
def icon_data(name: str = "light") -> bytes:
    """返回图标的 base64 数据(不落盘)。"""
    return _icon_bytes(name)

icon_photoimage(name='light', master=None)

直接用内嵌 base64 建 PhotoImage,完全不落盘。

:param name: "light" / "dark" / "icon" :param master: 目标控件;多解释器场景必须传,否则图片会挂到默认根窗口

.. warning:: 这里刻意不做进程级缓存。

早先按 (id(tk), name) 缓存过一份 PhotoImage,看起来省了一次 解码,代价却很大:

  • PhotoImage 与它的 Tk 解释器同生共死,缓存里留一张就等于 让整个解释器无法回收——实测反复开关窗口 50 次,内存涨了 ~96 MB(清掉缓存后只涨 ~12 MB);
  • id() 会被复用。解释器销毁后 id 可能被下一个解释器占用, 于是拿到的是一张属于已死解释器的图片,报 "not a photo image"。

解码内嵌的 base64 很便宜,而调用方(FluWindow / FluToplevel) 本来就用 self._icon_photo 持着引用,不需要这里再存一份。

源代码位于: tkflu\icons.py
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
def icon_photoimage(name: str = "light", master=None):
    """直接用内嵌 base64 建 ``PhotoImage``,**完全不落盘**。

    :param name: ``"light"`` / ``"dark"`` / ``"icon"``
    :param master: 目标控件;**多解释器场景必须传**,否则图片会挂到默认根窗口

    .. warning::
       这里**刻意不做进程级缓存**。

       早先按 ``(id(tk), name)`` 缓存过一份 ``PhotoImage``,看起来省了一次
       解码,代价却很大:

       * ``PhotoImage`` 与它的 Tk 解释器同生共死,缓存里留一张就等于
         **让整个解释器无法回收**——实测反复开关窗口 50 次,内存涨了
         ~96 MB(清掉缓存后只涨 ~12 MB);
       * ``id()`` 会被复用。解释器销毁后 id 可能被下一个解释器占用,
         于是拿到的是一张属于**已死解释器**的图片,报 "not a photo image"。

       解码内嵌的 base64 很便宜,而调用方(``FluWindow`` / ``FluToplevel``)
       本来就用 ``self._icon_photo`` 持着引用,不需要这里再存一份。
    """
    from tkinter import PhotoImage

    return PhotoImage(data=_icon_bytes(name), master=master)

is_wide_char(char)

判断是否为"宽字符"(中日韩文字、全角标点、emoji 等)。

这类字符的显示宽度约为拉丁字母的两倍。用 len() 估算文本宽度时 必须把它们按 2 个字符计,否则中文标签会被算得严重偏窄。

源代码位于: tkflu\defs.py
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
def is_wide_char(char: str) -> bool:
    """判断是否为"宽字符"(中日韩文字、全角标点、emoji 等)。

    这类字符的显示宽度约为拉丁字母的两倍。用 ``len()`` 估算文本宽度时
    必须把它们按 2 个字符计,否则中文标签会被算得严重偏窄。
    """
    if not char:
        return False
    code = ord(char)
    return (
        0x1100 <= code <= 0x115F  # 谚文字母
        or 0x2E80 <= code <= 0x303E  # 中日韩部首、康熙部首、中日韩符号
        or 0x3041 <= code <= 0x33FF  # 假名、注音、谚文兼容、中日韩兼容
        or 0x3400 <= code <= 0x4DBF  # 中日韩扩展 A
        or 0x4E00 <= code <= 0x9FFF  # 中日韩统一表意文字
        or 0xA000 <= code <= 0xA4CF  # 彝文
        or 0xAC00 <= code <= 0xD7A3  # 谚文音节
        or 0xF900 <= code <= 0xFAFF  # 中日韩兼容表意文字
        or 0xFE30 <= code <= 0xFE6F  # 中日韩兼容形式、小写变体
        or 0xFF00 <= code <= 0xFF60  # 全角形式
        or 0xFFE0 <= code <= 0xFFE6  # 全角符号
        or 0x1F300 <= code <= 0x1F9FF  # emoji
        or 0x20000 <= code <= 0x3FFFD  # 中日韩扩展 B 及以后
    )

label_cells(label)

把标签折算成"拉丁字符个数":宽字符算 2 个。

源代码位于: tkflu\defs.py
37
38
39
def label_cells(label) -> int:
    """把标签折算成"拉丁字符个数":宽字符算 2 个。"""
    return sum(2 if is_wide_char(ch) else 1 for ch in str(label or ""))

light()

浅色主题图标的文件路径(历史 API,见模块文档)。

源代码位于: tkflu\icons.py
93
94
95
def light() -> str:
    """浅色主题图标的文件路径(历史 API,见模块文档)。"""
    return _materialize("light")

list_renderers()

列出 (编号, 引擎名, 是否可用)。

::

>>> list_renderers()
[(0, 'tksvg', True), (1, 'wand', True), (2, 'skia', True), ...]
源代码位于: tkflu\designs\renderer.py
220
221
222
223
224
225
226
227
228
229
230
231
232
def list_renderers():
    """列出 ``(编号, 引擎名, 是否可用)``。

    ::

        >>> list_renderers()
        [(0, 'tksvg', True), (1, 'wand', True), (2, 'skia', True), ...]
    """
    available = list_engines()
    return [
        (index, name, available.get(name, False))
        for index, name in sorted(RENDERER_INDEX.items())
    ]

measure_label_width(master, label, padding=26)

估算一个菜单/按钮标签需要多宽(像素)。

历史实现用 len(label) * 8:对 "FluMenu1" 这类拉丁标签大致够用, 但对中日韩文字会严重偏窄——"文件" 只有 2 个字符,算出来 16 像素, 文字被挤成一团。

这里优先向 Tk 要真实度量;拿不到字体时退回"宽字符按 2 个计"的估算。

:param master: 任意控件(用于定位 Tk 解释器与字体) :param label: 标签文本 :param padding: 在文本宽度之外额外预留的左右内边距

源代码位于: tkflu\defs.py
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
def measure_label_width(master, label, padding: int = 26) -> int:
    """估算一个菜单/按钮标签需要多宽(像素)。

    历史实现用 ``len(label) * 8``:对 ``"FluMenu1"`` 这类拉丁标签大致够用,
    但对中日韩文字会**严重偏窄**——``"文件"`` 只有 2 个字符,算出来 16 像素,
    文字被挤成一团。

    这里优先向 Tk 要真实度量;拿不到字体时退回"宽字符按 2 个计"的估算。

    :param master: 任意控件(用于定位 Tk 解释器与字体)
    :param label: 标签文本
    :param padding: 在文本宽度之外额外预留的左右内边距
    """
    text = str(label or "")
    if not text:
        return max(24, padding)

    width = 0
    try:
        from tkinter.font import Font

        # 注意是 root= 而不是 master=:tkinter.font.Font 的第一个参数叫 root,
        # 写 master= 会被当成"一个叫 master 的字体选项",直接抛 TclError
        # (以前这里就是这个错,被 except 吞掉之后**永远**走下面的估算分支,
        #  菜单因此比真实需要宽了 1.6~2 倍)。
        font = Font(root=master, family=_DEFAULT_FONT_FAMILY, size=_DEFAULT_FONT_SIZE)
        width = int(font.measure(text))
    except Exception:
        width = 0

    if width <= 0:
        # 拿不到字体度量时的兜底估算
        width = label_cells(text) * _DEFAULT_FONT_SIZE

    return width + padding

renderer_description(name)

取某个引擎的一句话说明(用于 --list-engines 之类的输出)。

:param name: 引擎名;未知名字时返回空字符串

源代码位于: tkflu\designs\renderer.py
235
236
237
238
239
240
241
242
243
def renderer_description(name) -> str:
    """取某个引擎的一句话说明(用于 ``--list-engines`` 之类的输出)。

    :param name: 引擎名;未知名字时返回空字符串
    """
    for row in describe_engines():
        if row["name"] == str(name).lower():
            return row.get("description", "")
    return ""

set_default_font(font, attributes, master=None)

把字体写进 attributes.font,调用方传了就用调用方的。

:param font: 调用方指定的字体;None 表示用库默认的 Segoe UI :param attributes: 组件的属性字典(通常是 self.attributes) :param master: 组件自己(强烈建议传)。字体对象是按 Tk 解释器 注册的:不传 master 时它会挂到 tkinter._default_root 上, 同一进程里第二个 FluWindow 所属的解释器就不认识这个字体名, Tk 会静默退回系统兜底字体(实测"宋体 18"),界面直接走样。

.. note:: 旧实现只在 font is None 时才写 attributes.font, 于是 FluLabel(root, font=("Courier New", 24)) 里的 font 被整个丢掉,attributes.font 保持 None,最终画出来的是 TkDefaultFont——比库默认字体还不对。

源代码位于: tkflu\defs.py
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
def set_default_font(font, attributes, master=None):
    """把字体写进 ``attributes.font``,调用方传了就用调用方的。

    :param font: 调用方指定的字体;``None`` 表示用库默认的 Segoe UI
    :param attributes: 组件的属性字典(通常是 ``self.attributes``)
    :param master: 组件自己(**强烈建议传**)。字体对象是**按 Tk 解释器**
        注册的:不传 master 时它会挂到 ``tkinter._default_root`` 上,
        同一进程里第二个 ``FluWindow`` 所属的解释器就不认识这个字体名,
        Tk 会静默退回系统兜底字体(实测"宋体 18"),界面直接走样。

    .. note::
       旧实现只在 ``font is None`` 时才写 ``attributes.font``,
       于是 ``FluLabel(root, font=("Courier New", 24))`` 里的 ``font``
       被**整个丢掉**,``attributes.font`` 保持 ``None``,最终画出来的是
       ``TkDefaultFont``——比库默认字体还不对。
    """
    if font is not None:
        attributes.font = font
        return

    from .designs.fonts import SegoeFont

    attributes.font = SegoeFont(master=master)

set_renderer(way)

切换渲染引擎。

:param way: 整数编号(0–4)或引擎名("skia" / "pillow" / "cairo" / "tksvg" / "wand")

引擎不可用时抛 ValueError,避免"以为开了 skia 其实还在走慢路径"。 选择会同步给 tkdeft 的引擎注册表,并记在环境变量里以便子进程继承。

源代码位于: tkflu\designs\renderer.py
194
195
196
197
198
199
200
201
202
203
204
def set_renderer(way) -> None:
    """切换渲染引擎。

    :param way: 整数编号(0–4)或引擎名(``"skia"`` / ``"pillow"`` /
        ``"cairo"`` / ``"tksvg"`` / ``"wand"``)

    引擎不可用时抛 ``ValueError``,避免"以为开了 skia 其实还在走慢路径"。
    选择会同步给 tkdeft 的引擎注册表,并记在环境变量里以便子进程继承。
    """
    engine = set_engine(_to_engine_ref(way))
    environ[_ENV_KEY] = str(_INDEX_OF.get(engine.name, 0))

set_theme_easing(name)

设置主题过渡的缓动曲线,取值见 :data:EASINGS。

源代码位于: tkflu\designs\animation.py
113
114
115
def set_theme_easing(name: str) -> None:
    """设置主题过渡的缓动曲线,取值见 :data:`EASINGS`。"""
    environ["tkfluent.theme_easing"] = str(name)

suspended_widget_animation()

在这个上下文里,各组件自己的 _theme() 动画全部让路。

:func:~tkflu.theme_transition.run_theme_transition 用它来"静默地"把 目标配色写进各组件,从而拿到干净的插值起点。

.. note:: 用计数而不是布尔量:统一过渡本身也可能被嵌套调用(窗口 → 面板 → 菜单栏), 布尔量会在内层退出时把外层的暂停提前解除。

源代码位于: tkflu\designs\animation.py
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
@contextmanager
def suspended_widget_animation():
    """在这个上下文里,各组件自己的 ``_theme()`` 动画全部让路。

    :func:`~tkflu.theme_transition.run_theme_transition` 用它来"静默地"把
    目标配色写进各组件,从而拿到干净的插值起点。

    .. note::
       用计数而不是布尔量:统一过渡本身也可能被嵌套调用(窗口 → 面板 → 菜单栏),
       布尔量会在内层退出时把外层的暂停提前解除。
    """
    global _suspension_depth
    _suspension_depth += 1
    try:
        yield
    finally:
        _suspension_depth -= 1

widget_animation_suspended()

当前是否处于"让路"状态。

源代码位于: tkflu\designs\animation.py
149
150
151
def widget_animation_suspended() -> bool:
    """当前是否处于"让路"状态。"""
    return _suspension_depth > 0