tkintertools.py 35.2 KB
Newer Older
小康2022's avatar
小康2022 已提交
1 2 3 4 5 6 7
"""
## tkintertools
tkinter 模块的扩展模块

这个模块将给用户提供可透明的、可自定义的、现代化的控件,以及一些特殊的功能函数

模块作者: 小康2022
小康2022's avatar
小康2022 已提交
8 9
模块版本: pre-2.2
上次更新: 2022/10/18
小康2022's avatar
小康2022 已提交
10 11 12
---
### 可用内容
- 容器类控件: `Tk`、`Canvas`
小康2022's avatar
小康2022 已提交
13
- 工具类: `PhotoImage`
小康2022's avatar
小康2022 已提交
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
- 虚拟画布类控件: `CanvasLabel`、`CanvasButton`、`CanvasEntry`、`CanvasText`
- 处理函数: `move_widget`、`correct_text`、`process_color`
---
### 当前功能
    1. 可以使控件内部透明,以衬托出背景图片
    2. 可以自定义控件的各个细节的颜色,颜色为空字符时透明
    3. 不修改颜色时,默认值为现代的新式控件颜色(具体效果见测试函数)
    4. 提供了一些方便的文本处理函数
    5. 实现了使用 Place 几何布局时也能使控件扩展(随窗口大小变化),当然,也可以设置为不扩展
---
### 更新内容
- 修复了文本框无法输入空格的问题
- 修复了文本框光标移动显示错误的问题
- 增加了测试函数
---
##### 更多内容见: https://xiaokang2022.blog.csdn.net
小康2022's avatar
小康2022 已提交
30 31 32 33
"""


import random
小康2022's avatar
小康2022 已提交
34
import tkinter
小康2022's avatar
小康2022 已提交
35
import types
小康2022's avatar
小康2022 已提交
36 37
import typing

小康2022's avatar
小康2022 已提交
38
__all__ = ['Tk', 'Canvas',
小康2022's avatar
小康2022 已提交
39 40 41
           'CanvasLabel', 'CanvasButton',
           'CanvasEntry', 'CanvasText',
           'move_widget', 'correct_text', 'process_color']
小康2022's avatar
小康2022 已提交
42 43


小康2022's avatar
小康2022 已提交
44
# 常量
小康2022's avatar
小康2022 已提交
45 46


小康2022's avatar
小康2022 已提交
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
# 默认的文本前景颜色
COLOR_BLACK = ('#000000', '#000000', '#000000')
# 默认文本背景颜色
COLOR_WHITE = ('#FFFFFF', '#FFFFFF', '#FFFFFF')
# 默认的内部颜色
COLOR_FILL = ('#E1E1E1', '#E5F1FB', '#CCE4F7')
# 默认按钮外框颜色
COLOR_BUTTON = ('#C0C0C0', '#4A9EE0', '#4884B4')
# 默认文本外框颜色
COLOR_TEXT = ('#C0C0C0', '#5C5C5C', '#4A9EE0')
# 默认字体
FONT = ('楷体', 15)
# 默认控件外框宽度
BORDERWIDTH = 1
# 默认控件显示文本
TEXT = ''
小康2022's avatar
小康2022 已提交
63 64


小康2022's avatar
小康2022 已提交
65 66 67 68
# 容器控件


class Canvas(tkinter.Canvas):
小康2022's avatar
小康2022 已提交
69 70 71 72 73
    """
    画布类
    
    用于承载虚拟的画布控件
    """
小康2022's avatar
小康2022 已提交
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91

    def __init__(self,
                 master,  # type: Tk
                 width: int,
                 height: int,
                 lock: bool = True,
                 expand: bool = True,
                 **kwargs) -> None:
        """
        ### 参数说明
        `master`: 父控件
        `width`: 画布宽度
        `height`: 画布高度
        `lock`: 画布内控件的功能锁(False 时没有功能)
        `expand`: 画布内控件是否能缩放
        `**kwargs`: 与原 tkinter 模块内 Canvas 类的参数相同
        """
        tkinter.Canvas.__init__(self, master,
小康2022's avatar
小康2022 已提交
92 93
                                width=width, height=height,
                                highlightthickness=0, **kwargs)
小康2022's avatar
小康2022 已提交
94 95 96 97 98 99 100 101 102 103
        self.master: Tk
        self.master.canvas_list.append(self)  # 将实例添加到 Tk 的画布列表中
        self.widget_list: list[CanvasButton | CanvasEntry |
                               CanvasLabel | CanvasText] = []  # 子控件列表(与绑定有关)

        self.lock = lock
        self.width = width
        self.height = height
        self.expand = expand

小康2022's avatar
小康2022 已提交
104 105 106 107 108
        # 放缩比率
        self.rate_x = 1
        self.rate_y = 1


小康2022's avatar
小康2022 已提交
109
class Tk(tkinter.Tk):
小康2022's avatar
小康2022 已提交
110 111 112 113 114
    """
    Tk类

    用于集中处理 `Canvas` 绑定的关联事件以及缩放操作
    """
小康2022's avatar
小康2022 已提交
115

小康2022's avatar
小康2022 已提交
116 117 118 119 120 121 122 123 124 125 126 127 128 129
    def __init__(self,
                 title: str | None = None,
                 geometry: str | None = None,
                 minisize: tuple[int, int] | None = None,
                 proportion_lock: bool = False,
                 *args, **kwargs) -> None:
        """
        ### 参数说明
        `title`: 窗口标题
        `geometry`: 窗口大小及位置(格式:'宽度x高度+左上角横坐标+左上角纵坐标' 或者 '宽度x高度')
        `minisize`: 窗口的最小缩放大小(默认为参数 geometry 的宽度与高度)
        `proportion_lock`: 窗口缩放是否保持原比例
        `*args`, `**kwargs`: 与原 tkinter 模块中的 Tk 类的参数相同
        """
小康2022's avatar
小康2022 已提交
130
        tkinter.Tk.__init__(self, *args, **kwargs)
小康2022's avatar
小康2022 已提交
131 132 133 134 135 136 137 138 139 140 141

        if title:
            self.title(title)
        if geometry:
            self.geometry(geometry)
            args = geometry.replace('x', '+').split('+')  # NOTE: BUG

        if minisize:
            self.minsize(*minisize)

        self.canvas_list: list[Canvas] = []  # 子画布列表(与缩放绑定有关)
小康2022's avatar
小康2022 已提交
142 143 144 145
        # 开启窗口缩放检测
        self.bind('<Configure>', lambda event: self.__zoom(
            event, proportion_lock))

小康2022's avatar
小康2022 已提交
146
    def __zoom(self, event: tkinter.Event, lock: bool, geometry: list = []) -> None:  # NOTE: 可改进
小康2022's avatar
小康2022 已提交
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
        """ 画布缩放检测 """
        if not geometry:
            # 记住初始化窗口宽高比例
            geometry.append(event.width / event.height)
            # 记住初始化的窗口大小
            geometry.append(event.width)
            geometry.append(event.height)

        elif event.width != geometry[1] or event.height != geometry[2]:
            # 窗口大小改变
            if lock:
                # 使高度和宽度成比例同步变化
                delta = event.width / geometry[0] - event.height
                if event.width not in (geometry[1], self.winfo_screenwidth()):
                    # 宽度改变
                    event.height += round(delta)
                elif event.height not in (geometry[2], self.winfo_screenheight()):
                    # 高度改变
                    event.width -= round(delta * geometry[0])
                self.geometry('%sx%s' % (event.width, event.height))

            # 计算横向缩放倍率
            rate_x = event.width / geometry[1]
            # 计算纵向缩放倍率
            rate_y = event.height / geometry[2]

            # 更新子画布控件的大小
            for canvas in self.canvas_list:
小康2022's avatar
小康2022 已提交
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196
                if canvas.expand:
                    # 更新画布的横纵缩放比率
                    canvas.rate_x = event.width / canvas.width
                    canvas.rate_y = event.height / canvas.height
                    # 更新子画布控件的子虚拟画布控件的位置
                    for item in canvas.find_all():
                        canvas.coords(item, [coord * rate_y if ind % 2 else coord * rate_x for ind, coord in
                                             enumerate(canvas.coords(item))])
                        size: str = canvas.itemcget(item, 'tags')
                        if size.isdigit():
                            # 字体大小修改
                            font: str = canvas.itemcget(item, 'font')
                            font = font.split()
                            font[1] = round(int(size) * canvas.rate_x)
                            canvas.itemconfigure(item, font=font)

                    # 更新子画布控件的子虚拟画布控件位置数据
                    for widget in canvas.widget_list:
                        widget.x1 *= rate_x
                        widget.x2 *= rate_x
                        widget.y1 *= rate_y
                        widget.y2 *= rate_y
小康2022's avatar
小康2022 已提交
197 198 199

            # 更新默认参数
            geometry[1], geometry[2] = event.width, event.height
小康2022's avatar
小康2022 已提交
200 201

    @staticmethod
小康2022's avatar
小康2022 已提交
202 203 204 205 206
    def __touch(event: tkinter.Event, canvas: Canvas) -> None:
        """ 绑定鼠标触碰控件事件 """
        if canvas.lock:
            for widget in canvas.widget_list:
                widget.touch(event)
小康2022's avatar
小康2022 已提交
207 208

    @staticmethod
小康2022's avatar
小康2022 已提交
209 210 211 212 213 214
    def __press(event: tkinter.Event, canvas: Canvas) -> None:
        """ 绑定鼠标左键按下事件 """
        if canvas.lock:
            for widget in canvas.widget_list:
                if isinstance(widget, CanvasButton | CanvasEntry | CanvasText):
                    widget.press(event)
小康2022's avatar
小康2022 已提交
215 216

    @staticmethod
小康2022's avatar
小康2022 已提交
217 218 219 220 221 222 223
    def __release(event: tkinter.Event, canvas: Canvas) -> None:
        """ 绑定鼠标左键松开事件 """
        if canvas.lock:
            for widget in canvas.widget_list:
                if isinstance(widget, CanvasButton):
                    widget.execute(event)
                    widget.touch(event)
小康2022's avatar
小康2022 已提交
224 225

    @staticmethod
小康2022's avatar
小康2022 已提交
226 227 228
    def __mousewheel(event: tkinter.Event, canvas: Canvas) -> None:
        """ 绑定鼠标滚轮滚动事件 """
        if canvas.lock:
小康2022's avatar
小康2022 已提交
229
            for widget in canvas.widget_list:
小康2022's avatar
小康2022 已提交
230 231
                if isinstance(widget, CanvasText):
                    widget.scroll(event)
小康2022's avatar
小康2022 已提交
232

小康2022's avatar
小康2022 已提交
233 234 235 236 237 238 239 240 241 242
    def __input(self, event: tkinter.Event) -> None:
        """ 绑定键盘输入字符事件 """
        for canvas in self.canvas_list:
            if canvas.lock:
                for widget in canvas.widget_list:
                    if isinstance(widget, _TextWidget):
                        widget.input(event)
                break

    def __bind(self) -> None:
小康2022's avatar
小康2022 已提交
243
        """ 关联事件的绑定 """
小康2022's avatar
小康2022 已提交
244 245 246
        # 绑定键盘输入字符
        self.bind('<Any-Key>', self.__input)

小康2022's avatar
小康2022 已提交
247
        for canvas in self.canvas_list:
小康2022's avatar
小康2022 已提交
248
            # 绑定鼠标触碰控件
小康2022's avatar
小康2022 已提交
249
            canvas.bind('<Motion>', lambda event,
小康2022's avatar
小康2022 已提交
250
                        _canvas=canvas: self.__touch(event, _canvas))
小康2022's avatar
小康2022 已提交
251
            # 绑定鼠标左键按下
小康2022's avatar
小康2022 已提交
252
            canvas.bind('<Button-1>', lambda event,
小康2022's avatar
小康2022 已提交
253
                        _canvas=canvas: self.__press(event, _canvas))
小康2022's avatar
小康2022 已提交
254
            # 绑定鼠标左键松开
小康2022's avatar
小康2022 已提交
255
            canvas.bind('<ButtonRelease-1>', lambda event,
小康2022's avatar
小康2022 已提交
256
                        _canvas=canvas: self.__release(event, _canvas))
小康2022's avatar
小康2022 已提交
257
            # 绑定鼠标左键按下移动
小康2022's avatar
小康2022 已提交
258
            canvas.bind('<B1-Motion>', lambda event,
小康2022's avatar
小康2022 已提交
259
                        _canvas=canvas: self.__press(event, _canvas))
小康2022's avatar
小康2022 已提交
260
            # 绑定鼠标滚轮滚动
小康2022's avatar
小康2022 已提交
261
            canvas.bind('<MouseWheel>', lambda event,
小康2022's avatar
小康2022 已提交
262
                        _canvas=canvas: self.__mousewheel(event, _canvas))
小康2022's avatar
小康2022 已提交
263

小康2022's avatar
小康2022 已提交
264 265 266 267
    def mainloop(self) -> None:
        """ 调用 Tk 的主循环 """
        self.__bind()
        tkinter.Tk.mainloop(self)
小康2022's avatar
小康2022 已提交
268 269


小康2022's avatar
小康2022 已提交
270
# 控件基类
小康2022's avatar
小康2022 已提交
271 272


小康2022's avatar
小康2022 已提交
273 274
class _BaseWidget:
    """ 内部类 """
小康2022's avatar
小康2022 已提交
275 276

    def __init__(self,
小康2022's avatar
小康2022 已提交
277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316
                 canvas: Canvas,
                 x: float,
                 y: float,
                 width: float,
                 height: float,
                 text: str,
                 justify: str,
                 borderwidth: float,
                 font: tuple[str, int, str],
                 color_text: tuple[str, str, str],
                 color_fill: tuple[str, str, str],
                 color_outline: tuple[str, str, str]) -> None:
        """
        ### 标准参数
        `canvas`: 父画布容器控件
        `x`, `y`: 控件左上角的横纵坐标
        `width`, `height`: 控件的宽度与高度
        `text`: 控件显示的文本
        `justify`: 文本的对齐方式
        `borderwidth`: 外框的宽度
        `font`: 控件的字体设定 (字体, 大小, 样式)
        `color_text`: 控件文本的颜色
        `color_fill`: 控件内部的颜色
        `color_outline`: 控件外框的颜色
        ---
        ### 特定参数
        `command`: 按钮控件的关联函数
        `show`: 文本控件的显示文本
        `limit`: 文本控件的输入字数限制
        `space`: 文本控件能否输入空格的标识
        `read`: 文本控件的只读模式
        ---
        ### 详细说明
        字体的值为一个三元组 
        例如: `('微软雅黑', 15, 'bold') `

        颜色为一个包含三个 RGB 颜色字符串的元组 
        详细: `(正常颜色, 触碰颜色, 交互颜色)`
        """
        # 将实例添加到父画布控件
小康2022's avatar
小康2022 已提交
317
        canvas.widget_list.append(self)
小康2022's avatar
小康2022 已提交
318 319
        # 控件活跃标志
        self.live = True
小康2022's avatar
小康2022 已提交
320

小康2022's avatar
小康2022 已提交
321
        self.master = canvas
小康2022's avatar
小康2022 已提交
322
        self.value = text
小康2022's avatar
小康2022 已提交
323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351
        self.font = font
        self.color_text = color_text
        self.color_fill = color_fill
        self.color_outline = color_outline

        self.x1, self.y1 = x, y  # 控件左上角坐标
        self.x2, self.y2 = x + width, y + height  # 控件左下角坐标

        # 虚拟控件的外框
        self.rect = canvas.create_rectangle(x, y, x + width, y + height,
                                            width=borderwidth,
                                            outline=color_outline[0],
                                            fill=color_fill[0])
        # 虚拟控件显示的文字
        self.text = canvas.create_text(x + width / 2, y + height / 2,
                                       text=text,
                                       font=font,
                                       justify=justify,
                                       fill=color_text[0],
                                       tags=str(font[1]))

    def state(self, mode: typing.Literal['normal', 'touch', 'press']) -> None:
        """
        改变虚拟控件的状态
        `normal`: 正常状态
        `touch`: 鼠标经过时的状态
        `press`: 鼠标按下时的状态
        """
        mode = 0 if mode == 'normal' else 1 if mode == 'touch' else 2
小康2022's avatar
小康2022 已提交
352 353 354
        self.master.itemconfigure(self.text, fill=self.color_text[mode])
        self.master.itemconfigure(self.rect, fill=self.color_fill[mode])
        self.master.itemconfigure(self.rect, outline=self.color_outline[mode])
小康2022's avatar
小康2022 已提交
355 356

    def move(self, dx: float, dy: float) -> None:  # NOTE: 可改进
小康2022's avatar
小康2022 已提交
357 358 359 360 361 362
        """ 改变控件的位置 """
        self.x1 += dx
        self.x2 += dx
        self.y1 += dy
        self.y2 += dy

小康2022's avatar
小康2022 已提交
363 364
        self.master.move(self.rect, dx, dy)
        self.master.move(self.text, dx, dy)
小康2022's avatar
小康2022 已提交
365

小康2022's avatar
小康2022 已提交
366 367
        if hasattr(self, 'cursor'):
            self.cursor: tkinter._CanvasItemId
小康2022's avatar
小康2022 已提交
368
            self.master.move(self.cursor, dx, dy)
小康2022's avatar
小康2022 已提交
369

小康2022's avatar
小康2022 已提交
370 371 372 373
    def coords(self, x: int, y: int, width: int, height: int) -> None:
        """ 重设控件位置 """
        self.x1, self.y1 = x, y
        self.x2, self.y2 = x + width, y + height
小康2022's avatar
小康2022 已提交
374

小康2022's avatar
小康2022 已提交
375 376
        self.master.coords(self.rect, x, y, x + width, y + height)
        self.master.coords(self.text, x + width / 2, y + height / 2)
小康2022's avatar
小康2022 已提交
377

小康2022's avatar
小康2022 已提交
378 379 380
    def configure(self, **kwargs) -> None:
        """
        改变原有参数的值
小康2022's avatar
小康2022 已提交
381

小康2022's avatar
小康2022 已提交
382 383 384 385 386 387 388 389 390 391
        可供修改的参数有 `text`、`color_text`、`color_fill` 及 `color_outline`
        """
        if value := kwargs.get('text', self.value):
            self.value = value
        if text := kwargs.get('color_text', self.color_text):
            self.color_text = text
        if fill := kwargs.get('color_fill', self.color_fill):
            self.color_fill = fill
        if outline := kwargs.get('color_outline', self.color_outline):
            self.color_outline = outline
小康2022's avatar
小康2022 已提交
392

小康2022's avatar
小康2022 已提交
393 394
        self.master.itemconfigure(self.text, text=value, fill=text[0])
        self.master.itemconfigure(self.rect, fill=fill[0], outline=outline[0])
小康2022's avatar
小康2022 已提交
395

小康2022's avatar
小康2022 已提交
396 397 398 399
    def destroy(self) -> None:
        """ 摧毁控件释放内存 """
        if hasattr(self, 'live'):
            self.live = False
小康2022's avatar
小康2022 已提交
400 401
        self.master.delete(self.rect)
        self.master.delete(self.text)
小康2022's avatar
小康2022 已提交
402 403


小康2022's avatar
小康2022 已提交
404 405
class _TextWidget(_BaseWidget):
    """ 内部类 """
小康2022's avatar
小康2022 已提交
406

小康2022's avatar
小康2022 已提交
407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448
    def __init__(self,
                 canvas: Canvas,
                 x: int,
                 y: int,
                 width: int,
                 height: int,
                 text: tuple[str] | str,
                 limit: int,
                 space: bool,
                 justify: str,
                 borderwidth: int,
                 font: tuple[str, int, str],
                 color_text: tuple[str, str, str],
                 color_fill: tuple[str, str, str],
                 color_outline: tuple[str, str, str]) -> None:
        _BaseWidget.__init__(self, canvas, x, y, width, height, '', justify,
                             borderwidth, font, color_text, color_fill, color_outline)
        if type(text) == tuple:
            self.value_normal, self.value_touch = text
        else:
            self.value_normal = text
        # 表面显示值
        self.value_surface = ''
        # 鼠标左键按下标志
        self._press = False
        # 鼠标光标
        self.cursor = canvas.create_text(x+width/2, y+height/2,
                                         font=FONT, tags=str(FONT[1]),
                                         fill=color_text[2])
        # 光标闪烁间隔
        self.cursor_time = 300
        # 光标闪烁标志
        self._cursor = False

        self.limit = limit
        self.space = space

    def press_on(self) -> None:
        """ 控件获得焦点 """
        if not getattr(self, 'read', None):
            self._press = True
            self.state('press')
小康2022's avatar
小康2022 已提交
449
            self.master.itemconfigure(self.text, text=self.value_surface)
小康2022's avatar
小康2022 已提交
450
            self.cursor_flash()
小康2022's avatar
小康2022 已提交
451

小康2022's avatar
小康2022 已提交
452 453 454 455 456
    def press_off(self) -> None:
        """ 控件失去焦点 """
        if not getattr(self, 'read', None):
            self._press = False
            self.state('normal')
小康2022's avatar
小康2022 已提交
457
            self.master.itemconfigure(self.text, text=self.value_surface)
小康2022's avatar
小康2022 已提交
458

小康2022's avatar
小康2022 已提交
459 460
    def press(self, event: tkinter.Event) -> None:
        """ 交互状态检测 """
小康2022's avatar
小康2022 已提交
461
        if self.master.lock:
小康2022's avatar
小康2022 已提交
462 463 464 465 466 467 468 469 470
            if self.x1 <= event.x <= self.x2 and self.y1 <= event.y <= self.y2:
                if not self._press:
                    self.press_on()
            else:
                self.press_off()

    def touch(self,  # type: CanvasEntry | CanvasText
              event: tkinter.Event) -> None:  # NOTE: 可改进
        """ 触碰状态检测 """
小康2022's avatar
小康2022 已提交
471
        if self.master.lock:
小康2022's avatar
小康2022 已提交
472 473 474 475 476 477 478 479 480 481
            if self.x1 <= event.x <= self.x2 and self.y1 <= event.y <= self.y2:
                self.touch_on()
            else:
                self.touch_off()

    def cursor_flash(self) -> None:
        """ 鼠标光标闪烁 """
        if self.cursor_time >= 300:
            self.cursor_time, self._cursor = 0, not self._cursor
            if self._cursor:
小康2022's avatar
小康2022 已提交
482
                if self.master.itemcget(self.text, 'justify') == tkinter.CENTER:
小康2022's avatar
小康2022 已提交
483
                    # 居中的文本
小康2022's avatar
小康2022 已提交
484
                    self.master.itemconfigure(
小康2022's avatar
小康2022 已提交
485
                        self.cursor, text=self.__text(self.value) + '│')
小康2022's avatar
小康2022 已提交
486
                elif self.master.itemcget(self.text, 'justify') == tkinter.LEFT:
小康2022's avatar
小康2022 已提交
487
                    # 靠左的文本
小康2022's avatar
小康2022 已提交
488
                    self.master.itemconfigure(
小康2022's avatar
小康2022 已提交
489 490
                        self.cursor, text=self.__text(self.value_surface) + '│')
            else:
小康2022's avatar
小康2022 已提交
491
                self.master.itemconfigure(self.cursor, text='')
小康2022's avatar
小康2022 已提交
492

小康2022's avatar
小康2022 已提交
493 494
        if self._press:
            self.cursor_time += 10
小康2022's avatar
小康2022 已提交
495
            self.master.after(10, self.cursor_flash)
小康2022's avatar
小康2022 已提交
496 497
        else:
            self.cursor_time, self._cursor = 300, False  # 恢复默认值
小康2022's avatar
小康2022 已提交
498
            self.master.itemconfigure(self.cursor, text='')
小康2022's avatar
小康2022 已提交
499 500 501 502

    def cursor_update(self) -> None:
        """ 鼠标光标更新 """
        self.cursor_time, self._cursor = 300, False  # 恢复默认值
小康2022's avatar
小康2022 已提交
503
        if self.master.itemcget(self.text, 'justify') == tkinter.CENTER:
小康2022's avatar
小康2022 已提交
504
            # 居中的文本
小康2022's avatar
小康2022 已提交
505
            self.master.itemconfigure(
小康2022's avatar
小康2022 已提交
506
                self.cursor, text=self.__text(self.value) + '│')
小康2022's avatar
小康2022 已提交
507
        elif self.master.itemcget(self.text, 'justify') == tkinter.LEFT:
小康2022's avatar
小康2022 已提交
508
            # 靠左的文本
小康2022's avatar
小康2022 已提交
509
            self.master.itemconfigure(
小康2022's avatar
小康2022 已提交
510
                self.cursor, text=self.__text(self.value_surface) + '│')
小康2022's avatar
小康2022 已提交
511

小康2022's avatar
小康2022 已提交
512 513 514 515 516 517 518 519 520 521 522 523
    @staticmethod
    def __text(string: str) -> str:
        """ 内部函数 """
        out = ''
        for i in string:
            if i == '\n':
                out += i
            elif 0 <= ord(i) <= 256:
                out += ' '
            else:
                out += '  '
        return out
小康2022's avatar
小康2022 已提交
524 525


小康2022's avatar
小康2022 已提交
526
# 虚拟画布控件
小康2022's avatar
小康2022 已提交
527 528


小康2022's avatar
小康2022 已提交
529 530 531
class CanvasLabel(_BaseWidget):
    """
    虚拟画布标签控件
小康2022's avatar
小康2022 已提交
532

小康2022's avatar
小康2022 已提交
533 534
    创建一个虚拟的标签控件,用于显示少量文本
    """
小康2022's avatar
小康2022 已提交
535

小康2022's avatar
小康2022 已提交
536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557
    def __init__(self,
                 canvas: Canvas,
                 x: int,
                 y: int,
                 width: int,
                 height: int,
                 text: str = TEXT,
                 borderwidth: int = BORDERWIDTH,
                 justify: str = tkinter.CENTER,
                 font: tuple[str, int, str] = FONT,
                 color_text: tuple[str, str, str] = COLOR_BLACK,
                 color_fill: tuple[str, str, str] = COLOR_FILL,
                 color_outline: tuple[str, str, str] = COLOR_BUTTON) -> None:
        _BaseWidget.__init__(self, canvas, x, y, width, height, text, justify,
                             borderwidth, font, color_text, color_fill, color_outline)

    def touch(self, event: tkinter.Event) -> None:
        """ 触碰状态检测 """
        if self.x1 <= event.x <= self.x2 and self.y1 <= event.y <= self.y2:
            self.state('touch')
        else:
            self.state('normal')
小康2022's avatar
小康2022 已提交
558 559


小康2022's avatar
小康2022 已提交
560 561 562 563 564
class CanvasButton(_BaseWidget):
    """
    虚拟画布按钮控件

    创建一个虚拟的按钮,并执行关联函数
小康2022's avatar
小康2022 已提交
565
    """
小康2022's avatar
小康2022 已提交
566

小康2022's avatar
小康2022 已提交
567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583
    def __init__(self,
                 canvas: Canvas,
                 x: int,
                 y: int,
                 width: int,
                 height: int,
                 text: str = TEXT,
                 borderwidth: int = BORDERWIDTH,
                 justify: str = tkinter.CENTER,
                 font: tuple[str, int, str] = FONT,
                 command: types.FunctionType | None = None,
                 color_text: tuple[str, str, str] = COLOR_BLACK,
                 color_fill: tuple[str, str, str] = COLOR_FILL,
                 color_outline: tuple[str, str, str] = COLOR_BUTTON) -> None:
        _BaseWidget.__init__(self, canvas, x, y, width, height, text, justify,
                             borderwidth, font, color_text, color_fill, color_outline)
        self.command = command
小康2022's avatar
小康2022 已提交
584 585 586

    def execute(self, event: tkinter.Event) -> None:
        """ 执行关联函数 """
小康2022's avatar
小康2022 已提交
587 588
        if self.x1 <= event.x <= self.x2 and self.y1 <= event.y <= self.y2:
            if self.live and self.command:
小康2022's avatar
小康2022 已提交
589 590 591
                self.command()

    def press(self, event: tkinter.Event) -> None:
小康2022's avatar
小康2022 已提交
592 593 594 595 596
        """ 交互状态检测 """
        if self.x1 <= event.x <= self.x2 and self.y1 <= event.y <= self.y2:
            self.state('press')
        else:
            self.state('normal')
小康2022's avatar
小康2022 已提交
597

小康2022's avatar
小康2022 已提交
598 599 600 601 602 603
    def touch(self, event: tkinter.Event) -> None:
        """ 触碰状态检测 """
        if self.x1 <= event.x <= self.x2 and self.y1 <= event.y <= self.y2:
            self.state('touch')
        else:
            self.state('normal')
小康2022's avatar
小康2022 已提交
604 605


小康2022's avatar
小康2022 已提交
606
class CanvasEntry(_TextWidget):
小康2022's avatar
小康2022 已提交
607
    """
小康2022's avatar
小康2022 已提交
608
    虚拟画布输入框控件
小康2022's avatar
小康2022 已提交
609

小康2022's avatar
小康2022 已提交
610 611
    创建一个虚拟的输入框控件,可输入单行少量字符,并获取这些字符
    """
小康2022's avatar
小康2022 已提交
612 613

    def __init__(self,
小康2022's avatar
小康2022 已提交
614 615 616 617 618 619 620 621 622 623 624 625 626 627 628
                 canvas: Canvas,
                 x: int,
                 y: int,
                 width: int,
                 height: int,
                 text: tuple[str] | str = TEXT,
                 show: str | None = None,
                 limit: int = 15,
                 space: bool = False,
                 borderwidth: int = BORDERWIDTH,
                 justify: str = tkinter.CENTER,
                 font: tuple[str, int, str] = FONT,
                 color_text: tuple[str, str, str] = COLOR_BLACK,
                 color_fill: tuple[str, str, str] = COLOR_WHITE,
                 color_outline: tuple[str, str, str] = COLOR_TEXT) -> None:
小康2022's avatar
小康2022 已提交
629 630 631 632 633
        if type(text) == str:
            text = (text, '')
        elif type(text) == None:
            text = ('', '')

小康2022's avatar
小康2022 已提交
634 635
        _TextWidget.__init__(self, canvas, x, y, width, height, text, limit, space, justify,
                             borderwidth, font, color_text, color_fill, color_outline)
小康2022's avatar
小康2022 已提交
636
        self.master.itemconfigure(self.text, text=self.value_normal)
小康2022's avatar
小康2022 已提交
637 638 639 640 641 642
        self.show = show

    def press_off(self) -> None:
        # 重写父类方法
        _TextWidget.press_off(self)
        if self.value == '':
小康2022's avatar
小康2022 已提交
643
            self.master.itemconfigure(self.text, text=self.value_normal)
小康2022's avatar
小康2022 已提交
644 645

    def touch_on(self) -> None:
小康2022's avatar
小康2022 已提交
646
        """ 鼠标悬停状态 """
小康2022's avatar
小康2022 已提交
647 648
        if not self._press:
            self.state('touch')
小康2022's avatar
小康2022 已提交
649 650

            # 判断显示的值是否为第一默认值
小康2022's avatar
小康2022 已提交
651
            if self.master.itemcget(self.text, 'text') == self.value_normal:
小康2022's avatar
小康2022 已提交
652
                # 更新为第二默认值
小康2022's avatar
小康2022 已提交
653
                self.master.itemconfigure(self.text, text=self.value_touch)
小康2022's avatar
小康2022 已提交
654

小康2022's avatar
小康2022 已提交
655
    def touch_off(self) -> None:
小康2022's avatar
小康2022 已提交
656
        """ 鼠标离开状态 """
小康2022's avatar
小康2022 已提交
657 658
        if not self._press:
            self.state('normal')
小康2022's avatar
小康2022 已提交
659 660

            # 判断显示的值是否为第二默认值
小康2022's avatar
小康2022 已提交
661
            if self.master.itemcget(self.text, 'text') == self.value_touch:
小康2022's avatar
小康2022 已提交
662
                # 更新为第一默认值
小康2022's avatar
小康2022 已提交
663
                self.master.itemconfigure(self.text, text=self.value_normal)
小康2022's avatar
小康2022 已提交
664 665 666 667

    def change_text(self, value: str) -> None:
        """ 重新设定显示文字 """
        # 改变真实值
小康2022's avatar
小康2022 已提交
668
        self.value = value
小康2022's avatar
小康2022 已提交
669 670 671
        # 改变显示值
        self.value_surface = len(value) * self.show if self.show else value
        # 更新显示值
小康2022's avatar
小康2022 已提交
672
        self.master.itemconfigure(self.text, text=self.value_surface)
小康2022's avatar
小康2022 已提交
673 674 675

    def input(self, event: tkinter.Event) -> None:
        """ 文本输入 """
小康2022's avatar
小康2022 已提交
676
        if self.master.lock:
小康2022's avatar
小康2022 已提交
677
            if self._press and self.live:
小康2022's avatar
小康2022 已提交
678 679
                if event.keysym == 'BackSpace':
                    # 按下退格键
小康2022's avatar
小康2022 已提交
680 681 682 683
                    self.value = self.value[:-1]
                if len(event.char) and len(self.value) < self.limit:
                    key = ord(event.char)
                    if 32 < key < 127 or key > 255 or (key == 32 and self.space):
小康2022's avatar
小康2022 已提交
684
                        # 按下普通按键
小康2022's avatar
小康2022 已提交
685
                        self.value += event.char
小康2022's avatar
小康2022 已提交
686 687

                # 更新表面显示值
小康2022's avatar
小康2022 已提交
688
                self.value_surface = len(
小康2022's avatar
小康2022 已提交
689
                    self.value) * self.show if self.show else self.value
小康2022's avatar
小康2022 已提交
690
                # 更新显示
小康2022's avatar
小康2022 已提交
691
                self.master.itemconfigure(
小康2022's avatar
小康2022 已提交
692 693
                    self.text, text=self.value_surface)
                self.cursor_update()
小康2022's avatar
小康2022 已提交
694 695


小康2022's avatar
小康2022 已提交
696
class CanvasText(_TextWidget):
小康2022's avatar
小康2022 已提交
697 698 699 700
    """虚拟画布文本框类

    创建一个透明的虚拟文本框,
    用于输入多行文本和显示多行文本(只读模式)
小康2022's avatar
小康2022 已提交
701
    """
小康2022's avatar
小康2022 已提交
702

小康2022's avatar
小康2022 已提交
703 704 705 706 707 708 709 710 711 712 713 714 715 716 717
    def __init__(self,
                 canvas: Canvas,
                 x: int,
                 y: int,
                 width: int,
                 height: int,
                 limit: int = 100,
                 space: bool = True,
                 read: bool = False,
                 borderwidth: int = BORDERWIDTH,
                 justify: str = tkinter.LEFT,
                 font: tuple[str, int, str] = FONT,
                 color_text: tuple[str, str, str] = COLOR_BLACK,
                 color_fill: tuple[str, str, str] = COLOR_WHITE,
                 color_outline: tuple[str, str, str] = COLOR_TEXT) -> None:
小康2022's avatar
小康2022 已提交
718
        _TextWidget.__init__(self, canvas, x, y, width, height, TEXT, limit, space, justify,
小康2022's avatar
小康2022 已提交
719 720 721
                             borderwidth, font, color_text, color_fill, color_outline)
        # 只读模式
        self.read = read
小康2022's avatar
小康2022 已提交
722
        # 修改多行文本靠左显示
小康2022's avatar
小康2022 已提交
723 724 725 726
        self.master.coords(self.text, self.x1 + 2, self.y1 + 2)
        self.master.coords(self.cursor, self.x1 - 7, self.y1 + 2)
        self.master.itemconfigure(self.text, anchor='nw', justify=justify)
        self.master.itemconfigure(self.cursor, anchor='nw', justify=justify)
小康2022's avatar
小康2022 已提交
727
        # 计算单行文本容纳量
小康2022's avatar
小康2022 已提交
728
        self.row = round((self.x2 - self.x1) / int(font[1] * 3/4)) + 1
小康2022's avatar
小康2022 已提交
729
        # 计算文本容纳行数
小康2022's avatar
小康2022 已提交
730
        self.line = round((self.y2 - self.y1) / int(font[1] * 3/2)) + 1
小康2022's avatar
小康2022 已提交
731
        # 文本位置
小康2022's avatar
小康2022 已提交
732
        self._pos = self.line
小康2022's avatar
小康2022 已提交
733

小康2022's avatar
小康2022 已提交
734
    def touch_on(self) -> None:
小康2022's avatar
小康2022 已提交
735
        """ 鼠标悬停状态 """
小康2022's avatar
小康2022 已提交
736 737
        if not self._press:
            self.state('touch')
小康2022's avatar
小康2022 已提交
738

小康2022's avatar
小康2022 已提交
739
    def touch_off(self) -> None:
小康2022's avatar
小康2022 已提交
740
        """ 鼠标离开状态 """
小康2022's avatar
小康2022 已提交
741 742
        if not self._press:
            self.state('normal')
小康2022's avatar
小康2022 已提交
743 744 745 746

    def change_text(self, value: str) -> None:
        """ 重新设定显示文字 """
        # 改变文本
小康2022's avatar
小康2022 已提交
747
        self.value_surface = self.value = value
小康2022's avatar
小康2022 已提交
748 749 750 751 752 753 754
        # 更新显示值
        self.append('')

    def append(self, value: str) -> None:
        """ 添加文本 """
        # 改变文本
        self.value += value
小康2022's avatar
小康2022 已提交
755
        key = self.value.count('\n')
小康2022's avatar
小康2022 已提交
756
        if key <= self.line:
小康2022's avatar
小康2022 已提交
757
            # 更新显示值
小康2022's avatar
小康2022 已提交
758
            self.master.itemconfigure(self.text, text=self.value)
小康2022's avatar
小康2022 已提交
759
        else:
小康2022's avatar
小康2022 已提交
760
            # 同步更新文本上下位置数据
小康2022's avatar
小康2022 已提交
761
            self._pos += value.count('\n')
小康2022's avatar
小康2022 已提交
762
            # 计算显示文本的部分
小康2022's avatar
小康2022 已提交
763
            ind = key - self.line
小康2022's avatar
小康2022 已提交
764
            self.value_surface = '\n'.join(
小康2022's avatar
小康2022 已提交
765
                self.value.split('\n')[ind:ind + self.line])
小康2022's avatar
小康2022 已提交
766
            self.master.itemconfigure(self.text, text=self.value_surface)
小康2022's avatar
小康2022 已提交
767 768 769

    def scroll(self, event: tkinter.Event) -> None:
        """ 文本滚动 """
小康2022's avatar
小康2022 已提交
770
        if self.master.lock:
小康2022's avatar
小康2022 已提交
771
            if self.x1 <= event.x <= self.x2 and self.y1 <= event.y <= self.y2:
小康2022's avatar
小康2022 已提交
772
                if event.delta > 0 and self._pos > self.line:
小康2022's avatar
小康2022 已提交
773
                    # 鼠标向上滚动,显示文本部分向下滚动
小康2022's avatar
小康2022 已提交
774 775
                    self._pos -= 1
                elif event.delta < 0 and self._pos < self.value.count('\n'):
小康2022's avatar
小康2022 已提交
776
                    # 鼠标向下滚动,显示文本部分向上滚动
小康2022's avatar
小康2022 已提交
777
                    self._pos += 1
小康2022's avatar
小康2022 已提交
778
                # 计算显示文本的部分
小康2022's avatar
小康2022 已提交
779
                ind = self._pos - self.line
小康2022's avatar
小康2022 已提交
780
                self.value_surface = '\n'.join(
小康2022's avatar
小康2022 已提交
781
                    self.value.split('\n')[ind:ind + self.line])
小康2022's avatar
小康2022 已提交
782
                self.master.itemconfigure(self.text, text=self.value_surface)
小康2022's avatar
小康2022 已提交
783 784 785

    def input(self, event: tkinter.Event) -> None:
        """ 文本输入 """
小康2022's avatar
小康2022 已提交
786
        if self.master.lock:
小康2022's avatar
小康2022 已提交
787
            if self._press and self.live and not self.read:
小康2022's avatar
小康2022 已提交
788 789 790 791 792 793 794 795
                if event.keysym == 'BackSpace':
                    # 按下退格键
                    if len(self.value) > 1 and self.value[-2] == '\n':
                        self.value = self.value[:-2]
                    elif len(self.value):
                        self.value = self.value[:-1]
                elif len(event.char) and len(self.value) < self.limit:
                    # 按下普通按键
小康2022's avatar
小康2022 已提交
796 797
                    key = ord(event.char)
                    if 32 < key < 127 or key > 255 or (key == 32 and self.space):
小康2022's avatar
小康2022 已提交
798
                        line = sum(
小康2022's avatar
小康2022 已提交
799 800 801
                            [1 if 32 <= ord(i) < 127 else 2 for i in self.value.split('\n')[-1]])
                        line += 1 if 32 <= key < 127 else 2
                        if line > self.row:
小康2022's avatar
小康2022 已提交
802 803 804 805 806
                            self.value += '\n' + event.char
                        else:
                            self.value += event.char

                # 更新显示
小康2022's avatar
小康2022 已提交
807
                self.value_surface = self.value
小康2022's avatar
小康2022 已提交
808
                self.master.itemconfigure(self.text, text=self.value)
小康2022's avatar
小康2022 已提交
809
                self.cursor_update()
小康2022's avatar
小康2022 已提交
810 811


小康2022's avatar
小康2022 已提交
812 813 814
# 工具类


小康2022's avatar
小康2022 已提交
815 816 817 818 819 820
class PhotoImage(tkinter.PhotoImage):  # NOTE: 可改进
    """
    图片类

    生成图片并进行相应的一些处理
    """
小康2022's avatar
小康2022 已提交
821 822 823 824 825

    def __init__(self,
                 file: str | bytes,
                 gif: bool = False,
                 *args, **kwargs):
小康2022's avatar
小康2022 已提交
826 827 828
        """
        
        """
小康2022's avatar
小康2022 已提交
829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847
        self.file = file
        self.gif = gif  # 是否为动图
        self.frames = []

        if not gif:
            return tkinter.PhotoImage.__init__(self, file=file, *args, **kwargs)

    def parse(self, total: int, generator: bool = False):
        """ 解析动图 """
        while (ind := 0) != total:
            image = tkinter.PhotoImage(file=self.file, format='gif -index %d' % ind)
            ind += 1
            if generator:
                yield image
            else:
                self.frames.append(image)
        else:
            yield self.frames

小康2022's avatar
小康2022 已提交
848
    def play(self, canvas: Canvas, id, interval: int, _ind: int = 0):
小康2022's avatar
小康2022 已提交
849 850 851
        """ 播放动图 """
        if self.gif:
            if canvas.lock:
小康2022's avatar
小康2022 已提交
852 853 854 855
                if _ind == len(self.frames):
                    _ind = 0
                canvas.configure(id, image=self.frames[_ind])
                canvas.after(interval, self.play, canvas, id, interval, _ind + 1)
小康2022's avatar
小康2022 已提交
856
            else:
小康2022's avatar
小康2022 已提交
857
                canvas.after(interval, self.play, canvas, id, interval, _ind)
小康2022's avatar
小康2022 已提交
858 859


小康2022's avatar
小康2022 已提交
860
# 功能函数
小康2022's avatar
小康2022 已提交
861 862


小康2022's avatar
小康2022 已提交
863
def move_widget(widget: Canvas | _BaseWidget,
小康2022's avatar
小康2022 已提交
864 865
                dx: int,
                dy: int,
小康2022's avatar
小康2022 已提交
866
                times: float,
小康2022's avatar
小康2022 已提交
867
                mode: typing.Literal['smooth', 'shake', 'flat'],
小康2022's avatar
小康2022 已提交
868 869 870
                _x: int = 0,
                _y: int = 0,
                _ind: int = 0) -> None:
小康2022's avatar
小康2022 已提交
871
    """
小康2022's avatar
小康2022 已提交
872
    ### 控件移动函数
小康2022's avatar
小康2022 已提交
873

小康2022's avatar
小康2022 已提交
874 875 876 877 878 879 880 881 882 883 884 885
    以特定方式移动由 Place 布局的某个控件或某些控件的集合或图像

    #### 参数说明

    `widget`: 要移动位置的控件
    `dx`: 横向移动的距离(单位:像素)
    `dy`: 纵向移动的距离
    `times`: 移动总时长(单位:秒)
    `mode`: 模式,可选三种(如下)
    1. `smooth`: 速度先慢后快再慢
    2. `shake`: 和 smooth 一样,但是最后会回弹一下
    3. `flat`: 匀速平移
小康2022's avatar
小康2022 已提交
886
    """
小康2022's avatar
小康2022 已提交
887 888 889

    # 三种模式的速度变化列表
    if mode == 'smooth':
小康2022's avatar
小康2022 已提交
890
        _ = [1, 2, 2, 3, 3, 5, 6, 7, 9, 12, 12, 9, 7, 6, 5, 3, 3, 2, 2, 1]
小康2022's avatar
小康2022 已提交
891
    elif mode == 'shake':
小康2022's avatar
小康2022 已提交
892
        _ = [10, 10, 9, 9, 8, 8, 7, 7, 6, 6, 5, 5, 4, 4, 3, 3, 1, -1, - 2, -3]
小康2022's avatar
小康2022 已提交
893
    elif mode == 'flat':
小康2022's avatar
小康2022 已提交
894
        _ = [5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5]
小康2022's avatar
小康2022 已提交
895

小康2022's avatar
小康2022 已提交
896 897 898 899 900 901 902 903 904 905 906 907 908 909
    # 总计实际应该偏移值
    total = sum(_[:_ind + 1]) / 100

    # 计算偏移量
    x = int(_[_ind] * dx / 100)
    y = int(_[_ind] * dy / 100)

    # 累计偏移量(用于修正偏移)
    _x += x
    _y += y

    # 修正值
    _dx = round(total * dx) - _x
    _dy = round(total * dy) - _y
小康2022's avatar
小康2022 已提交
910

小康2022's avatar
小康2022 已提交
911 912 913 914 915 916 917 918
    # 修正值矫正
    x += _dx
    y += _dy
    _x += _dx
    _y += _dy

    if isinstance(widget.master, tkinter.Tk | tkinter.Frame):
        # 原坐标
小康2022's avatar
小康2022 已提交
919 920 921
        origin_x = int(widget.place_info()['x'])
        origin_y = int(widget.place_info()['y'])
        widget.place(x=origin_x + x, y=origin_y + y)
小康2022's avatar
小康2022 已提交
922
    elif isinstance(widget.master, Canvas):
小康2022's avatar
小康2022 已提交
923
        widget.move(x, y)
小康2022's avatar
小康2022 已提交
924
    else:
小康2022's avatar
小康2022 已提交
925
        widget.master.move(widget, x, y)  # NOTE: BUG
小康2022's avatar
小康2022 已提交
926

小康2022's avatar
小康2022 已提交
927
    if _ind < 19:
小康2022's avatar
小康2022 已提交
928
        # 更新函数
小康2022's avatar
小康2022 已提交
929 930
        widget.master.after(round(times * 50), move_widget, widget,
                   dx, dy, times, mode, _x, _y, _ind + 1)
小康2022's avatar
小康2022 已提交
931 932 933


def correct_text(length: int, string: str) -> str:
小康2022's avatar
小康2022 已提交
934
    """
小康2022's avatar
小康2022 已提交
935
    ### 修正字符串长度
小康2022's avatar
小康2022 已提交
936

小康2022's avatar
小康2022 已提交
937
    可将目标字符串改为目标长度并居中对齐
小康2022's avatar
小康2022 已提交
938 939 940 941 942

    #### 参数说明

    `length`: 目标长度
    `string`: 要修改的字符串
小康2022's avatar
小康2022 已提交
943
    """
小康2022's avatar
小康2022 已提交
944 945 946 947 948 949 950 951 952

    # 修正长度
    n = length - sum([1 + (ord(i) > 256) for i in string])
    # 修正空格数
    space = (n // 2) * ' '
    # 居中对齐
    value = space + string + space
    # 奇偶处理
    return value if n % 2 == 0 else value + ' '
小康2022's avatar
小康2022 已提交
953 954


小康2022's avatar
小康2022 已提交
955 956 957 958 959
def process_color(color: str | None = None, key: float | str = '') -> str:  # NOTE: 可改进
    """
    ### 颜色字符串处理函数(RGB码)

    随机产生一个RGB颜色字符串,以及给出已有RGB颜色字符串的渐变RGB颜色字符串
小康2022's avatar
小康2022 已提交
960

小康2022's avatar
小康2022 已提交
961
    #### 参数说明
小康2022's avatar
小康2022 已提交
962

小康2022's avatar
小康2022 已提交
963 964
    `color`: 颜色字符串
    `key`: 比值
小康2022's avatar
小康2022 已提交
965 966 967
    """

    lib, rgb = '0123456789ABCDEF', ''
小康2022's avatar
小康2022 已提交
968

小康2022's avatar
小康2022 已提交
969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984
    if not color:
        # 随机RGB颜色字符串
        for _ in range(6):
            rgb += lib[random.randint(0, 15)]
    else:
        # 渐变RGB颜色字符串的生成
        *slice_seq, length = (1, 2, 3, 1) if len(color) == 4 else (1, 3, 5, 2)
        if type(key) == float:
            for ind in slice_seq:
                rgb += oct(round(int(color[ind: ind +
                           length], 16) * key) % 16)[2:]
        elif type(key) == str:
            for ind in slice_seq:
                rgb += oct(int(color[ind: ind + length],
                           16) + int(key, 16))[2:]
    return '#' + rgb
小康2022's avatar
小康2022 已提交
985 986 987 988 989 990 991 992 993 994 995 996 997 998 999


def _test():
    """ 测试函数 """
    root = Tk('Test', '960x540', None, True)
    canvas = Canvas(root, 960, 540)
    canvas.pack(expand=True, fill='both')
    CanvasButton(canvas, 100, 100, 100, 25, '按钮', command=lambda: print('Yes'))
    CanvasLabel(canvas, 100, 200, 150, 100)
    CanvasEntry(canvas, 300, 100, 150, 25, ('输入框', '点击输入'), limit=9)
    CanvasText(canvas, 300, 200, 300, 200, limit=400)
    root.mainloop()


if __name__ == '__main__':
小康2022's avatar
小康2022 已提交
1000
    print(__doc__.replace('# ', '').replace('#', '').replace('---', '').replace('    ', ''))
小康2022's avatar
小康2022 已提交
1001
    _test()