跳转至

Release Notes - 版本发布说明#

约 1108 个字 392 行代码 预计阅读时间 34 分钟

Framework - 框架#

  • 🔖 Version - 最新版本 : 3.0.0.beta1
  • 🕓 Last Update - 上次更新 : 2024/05/17
pip install tkintertools==3.0.0b1

Change Things - 更新内容#

🟢 Added - 新增

  • The docstrings for a portion of the code has been added
    添加了一部分代码的文本字符串

  • Added the animation class MoveItem to move items on the canvas
    增加了动画类 MoveItem 来移动画布上的 Item

  • The animation base class Animation adds the initialization parameter derivation to control whether the parameters of the callback function are derived
    动画基类 Animation 增加了初始化参数 derivation 来控制回调函数的参数是否求导

  • The subpackage color adds the module colormap to speed up the conversion of color names to their corresponding RGB codes
    子包 color 增加了模块 colormap 来加速颜色名称到其对应 RGB 码的转换速度

  • The subpackage color adds the functions contrast, convert, blend and gradient to complete the color processing mechanism
    子包 color 新增函数 contrastconvertblendgradient 来完善颜色处理机制的功能

  • The subpackage style adds the module theme to control the overall theme of the application
    子包 style 新增模块 theme 来控制应用程序整体的主题

  • Added method disabled to the widget class to disable it. If a style with a disabled state is defined in the stylesheet, the defined style is used, otherwise the style in the disabled state is automatically generated based on the current style (color to background color conversion by a factor of 0.618)
    小部件类新增方法 disabled 来使其处于禁用状态。若在样式表中定义了禁用状态的样式,则会使用定义的样式,否则根据当前样式自动生成禁用状态的样式(色彩向背景色转换 0.618 倍)

  • The widget RadioButton has a new initialization parameter default to control its default state
    小部件 RadioButton 新增初始化参数 default 来控制其默认的状态

  • Experimental support for color strings in RGBA format has been added to the Color subpackage
    颜色子包新增对 RGBA 格式的颜色字符串的实验性支持

🟣 Fixed - 修复

  • Fixed an bug where the animation classes MoveWidget and MoveComponent were not moving objects to the correct position when they were called repeatedly
    修复了动画类 MoveWidgetMoveComponent 在被反复调用的情况下无法将对象移动到正确位置的问题

  • Fixed an bug where the animation class ScaleFontSize did not scale the font size correctly
    修复了动画类 ScaleFontSize 无法正确缩放字体大小的问题

  • Fixed and enhanced a bug with the centering function of container widgets such as Toplevel
    修复并增强了容器小部件 Toplevel 等在居中功能上的问题

🔵 Optimized - 优化

  • Optimized the way to get the style file, the widget can set a relative name to reduce the amount of code, and the relative name starts with a decimal point
    优化了样式文件的获取方式,小组件可以设置相对名称来减少代码量,相对名称以小数点开头

  • The theme mechanism is optimized, there is no longer a need to write a tag in the style file, and the mapping relationship between the color parameters of the item and the keywords of the style file can be written in the definition of Shape, so as to reduce the redundant content in the style file and improve the compatibility between the style files
    主题机制优化,样式文件中不再需要写出 tag,可在 Shape 的定义中写明 Item 的颜色参数与样式文件关键字的映射关系,以此缩减样式文件中的冗余内容,提高各样式文件之间的兼容性

  • Optimized the appearance of some widgets
    优化部分小部件外观

  • Improved cross-platform compatibility
    提高了跨平台的兼容性

  • Improved 3D submodule compatibility with the new version of tkintertools
    提高了 3D 子模块对新版 tkintertools 兼容性

  • Change the constants FONT and SIZE to dynamic values, so that font modifications can take effect globally
    将常量 FONTSIZE 改成动态取值,便于字体修改可以全局生效

🟡 Changed - 变更

  • The animation class Gradient no longer converts an empty color string to black when it accepts it, but simply throws an exception
    动画类 Gradient 在接受空颜色字符串时不再将其转化为黑色,而是直接抛出异常

  • The implementation code for the 3D subpackage has been moved from file three/__init__.py to file three/engine.py
    3D 子包的实现代码从文件 three/__init__.py 移动到了文件 three/engine.py

  • The submodule style has been changed to the sub-package style and its contents have been reorganized
    子模块 style 变更为子包 style,其内容进行了重新的整理

🔴 Removed - 移除

  • Remove the useless class from the submodule images of the subpackage standard
    移除子包 standard 的子模块 images 中无用的类

  • Remove the function color from the color subpack (There are other better implementations)
    移除颜色子包中的函数 color(已有其他更好的实现)

🟤 Refactored - 重构

  • Some of the code has been refactored
    重构了部分代码

Preview - 预览#

Windows11#

png

png

Windows10#

png

png

macOS#

png

png

WSL#

png

png

Kubuntu#

png

png

Demo Code#

import itertools
import math
import random
import statistics
import webbrowser

import tkintertools as tkt
import tkintertools.animation as animation
import tkintertools.color as color
import tkintertools.constants as constants
import tkintertools.standard.dialogs as dialogs  # Customied
import tkintertools.standard.features as features  # Customied
import tkintertools.standard.shapes as shapes  # Customied
import tkintertools.standard.texts as texts  # Customied
import tkintertools.style as style
import tkintertools.three as three

root = tkt.Tk(title=f"tkintertools {tkt.__version__}")
root.center()
canvas = tkt.Canvas(root, zoom_item=True, keep_ratio="full",
                    free_anchor=True, highlightthickness=1, highlightbackground="grey")
canvas.place(width=1280, height=720, x=640, y=360, anchor="center")
canvas.create_line(880, 0, 880, 800, fill="grey")

# constants.SYSTEM = "Windows10"
# constants.FONT = "霞鹜文楷 屏幕阅读版"

ORIGIN_SYSTEM = constants.SYSTEM

"""
Data Card (RGBA - Experimental)
"""

_l = tkt.Label(canvas, (620, 390), (240, 310), name="")
_l.shapes[0].styles = {"normal": {"fill": "#448AFF33", "outline": "#448AFF"},
                       "hover": {"fill": "#00BFA533", "outline": "#00BFA5"}}
_l.update()

tkt.Information(canvas, (740, 430), text="— RGBA Card —")
tkt.UnderlineButton(canvas, (740, 490), text="Home Page", through=True,
                    command=lambda: webbrowser.open_new_tab("https://xiaokang2022.github.io/tkintertools/"))
tkt.UnderlineButton(canvas, (740, 530), text="GitHub (Source)", through=True,
                    command=lambda: webbrowser.open_new_tab("https://github.com/Xiaokang2022/tkintertools"))
tkt.UnderlineButton(canvas, (740, 570), text="Gitee (Mirror)", through=True,
                    command=lambda: webbrowser.open_new_tab("https://gitee.com/xiaokang-2022/tkintertools"))
tkt.UnderlineButton(canvas, (740, 610), text="GitCode (Mirror)", through=True,
                    command=lambda: webbrowser.open_new_tab("https://gitcode.com/Xiaokang2022/tkintertools"))
tkt.UnderlineButton(canvas, (740, 650), text="Bug Reports", through=True,
                    command=lambda: webbrowser.open_new_tab("https://github.com/Xiaokang2022/tkintertools/issues"))


"""
Here's the customization section
"""


class MyCustomWidget(tkt.Widget):
    """"""

    def __init__(self, *args, **kw) -> None:
        super().__init__(*args, **kw)


class MyToplevel(tkt.Toplevel):
    """My Customized Toplevel"""

    def __init__(self, *args, **kw) -> None:
        constants.SYSTEM = ORIGIN_SYSTEM
        super().__init__(*args, size=(720, 405), **kw)
        self.topmost(True)
        self.center(self.master)
        self.canvas = canvas = tkt.Canvas(self, free_anchor=True, expand="")
        canvas.place(width=720, height=405, x=360, y=202, anchor="center")
        canvas.create_rectangle(40, 40, 120, 120, dash="-", outline="grey")
        canvas.create_text(80, 25, text="Shape", fill="grey")
        self.s = canvas.create_text(80, 80, fill="red", justify="center")
        canvas.create_line(120, 80, 180, 80, 180, 160, 220,
                           160, fill="grey", arrow="last")
        canvas.create_rectangle(40, 160, 120, 240, dash="-", outline="grey")
        canvas.create_text(80, 145, text="Text", fill="grey")
        canvas.create_line(120, 202.5, 220, 202.5, fill="grey", arrow="last")
        canvas.create_rectangle(40, 280, 120, 360, dash="-", outline="grey")
        canvas.create_text(80, 265, text="Image", fill="grey")
        canvas.create_line(120, 320, 180, 320, 180, 240,
                           220, 240, fill="grey", arrow="last")
        canvas.create_rectangle(540, 40, 680, 120, dash="-", outline="grey")
        canvas.create_text(610, 25, text="Feature", fill="grey")
        self.f = canvas.create_text(610, 80, fill="purple")
        canvas.create_line(610, 120, 610, 202.5, 500,
                           202.5, fill="grey", arrow="last")
        canvas.create_rectangle(220, 70, 500, 350, dash=".", outline="red")
        canvas.create_text(360, 40, text="Widget", fill="red")

        self.shape: tkt.Shape = None
        self.text: tkt.Text = None
        self.image: tkt.Image = None
        self.feature: tkt.Feature = None
        self.style: dict = None
        self.widget: tkt.Widget = None

        tkt.Button(canvas, (585, 275), text="Clear", command=self.clear)
        tkt.Button(canvas, (585, 340), text="Generate", command=self.generate)

    def generate(self) -> None:
        """Generate a Widget randomly"""
        self.clear()
        self.shape = random.choice([shapes.Rectangle, shapes.Oval, shapes.Parallelogram, shapes.SharpRectangle,
                                   shapes.RegularPolygon, shapes.RoundedRectangle, shapes.SemicircularRectangle])
        self.feature = random.choice(
            [features.Label, features.Button, features.UnderLine, features.Highlight])
        self.text = texts.Information
        self.widget = tkt.Widget(self.canvas, (300, 162), (120, 80))
        kw = {}
        match self.shape:
            case shapes.RegularPolygon: kw["side"] = random.randint(3, 9)
            case shapes.Parallelogram: kw["theta"] = random.uniform(-math.pi/6, math.pi/3)
            case shapes.RoundedRectangle: kw["radius"] = random.randint(4, 25)
            case shapes.SharpRectangle:
                kw["theta"] = random.uniform(math.pi/6, math.pi/3)
                kw["ratio"] = random.uniform(0, 1), random.uniform(0, 1)
        try:
            _s = self.shape(self.widget, **kw)
            self.shape = self.shape(self.widget, (-280, -120), **kw)
            _s.styles = self.shape.styles = {
                "normal": {"fill": self.randcolor(), "outline": self.randcolor()},
                "hover": {"fill": self.randcolor(), "outline": self.randcolor()},
                "active": {"fill": self.randcolor(), "outline": self.randcolor()},
            }
        except:
            self.canvas.itemconfigure(self.s, text="Param\nError")
        _t = self.text(self.widget, text="TKT")
        self.text = self.text(self.widget, (-280, 0), text="TKT")
        _t.styles = self.text.styles = {
            "normal": {"fill": self.randcolor()},
            "hover": {"fill": self.randcolor()},
            "active": {"fill": self.randcolor()},
        }
        self.canvas.itemconfigure(self.f, text=self.feature.__name__)
        self.feature = self.feature(self.widget)
        self.widget.update()

    def randcolor(self) -> str:
        """Get a random color string"""
        num = random.randint(0, (1 << 24) - 1)
        return f"#{num:06X}"

    def clear(self) -> None:
        """Clear Widget"""
        if self.widget is not None:
            self.widget.destroy()
            self.widget = None
        self.canvas.itemconfigure(self.s, text="")
        self.canvas.itemconfigure(self.f, text="")


"""
Below is the section of the style following system
"""

tkt.Switch(canvas, (50, 35), command=lambda b: style.use_theme(
    "dark" if b else "light"), default=style.DARK_MODE)


i = canvas.create_text(
    440, 50, text="tkintertools 3: a Brand New UI Framework", font=26)

tkt.HighlightButton(canvas, (790, 50), text="Get More!", command=MyToplevel)

tkt.Button(canvas, (900, 405), (360, 50),
           text="Call a Nested Window", command=tkt.NestedToplevel)
tkt.Button(canvas, (900, 465), (360, 50),
           text="Call a Font Chooser", command=dialogs.FontChooser)
tkt.Button(canvas, (900, 525), (360, 50),
           text="Call a Color Chooser", command=dialogs.ColorChooser)

info = tkt.Button(canvas, (900, 585), (175, 50), name="",
                  text="Info", command=lambda: dialogs.Message(icon="info"))
info.shapes[0].styles = {"normal": {"fill": "skyblue", "outline": "grey"}}
info.update()
question = tkt.Button(canvas, (900 + 185, 585), (175, 50), name="",
                      text="Question", command=lambda: dialogs.Message(icon="question"))
question.shapes[0].styles = {"normal": {
    "fill": "lightgreen", "outline": "grey"}}
question.update()
warning = tkt.Button(canvas, (900, 645), (175, 50), name="",
                     text="Warning", command=lambda: dialogs.Message(icon="warning"))
warning.shapes[0].styles = {"normal": {"fill": "orange", "outline": "grey"}}
warning.update()
error = tkt.Button(canvas, (900 + 185, 645), (175, 50), name="",
                   text="Error", command=lambda: dialogs.Message(icon="error"))
error.shapes[0].styles = {"normal": {"fill": "red", "outline": "grey"}}
error.update()


random_color = f"#{random.randint(0, (1 << 24) - 1):06X}"
contast_color = color.rgb_to_str(
    color.contrast(color.str_to_rgb(random_color)))


animation.Gradient(canvas, i, "fill", 2000, (random_color, contast_color),
                   repeat=-1, controller=lambda x: math.sin(x*math.pi)).start()


"""
Here's the part for customizing the system
"""


constants.SYSTEM = "Windows11"

tkt.Label(canvas, (50, 100), (120, 50), text="Label")
tkt.Label(canvas, (180, 100), (120, 50), text="Label").disabled()
l = tkt.Label(canvas, (310, 100), (120, 50), text="Label", name="")
l.shapes[0].styles = {"normal": {"fill": "", "outline": "#5E8BDE"},
                      "hover": {"fill": "", "outline": "#FFAC33"}}
l.texts[0].styles = {"normal": {"fill": "#5E8BDE"},
                     "hover": {"fill": "#FFAC33"}}
l.update()

tkt.Button(canvas, (50, 180), (120, 50), text="Button")
tkt.Button(canvas, (180, 180), (120, 50), text="Button").disabled()
b = tkt.Button(canvas, (310, 180), (120, 50), text="Button", name="")
b.shapes[0].styles = {"normal": {"fill": "#5E8BDE", "outline": "#5E8BDE"},
                      "hover": {"fill": "#CCCC00", "outline": "#CCCC00"},
                      "active": {"fill": "#FFAC33", "outline": "#FFAC33"}}
b.update()

pb1 = tkt.ProgressBar(canvas, (50, 260), (380, 8))
pb2 = tkt.ProgressBar(canvas, (50, 280), (380, 8), name="")

pb2.shapes[0].styles = {"normal": {"fill": "", "outline": ""}}
pb2.shapes[1].styles = {"normal": {"fill": "gold", "outline": "gold"}}
pb2.shapes[1].disappear()
pb2.update()

animation.Animation(2000, animation.smooth, callback=pb1.set,
                    fps=60, repeat=math.inf).start(delay=1500)
animation.Animation(2000, animation.smooth, callback=pb2.set,
                    fps=60, repeat=math.inf).start(delay=1000)

pb3 = tkt.ProgressBar(canvas, (50, 315), (380, 20))
pb4 = tkt.ProgressBar(canvas, (50, 350), (380, 20), name="")

pb4.shapes[0].styles = {"normal": {"fill": "", "outline": "grey"}}
pb4.shapes[1].styles = {"normal": {"fill": "pink", "outline": "pink"}}
pb4.shapes[1].disappear()
pb4.update()

animation.Animation(2000, animation.smooth, callback=pb3.set,
                    fps=60, repeat=math.inf).start(delay=500)
animation.Animation(2000, animation.smooth, callback=pb4.set,
                    fps=60, repeat=math.inf).start()

tkt.CheckButton(canvas, (50, 390))
tkt.Information(canvas, (165, 390 + 15), text="CheckButton")
tkt.RadioButton(canvas, (250, 390 + 3))
tkt.Information(canvas, (355, 390 + 15), text="RadioButton")
tkt.Information(canvas, (460, 390 + 15), text="Off")
tkt.Switch(canvas, (490, 390))
tkt.Information(canvas, (580, 390 + 15), text="On")

tkt.CheckButton(canvas, (50, 440), default=True).disabled()
tkt.Information(canvas, (165, 440 + 15), text="CheckButton").disabled()
tkt.RadioButton(canvas, (250, 440 + 3), default=True).disabled()
tkt.Information(canvas, (355, 440 + 15), text="RadioButton").disabled()
tkt.Information(canvas, (460, 440 + 15), text="Off").disabled()
tkt.Switch(canvas, (490, 440), default=True).disabled()
tkt.Information(canvas, (580, 440 + 15), text="On").disabled()

tkt.Entry(canvas, (50, 595 - 5), (270, 50))
e = tkt.Entry(canvas, (50, 655 - 5), (270, 50))
e.set("Entry")
e.disabled()

constants.SYSTEM = "Windows10"

tkt.Label(canvas, (50 + 410, 100), (120, 50), text="Label")
tkt.Label(canvas, (180 + 410, 100), (120, 50), text="Label").disabled()
tkt.Label(canvas, (310 + 410, 100), (120, 50), text="Label", name="")

tkt.Button(canvas, (50 + 410, 180), (120, 50), text="Button")
tkt.Button(canvas, (180 + 410, 180), (120, 50), text="Button").disabled()
b2 = tkt.Button(canvas, (310 + 410, 180), (120, 50), text="Button", name="")

b2.shapes[0].styles = {"normal": {"fill": "", "outline": ""},
                       "hover": {"fill": "yellow", "outline": "red"}}
b2.texts[0].styles = {"normal": {"fill": ""},
                      "hover": {"fill": "black"}}
b2.update()

pb5 = tkt.ProgressBar(canvas, (50 + 410, 260), (380, 8))
pb6 = tkt.ProgressBar(canvas, (50 + 410, 280), (380, 8), name="")

pb6.shapes[0].styles = {"normal": {"fill": "orange", "outline": "orange"}}
pb6.shapes[1].styles = {"normal": {"fill": "red", "outline": "red"}}
pb6.shapes[1].disappear()
pb6.update()

animation.Animation(2000, animation.flat, callback=pb5.set,
                    fps=60, repeat=math.inf).start()
animation.Animation(2000, animation.flat, callback=pb6.set,
                    fps=60, repeat=math.inf).start(delay=500)

pb7 = tkt.ProgressBar(canvas, (50 + 410, 315), (380, 20))
pb8 = tkt.ProgressBar(canvas, (50 + 410, 350), (380, 20), name="")

pb8.shapes[0].styles = {"normal": {"fill": "", "outline": ""}}
pb8.shapes[1].styles = {"normal": {"fill": "purple", "outline": "cyan"}}
pb8.shapes[1].disappear()
pb8.update()

animation.Animation(2000, animation.flat, callback=pb7.set,
                    fps=60, repeat=math.inf).start(delay=1000)
animation.Animation(2000, animation.flat, callback=pb8.set,
                    fps=60, repeat=math.inf).start(delay=1500)

tkt.CheckButton(canvas, (50, 490))
tkt.Information(canvas, (165, 490 + 15), text="CheckButton")
tkt.RadioButton(canvas, (250, 490 + 3))
tkt.Information(canvas, (355, 490 + 15), text="RadioButton")
tkt.Information(canvas, (460, 490 + 15), text="Off")
tkt.Switch(canvas, (490, 490))
tkt.Information(canvas, (580, 490 + 15), text="On")

tkt.CheckButton(canvas, (50, 540)).disabled()
tkt.Information(canvas, (165, 540 + 15), text="CheckButton").disabled()
tkt.RadioButton(canvas, (250, 540 + 3)).disabled()
tkt.Information(canvas, (355, 540 + 15), text="RadioButton").disabled()
tkt.Information(canvas, (460, 540 + 15), text="Off").disabled()
tkt.Switch(canvas, (490, 540)).disabled()
tkt.Information(canvas, (580, 540 + 15), text="On").disabled()

tkt.Entry(canvas, (50 + 280, 595 - 5), (270, 50))
tkt.Entry(canvas, (50 + 280, 655 - 5), (270, 50)).disabled()

"""
Here's the 3D part
"""


space = three.Space(canvas, free_anchor=True, zoom_item=True,
                    highlightthickness=1, highlightbackground="grey")
space.place(width=360, height=360, x=900, y=20)
space.update()

m = 150 * math.sqrt(50 - 10 * math.sqrt(5)) / 10
n = 150 * math.sqrt(50 + 10 * math.sqrt(5)) / 10
points = []
dis_side = 150 * (3 * math.sqrt(3) + math.sqrt(15)) / 12 / \
    ((math.sqrt(10 + 2 * math.sqrt(5))) / 4)
count, color_lst = 0, ['00', '77', 'FF']
colors = [f'#{r}{g}{b}' for r in color_lst for g in color_lst for b in color_lst]

for i in m, -m:
    for j in n, -n:
        points.append([0, j, i])
        points.append([i, 0, j])
        points.append([j, i, 0])

for p in itertools.combinations(points, 3):
    dis = math.hypot(*[statistics.mean(c[i] for c in p) for i in range(3)])
    if math.isclose(dis, dis_side):
        three.Side(space, *p, fill=colors[count], outline='grey')
        count += 1


space.space_sort()


count = 0


def _callback(_: float) -> None:
    """callback function of animation"""
    global count
    count += 0.08
    for item in space.items_3d():
        item.rotate(dy=-0.01, dz=0.02)
        item.translate(dz=math.sin(count))
        item.update()
    space.space_sort()


an = animation.Animation(2000, animation.flat, callback=_callback,
                         fps=60, repeat=-1, derivation=True)


tkt.Switch(space, (10, 10), command=lambda flag: an.start()
           if flag else an.stop())

root.mainloop()

欢迎在评论区留下你的想法


下面没有更多评论啦😆