提交 518d0fcd 编写于 作者: 小康2022

version 2.6.10.dev1

上级 e4afd671
ChangeLog/更新日志
=================
[`2.6.10.dev0`] - 2023-08-10
[`2.6.10.dev1`] - 2023-08-11
----------------------------
### Added/新增
- [X] Added file exceptions.py and exception classes `ScaleArgsValueError`, `ColorArgsValueError` and `WidgetStateModeError` to provide a description of some exceptions
新增文件 exceptions.py 及异常类 `ScaleArgsValueError``ColorArgsValueError``WidgetStateModeError` 以提供部分异常的描述
- [X] The widget `Progressbar` adds a indeterminate mode
控件 `Progressbar` 新增不定模式
......@@ -17,6 +17,19 @@ ChangeLog/更新日志
- [X] The parameter `width` of class `Switch` was changed from positional argument to keyword argument
`Switch` 的参数 `width` 由位置参数更改为关键字参数
### Optimized/优化
- [X] Change the way the output is formatted in all code from the "%" mode to the more efficient f-string mode
将所有代码中格式化输出的方式由 “%” 方式更改为效率更高的 f-string 方式
- [X] Optimized type hints for some code
优化了部分代码的类型提示
### Removed/移除
- [X] Remove the function `text` and use f-string instead
移除函数 `text`,可使用 f-string 来对其进行代替
[`2.6.9`] - 2023-08-09
----------------------
......
......@@ -4,13 +4,13 @@
<img src="tkt.png" style="height: 128px" alt="Logo" />
`tkintertools` 模块是 `tkinter` 模块的一个辅助模块\
The `tkintertools` module is an auxiliary module of the `tkinter` module
`tkintertools` 是 Python3 内置模块 `tkinter` 的一个辅助开发模块\
The `tkintertools` is an auxiliary development module of the Python3 built-in module `tkinter`
[![Version](https://img.shields.io/pypi/v/tkintertools?label=Version)](.)
[![License](https://img.shields.io/pypi/l/tkintertools?label=License)](LICENSE.txt)
[![ChangeLog](https://img.shields.io/badge/ChangeLog-2023/08/10-orange)](CHANGELOG.md)
[![ToDo](https://img.shields.io/badge/ToDo-13-yellow)](TODO.md)
[![ChangeLog](https://img.shields.io/badge/ChangeLog-2023/08/11-orange)](CHANGELOG.md)
[![ToDo](https://img.shields.io/badge/ToDo-12-yellow)](TODO.md)
[![Size](https://img.shields.io/github/languages/code-size/Xiaokang2022/tkintertools?label=Size)](tkintertools)
[![Wiki](https://img.shields.io/badge/Wiki-14-purple)](https://github.com/Xiaokang2022/tkintertools/wiki)\
[![Downloads](https://img.shields.io/pypi/dm/tkintertools?label=Downloads&logo=pypi)](https://pypistats.org/packages/tkintertools)
......@@ -40,15 +40,15 @@ pip install tkintertools==2.6.9
### Development Version/开发版本
* Version/最新版本 : `2.6.10.dev0` (第 1 个预发布版本)
* Release/发布日期 : 2023/08/10 (UTC+08)
* Version/最新版本 : `2.6.10.dev1` (第 2 个预发布版本)
* Release/发布日期 : 2023/08/11 (UTC+08)
这个是我正在开发的版本,可能有新功能,bug 可能会比较多,但也可能会比原来的版本更加稳定。开发版没有经过多操作系统的测试,仅能保证在 Windows 系统下运行所有功能,在其他的操作系统上,可能有部分功能无法正常运行。大家可以在 Issues 中提出一些建议,我可能会适当采纳一些并在开发版本中更改或实现。
**PIP Cmd/安装命令:**
```
pip install tkintertools==2.6.10.dev0
pip install tkintertools==2.6.10.dev1
```
> **Warning**
......@@ -79,19 +79,27 @@ News/最新功能👇
### Release Notes/版本说明
**最新版本: `tkintertools-v2.6.10.dev0`**
**最新版本: `tkintertools-v2.6.10.dev1`**
> **Note**
> tkintertools 的介绍、使用教程和开发文档均在 [Wiki](https://github.com/Xiaokang2022/tkintertools/wiki) 中,大家可前往查阅
下面是本次开发版本(`v2.6.9` -> `v2.6.10.dev0`)的更新内容条目:
下面是本次开发版本(`v2.6.9` -> `v2.6.10.dev1`)的更新内容条目:
- [X] Added file exceptions.py and exception classes `ScaleArgsValueError`, `ColorArgsValueError` and `WidgetStateModeError` to provide a description of some exceptions
新增文件 exceptions.py 及异常类 `ScaleArgsValueError``ColorArgsValueError``WidgetStateModeError` 以提供部分异常的描述
- [X] The widget `Progressbar` adds a indeterminate mode
控件 `Progressbar` 新增不定模式
- [X] The value of the constant `SWITCH_WIDTH` is changed from 0 to 60
常量 `SWITCH_WIDTH` 的值从 0 更改为 60
- [X] The parameter `width` of class `Switch` was changed from positional argument to keyword argument
`Switch` 的参数 `width` 由位置参数更改为关键字参数
- [X] Change the way the output is formatted in all code from the "%" mode to the more efficient f-string mode
将所有代码中格式化输出的方式由 “%” 方式更改为效率更高的 f-string 方式
- [X] Optimized type hints for some code
优化了部分代码的类型提示
- [X] Remove the function `text` and use f-string instead
移除函数 `text`,可使用 f-string 来对其进行代替
### Template Demo/模板演示
......
TODO/待办
---------
### Bugs/已知问题
1. [ ] Solve the bug that the vertical position of the text is wrong when the `Text` class is enlarged
解决 `Text` 类放大时,文本纵向位置错误的 bug
2. [ ] Solve the bug that text shrinks and overflows after zooming text controls
解决文本类控件缩放后文本产生缩水和溢出的 bug
3. [ ] Fix the bug of deletion misalignment when there is too much text in the `Text` class
解决 `Text` 类中文本过多时删减错位的 bug
4. [ ] Fix the bug where the method `place` of class `anvas` did not work correctly
解决类 `Canvas` 的方法 `place` 无法正常工作的 bug
5. [ ] The position relationship between the space before and after the 3D object is still problematic at some point
3D 对象前后空间的位置关系在某些时候仍有问题
6. [ ] When a widget is tapped, the widgets that follow it are also triggered
点击控件时,其后面的控件也会被触发
### Features/期望功能
### TODO/期望功能
1. [ ] Perfect the scroll bar function of `Text` class
完善 `Text` 类的滚动条功能
......@@ -43,3 +20,20 @@ TODO/待办
7. [ ] Add lights and achieve simple light and shadow renderings
添加光源,并实现简单的光影渲染的效果
### BUG/已知问题
1. [ ] Solve the bug that the vertical position of the text is wrong when the `Text` class is enlarged
解决 `Text` 类放大时,文本纵向位置错误的 bug
2. [ ] Solve the bug that text shrinks and overflows after zooming text controls
解决文本类控件缩放后文本产生缩水和溢出的 bug
3. [ ] Fix the bug of deletion misalignment when there is too much text in the `Text` class
解决 `Text` 类中文本过多时删减错位的 bug
4. [ ] The position relationship between the space before and after the 3D object is still problematic at some point
3D 对象前后空间的位置关系在某些时候仍有问题
5. [ ] When a widget is tapped, the widgets that follow it are also triggered
点击控件时,其后面的控件也会被触发
......@@ -19,7 +19,6 @@ Provides
* Scalable png pictures and playable gif pictures
* Regular mobile widgets and canvas interfaces
* Gradient colors and contrast colors
* Text with controllable length and alignment
* Convenient, inheritable singleton pattern class
* 3D drawing
......@@ -27,7 +26,7 @@ Contents
--------
* Container Widgets: `Tk`, `Toplevel`, `Canvas`
* Virtual Canvas Widgets: `Label`, `Button`, `CheckButton`, `Entry`, `Text`, `Progressbar`,`ToolTip`, `Switch`
* Tool Functions: `text`, `color`, `askfont`, `SetProcessDpiAwareness`
* Tool Functions: `color`, `askfont`, `SetProcessDpiAwareness`
* Tool Classes: `PhotoImage`, `Animation`
* Tool Submodules: `tool_3d`
......@@ -42,13 +41,14 @@ More
import sys # Get interpreter version information
if sys.version_info < (3, 8): # Version Check
raise RuntimeError('Python version is too low (>=3.8)')
err_info = 'Python version is too low (>=3.8)'
raise RuntimeError(err_info)
from .__main__ import *
from .constants import *
__author__ = 'Xiaokang2022<2951256653@qq.com>'
__version__ = '2.6.10.dev0'
__version__ = '2.6.10.dev1'
__all__ = [
# Container Widgets
'Tk', 'Toplevel', 'Canvas',
......@@ -57,5 +57,5 @@ __all__ = [
# Tool Classes
'PhotoImage', 'Animation',
# Tool Functions
'text', 'color', 'askfont', 'SetProcessDpiAwareness',
'color', 'askfont', 'SetProcessDpiAwareness',
] + all_constants
......@@ -17,7 +17,8 @@ if sys.platform == 'win32': # 仅在 Windows 平台下支持设置 DPI 级别
else:
WinDLL = None
from .constants import *
from .constants import * # 常量
from .exceptions import * # 异常
class Tk(tkinter.Tk):
......@@ -60,9 +61,9 @@ class Tk(tkinter.Tk):
if width is not None and height is not None:
if x is not None and y is not None:
self.geometry('%dx%d+%d+%d' % (width, height, x, y))
self.geometry(f'{width}x{height}+{x}+{y}')
else:
self.geometry('%dx%d' % (width, height))
self.geometry(f'{width}x{height}')
if title is not None:
self.title(title)
......@@ -109,16 +110,16 @@ class Tk(tkinter.Tk):
self.width, self.height = [width]*2, [height]*2
if width != '':
geometry = '%sx%s+%s+%s' % (width, height, _width, _height)
geometry = f'{width}x{height}+{_width}+{_height}'
else:
geometry = '+%s+%s' % (_width, _height)
geometry = f'+{_width}+{_height}'
if not _:
geometry = geometry.split('+')[0]
return tkinter.Tk.wm_geometry(self, geometry)
geometry = tkinter.Tk.wm_geometry(self, newGeometry)
width, height, _width, _height, * \
_ = map(int, (geometry+'+0+0').replace('+', 'x').split('x'))
return '%dx%d+%d+%d' % (width, height, _width, _height)
return f'{width}x{height}+{_width}+{_height}'
geometry = wm_geometry # 方法别名
......@@ -213,8 +214,8 @@ class Canvas(tkinter.Canvas):
self.rx = 1. # 横向放缩比率
self.ry = 1. # 纵向放缩比率
self._widget = [] # type: list[BaseWidget] # 子控件列表(与事件绑定有关)
self._font = {} # type: dict[tkinter._CanvasItemId, float]
self._image = {} # type: dict[tkinter._CanvasItemId, list]
self._font = {} # type: dict[int, float]
self._image = {} # type: dict[int, list]
tkinter.Canvas.__init__(
self, master, width=width, height=height, highlightthickness=0, **kw)
......@@ -363,7 +364,7 @@ class Canvas(tkinter.Canvas):
if widget._paste():
return
def create_text(self, *args, **kw): # type: (...) -> tkinter._CanvasItemId
def create_text(self, *args, **kw): # type: (...) -> int
# override: 添加对 text 类型的 _CanvasItemId 的字体大小的控制
font = kw.get('font')
if not font:
......@@ -374,17 +375,14 @@ class Canvas(tkinter.Canvas):
self._font[item] = list(kw['font'])
return item
def create_image(self, *args, **kw): # type: (...) -> tkinter._CanvasItemId
def create_image(self, *args, **kw): # type: (...) -> int
# override: 添加对 image 类型的 _CanvasItemId 的图像大小的控制
item = tkinter.Canvas.create_image(self, *args, **kw)
self._image[item] = [kw.get('image'), None]
return item
def itemconfigure(
self,
tagOrId, # type: str | tkinter._CanvasItemId
**kw
): # type: (...) -> dict[str, tuple[str, str, str, str, str]] | None
def itemconfigure(self, tagOrId, **kw):
# type: (str | int, ...) -> dict[str, tuple[str, str, str, str, str]] | None
# override: 创建空 image 的 _CanvasItemId 时漏去对图像大小的控制
if type(kw.get('image')) == PhotoImage:
self._image[tagOrId] = [kw.get('image'), None]
......@@ -595,8 +593,10 @@ class BaseWidget:
mode = 1
elif self._state == 'click':
mode = 2
else:
elif self._state == 'disabled':
mode = 3
else:
raise WidgetStateModeError(self._state)
if self.tooltip is not None:
if self._state == 'normal':
......@@ -814,7 +814,7 @@ class TextWidget(BaseWidget):
if self.master.itemcget(self.text, 'text') == self._value[2]:
self.master.itemconfigure(self.text, text=self._value[1])
def _click(self, event): # type: (tkinter.Event) -> None
def _click(self, event): # type: (Entry | Text, tkinter.Event) -> None
""" 交互状态检测 """
if self.x1 <= event.x <= self.x2 and self.y1 <= event.y <= self.y2:
if self._state != 'click':
......@@ -822,10 +822,7 @@ class TextWidget(BaseWidget):
else:
self._click_off()
def _touch(
self, # type: Entry | Text
event # type: tkinter.Event
): # type: (...) -> bool
def _touch(self, event): # type: (Entry | Text, tkinter.Event) -> bool
""" 触碰状态检测 """
condition = self.x1 <= event.x <= self.x2 and self.y1 <= event.y <= self.y2
self._touch_on() if condition else self._touch_off()
......@@ -888,7 +885,7 @@ class TextWidget(BaseWidget):
self.value = self._value[0] = value
self.master.itemconfigure(self.text, text=self._value[0])
def append(self, value): # type: (str) -> None
def append(self, value): # type: (Entry | Text, str) -> None
""" 添加输入框的值 """
event = tkinter.Event()
event.keysym = None
......@@ -1308,11 +1305,11 @@ class Progressbar(BaseWidget):
def load(self, percentage): # type: (float) -> None
"""
### 加载
`percentage`: 进度条的值,范围 0~1
`percentage`: 进度条的值,范围 0~1,大于 1 的值将被视为 1
"""
percentage = 0 if percentage < 0 else 1 if percentage > 1 else percentage
if self.mode == 'determinate':
self.configure(text='%.2f%%' % (percentage * 100))
self.configure(text=f'{percentage*100:.2f}%')
x1, x2 = 0, self.width * percentage
elif self.mode == 'indeterminate':
length = percentage * self.width * 4 / 3
......@@ -1456,7 +1453,7 @@ class ToolTip:
highlightthickness=self.highlightthickness, highlightbackground=self.highlightbackground)
self.toplevel.overrideredirect(True)
x, y = self.toplevel.winfo_pointerxy()
self.toplevel.geometry('+%d+%d' % (x, y+26))
self.toplevel.geometry(f'+{x}+{y+26}')
tkinter.Label(self.toplevel, text=self.text, font=self.font,
fg=self.fg, bg=self.bg, justify=self.justify).pack()
self.toplevel.after(self.duration, self._destroy)
......@@ -1482,7 +1479,7 @@ class PhotoImage(tkinter.PhotoImage):
"""
self.file = file # 图片文件的路径
self.extension = file.rsplit('.', 1)[-1] # 文件扩展名
self._item = {} # type: dict[tkinter._CanvasItemId, Canvas | None]
self._item = {} # type: dict[int, Canvas | None]
if self.extension == 'gif': # 动态图片
self.image = [] # type: list[tkinter.PhotoImage]
......@@ -1499,19 +1496,13 @@ class PhotoImage(tkinter.PhotoImage):
try:
while True:
self.image.append(tkinter.PhotoImage(
file=self.file, format='gif -index %d' % start))
file=self.file, format=f'gif -index {start}'))
value = yield start # 抛出索引
start += value if value else 1
except tkinter.TclError:
return
def play(
self,
canvas, # type: Canvas
item, # type: tkinter._CanvasItemId
interval, # type: int
**kw
): # type: (...) -> None
def play(self, canvas, item, interval, **kw): # type: (Canvas, int, int, ...) -> None
"""
### 播放动图
播放动图,设置 canvas.lock 为 False 会暂停 \n
......@@ -1530,11 +1521,7 @@ class PhotoImage(tkinter.PhotoImage):
canvas.after(interval, lambda: self.play( # 迭代执行函数
canvas, item, interval, _ind=0 if _ind == len(self.image) else _ind))
def stop(
self,
item, # type: tkinter._CanvasItemId
clear=False # type: bool
): # type: (...) -> None
def stop(self, item, clear=False): # type: (int, bool) -> None
"""
### 终止播放
终止对应动图的播放,且无法重新播放 \n
......@@ -1546,13 +1533,8 @@ class PhotoImage(tkinter.PhotoImage):
if clear: # 清除背景
self._item[item].itemconfigure(item, image=None)
def zoom(
self,
rate_x, # type: float
rate_y, # type: float
*,
precision=None # type: float | None
): # type: (...) -> tkinter.PhotoImage
def zoom(self, rate_x, rate_y, *, precision=None):
# type: (float, float, ..., float | None) -> tkinter.PhotoImage
"""
### 缩放图片
不会缩放该图片对象本身,只是返回一个缩放后的图片对象 \n
......@@ -1568,8 +1550,10 @@ class PhotoImage(tkinter.PhotoImage):
return ImageTk.PhotoImage(image)
if precision is not None:
limit = round(10**precision)
rate_x = Fraction(str(rate_x)).limit_denominator(limit)
rate_y = Fraction(str(rate_y)).limit_denominator(limit)
rate_x = Fraction(str(rate_x)).limit_denominator(
limit) # type: Fraction
rate_y = Fraction(str(rate_y)).limit_denominator(
limit) # type: Fraction
image = tkinter.PhotoImage.zoom(
self, rate_x.numerator, rate_y.numerator)
image = image.subsample(rate_x.denominator, rate_y.denominator)
......@@ -1578,8 +1562,8 @@ class PhotoImage(tkinter.PhotoImage):
image = tkinter.PhotoImage(width=width, height=height)
for x in range(width):
for y in range(height):
image.put('#%02X%02X%02X' % self.get(
int(x/rate_x), int(y/rate_y)), (x, y))
r, g, b = self.get(int(x/rate_x), int(y/rate_y))
image.put(f'#{r:02X}{g:02X}{b:02X}', (x, y))
return image
......@@ -1662,14 +1646,14 @@ class Animation:
""" 平移 """
if isinstance(self.widget, (tkinter.Tk, tkinter.Toplevel)): # 窗口
size, x, y = self.widget.geometry().split('+')
self.widget.geometry('%s+%d+%d' % (size, int(x)+dx, int(y)+dy))
self.widget.geometry(f'{size}+{int(x)+dx}+{int(y)+dy}')
elif isinstance(self.widget, tkinter.Widget): # tkinter 控件
place_info = self.widget.place_info()
origin_x, origin_y = float(place_info['x']), float(place_info['y'])
self.widget.place(x=origin_x+dx, y=origin_y+dy)
elif isinstance(self.widget, BaseWidget): # tkintertools 控件
self.widget.move(dx, dy)
elif isinstance(self.widget, int): # tkinter._CanvasItemId
elif isinstance(self.widget, int): # int
self.master.move(self.widget, dx, dy)
def run(self): # type: () -> None
......@@ -1678,30 +1662,6 @@ class Animation:
self._run()
def text(
length, # type: int
string, # type: str
position='center' # type: Literal['left', 'center', 'right']
): # type: (...) -> str
"""
### 文本对齐函数
可将目标字符串改为目标长度并居中对齐 \
ASCII 字符算 1 个长度,中文及其他字符算 2 个
---
`length`: 目标长度 \
`string`: 要修改的字符串 \
`position`: 文本处于该长度范围的位置,可选 left(靠左)、center(居中)和 right(靠右)这三个值
"""
length -= sum(1 + (ord(i) >= 256) for i in string) # 计算空格总个数
if position == 'left': # 靠左
return ' '*length+string
elif position == 'right': # 靠右
return string+length*' '
else: # 居中
length, key = divmod(length, 2)
return ' '*length+string+(length+key)*' '
@overload
def color(
color, # type: Iterable[str]
......@@ -1730,10 +1690,13 @@ def color(
`color`: 颜色元组或列表 (初始颜色, 目标颜色),或者一个颜色字符串(此时返回其对比色) \
`proportion`: 改变比例(浮点数,范围为 0~1)
"""
if not 0 <= proportion <= 1:
raise ColorArgsValueError(proportion)
rgb, _rgb = [[None]*3, [None]*3], 0
if isinstance(color, str): # 对比色的情况处理
color = color, '#%06X' % (16777216-int(color[1:], 16))
color = color, f'{16777216-int(color[1:], 16):06X}'
for i, c in enumerate(color): # 解析颜色的 RGB
_ = int(c[1:], 16)
......@@ -1744,7 +1707,7 @@ def color(
_rgb <<= 8
_rgb += c + round((_c - c) * proportion)
return '#%06X' % _rgb
return f'{_rgb:06X}'
def askfont(
......
""" All exceptions """
class ScaleArgsValueError(ValueError):
""" 缩放函数参数值错误 """
def __init__(self, value): # type: (float) -> None
self.value = value
def __str__(self): # type: () -> None
return f'The scaling factor should be a positive floating-point number, not {self.value}'
class ColorArgsValueError(ValueError):
""" 颜色函数参数值错误 """
def __init__(self, value): # type: (float) -> None
self.value = value
def __str__(self): # type: () -> None
return f'The parameter proportion should be a floating-point number between 0~1, not {self.value}'
class WidgetStateModeError(ValueError):
""" 控件状态模式错误 """
def __init__(self, value): # type: (str) -> None
self.value = value
def __str__(self): # type: () -> None
return f'The mode can only be "normal", "touch", "click" or "disabled", not "{self.value}"'
......@@ -7,6 +7,7 @@ from typing import Iterable, overload # 类型提示
from .__main__ import Canvas, Tk, Toplevel # 继承和类型提示
from .constants import * # 常量
from .exceptions import * # 异常
class Canvas_3D(Canvas):
......@@ -252,8 +253,9 @@ def scale(coordinate, kx=1, ky=1, kz=1, *, center):
`kz`: z 方向缩放比例 \
`center`: 缩放中心的空间坐标
"""
if kx <= 0 or ky <= 0 or kz <= 0: # 限制缩放比例的范围
raise ValueError('invalid scaling factor')
for k in kx, ky, kz:
if k <= 0:
raise ScaleArgsValueError(k)
for i, k in enumerate((kx, ky, kz)):
coordinate[i] += (coordinate[i] - center[i]) * (k - 1)
......@@ -320,7 +322,7 @@ class _3D_Object:
### 投影
`distance`: 对象与观察者的距离
"""
# NOTE: 这里可能需要一些优化
# TODO: 这里可能需要完善
class _Point(_3D_Object):
......@@ -333,7 +335,7 @@ class _Point(_3D_Object):
# override: 具体的实现
relative_dis = distance - self.coordinates[0][0]
if relative_dis <= 1e-16:
return [float('inf')]*2 # BUG: 目前超出范围只能让其消失
return [float('inf')]*2 # XXX: 目前超出范围只能让其消失,需要优化
k = distance/relative_dis
return [self.coordinates[0][1]*k, self.coordinates[0][2]*k]
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册