easypyui 2.2.0__tar.gz → 2.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- easypyui-2.4.0/LICENSE +9 -0
- easypyui-2.4.0/PKG-INFO +199 -0
- easypyui-2.4.0/README.md +179 -0
- {easypyui-2.2.0 → easypyui-2.4.0}/easypyui/__init__.py +17 -1
- {easypyui-2.2.0 → easypyui-2.4.0}/easypyui/core.py +6 -0
- easypyui-2.4.0/easypyui/largeapp.py +138 -0
- easypyui-2.4.0/easypyui/productivity.py +142 -0
- easypyui-2.4.0/easypyui.egg-info/PKG-INFO +199 -0
- {easypyui-2.2.0 → easypyui-2.4.0}/easypyui.egg-info/SOURCES.txt +3 -0
- easypyui-2.4.0/pyproject.toml +29 -0
- easypyui-2.2.0/PKG-INFO +0 -7
- easypyui-2.2.0/README.md +0 -49
- easypyui-2.2.0/easypyui.egg-info/PKG-INFO +0 -7
- easypyui-2.2.0/pyproject.toml +0 -14
- {easypyui-2.2.0 → easypyui-2.4.0}/easypyui/animation.py +0 -0
- {easypyui-2.2.0 → easypyui-2.4.0}/easypyui/modern.py +0 -0
- {easypyui-2.2.0 → easypyui-2.4.0}/easypyui.egg-info/dependency_links.txt +0 -0
- {easypyui-2.2.0 → easypyui-2.4.0}/easypyui.egg-info/top_level.txt +0 -0
- {easypyui-2.2.0 → easypyui-2.4.0}/setup.cfg +0 -0
easypyui-2.4.0/LICENSE
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 EasyPyUI
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software.
|
easypyui-2.4.0/PKG-INFO
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: easypyui
|
|
3
|
+
Version: 2.4.0
|
|
4
|
+
Summary: A simple and modern Python GUI library built on tkinter and ttk
|
|
5
|
+
Author: EasyPyUI
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://pypi.org/project/easypyui/
|
|
8
|
+
Keywords: gui,tkinter,ttk,desktop,ui,python-gui
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
15
|
+
Classifier: Topic :: Software Development :: User Interfaces
|
|
16
|
+
Requires-Python: >=3.9
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Dynamic: license-file
|
|
20
|
+
|
|
21
|
+
# EasyPyUI
|
|
22
|
+
|
|
23
|
+
**Build modern Python desktop GUIs with much less code than raw tkinter.**
|
|
24
|
+
|
|
25
|
+
EasyPyUI is a lightweight GUI library built on Python's standard `tkinter` and `ttk`.
|
|
26
|
+
It provides a short beginner-friendly API while keeping access to native Tk widgets.
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install easypyui
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Quick Start
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
from easypyui import *
|
|
38
|
+
|
|
39
|
+
app("Hello EasyPyUI", 500, 400, appearance="dark")
|
|
40
|
+
title("Hello!")
|
|
41
|
+
|
|
42
|
+
name = minput("Name", placeholder="Your name")
|
|
43
|
+
mbutton("Say hello", lambda: notify("Hello " + name.get()))
|
|
44
|
+
|
|
45
|
+
fade_in(400)
|
|
46
|
+
run()
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Modern UI
|
|
50
|
+
|
|
51
|
+
EasyPyUI 2.x includes `mbutton()`, `minput()`, `mswitch()`, `mcard()`,
|
|
52
|
+
Light/Dark appearance and custom accent colors.
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
app("Dashboard", 700, 500, appearance="dark", accent="#8b5cf6")
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Animation
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
ring = progress_ring(20, size=110)
|
|
62
|
+
mbutton("Start", lambda: ring.animate_to(100, 1000))
|
|
63
|
+
fade_in(500)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Easing modes: `linear`, `ease_in`, `ease_out`, `ease_in_out`.
|
|
67
|
+
|
|
68
|
+
## Pages / Router
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
from easypyui import *
|
|
72
|
+
|
|
73
|
+
app("Pages", 500, 350)
|
|
74
|
+
|
|
75
|
+
def home(ui):
|
|
76
|
+
title("Home")
|
|
77
|
+
on("Settings", lambda: go("settings"))
|
|
78
|
+
|
|
79
|
+
def settings(ui):
|
|
80
|
+
title("Settings")
|
|
81
|
+
on("Back", lambda: go("home"))
|
|
82
|
+
|
|
83
|
+
route("home", home)
|
|
84
|
+
route("settings", settings)
|
|
85
|
+
go("home")
|
|
86
|
+
run()
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Background Tasks
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
def work():
|
|
93
|
+
import time
|
|
94
|
+
time.sleep(2)
|
|
95
|
+
return "Done!"
|
|
96
|
+
|
|
97
|
+
run_task(work, done=lambda result: notify(result), loading_text="Working...")
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Native tkinter access
|
|
101
|
+
|
|
102
|
+
Most EasyPyUI wrapper objects expose `.tk`:
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
btn = mbutton("Test", lambda: print("clicked"))
|
|
106
|
+
btn.tk.bind("<Button-2>", lambda event: print("middle click"))
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Classic API
|
|
110
|
+
|
|
111
|
+
The older API remains available, including `window()`, `text()`, `inputbox()`,
|
|
112
|
+
`button()`, `table()`, `canvas()` and `start()`.
|
|
113
|
+
|
|
114
|
+
## Requirements
|
|
115
|
+
|
|
116
|
+
- Python 3.9+
|
|
117
|
+
- tkinter support in your Python installation
|
|
118
|
+
- No mandatory third-party runtime GUI dependency
|
|
119
|
+
|
|
120
|
+
## License
|
|
121
|
+
|
|
122
|
+
MIT License
|
|
123
|
+
|
|
124
|
+
## Version
|
|
125
|
+
|
|
126
|
+
Current release: **2.4.0**
|
|
127
|
+
|
|
128
|
+
2.2.1 is a PyPI metadata and documentation update for the 2.2 series.
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
## 2.3 Productivity APIs
|
|
132
|
+
|
|
133
|
+
### Form validation
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
f = form()
|
|
137
|
+
|
|
138
|
+
email = f.add(
|
|
139
|
+
"Email",
|
|
140
|
+
minput("Email"),
|
|
141
|
+
[Validator.required(), Validator.email()]
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
password_box = f.add(
|
|
145
|
+
"Password",
|
|
146
|
+
minput("Password", password=True),
|
|
147
|
+
[Validator.required(), Validator.min_length(8)]
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
def submit():
|
|
151
|
+
ok, errors = f.validate()
|
|
152
|
+
if ok:
|
|
153
|
+
notify(str(f.values()))
|
|
154
|
+
|
|
155
|
+
mbutton("Submit", submit)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Reactive State
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
count = state(0)
|
|
162
|
+
|
|
163
|
+
label_widget = text("0")
|
|
164
|
+
count.bind_text(label_widget)
|
|
165
|
+
|
|
166
|
+
mbutton("+1", lambda: count.update(lambda n: n + 1))
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Keyboard shortcuts
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
shortcut("ctrl+s", save)
|
|
173
|
+
shortcut("ctrl+q", close)
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Debounce / Throttle
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
search = debounce(300, do_search)
|
|
180
|
+
resize_handler = throttle(100, update_layout)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
## 2.4 Large Application Widgets
|
|
185
|
+
|
|
186
|
+
- `datatable()` — search, sorting and pagination
|
|
187
|
+
- `modal()` — modal window
|
|
188
|
+
- `drawer()` — collapsible side drawer
|
|
189
|
+
- `accordion()` — expandable sections
|
|
190
|
+
- `datepicker()` — date value input
|
|
191
|
+
- `notifications()` — notification center
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
table = datatable(["name", "score"], ["Name", "Score"], page_size=5)
|
|
195
|
+
table.set_rows([
|
|
196
|
+
("Noah", 100),
|
|
197
|
+
("Daniel", 95),
|
|
198
|
+
])
|
|
199
|
+
```
|
easypyui-2.4.0/README.md
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# EasyPyUI
|
|
2
|
+
|
|
3
|
+
**Build modern Python desktop GUIs with much less code than raw tkinter.**
|
|
4
|
+
|
|
5
|
+
EasyPyUI is a lightweight GUI library built on Python's standard `tkinter` and `ttk`.
|
|
6
|
+
It provides a short beginner-friendly API while keeping access to native Tk widgets.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pip install easypyui
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Quick Start
|
|
15
|
+
|
|
16
|
+
```python
|
|
17
|
+
from easypyui import *
|
|
18
|
+
|
|
19
|
+
app("Hello EasyPyUI", 500, 400, appearance="dark")
|
|
20
|
+
title("Hello!")
|
|
21
|
+
|
|
22
|
+
name = minput("Name", placeholder="Your name")
|
|
23
|
+
mbutton("Say hello", lambda: notify("Hello " + name.get()))
|
|
24
|
+
|
|
25
|
+
fade_in(400)
|
|
26
|
+
run()
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Modern UI
|
|
30
|
+
|
|
31
|
+
EasyPyUI 2.x includes `mbutton()`, `minput()`, `mswitch()`, `mcard()`,
|
|
32
|
+
Light/Dark appearance and custom accent colors.
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
app("Dashboard", 700, 500, appearance="dark", accent="#8b5cf6")
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Animation
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
ring = progress_ring(20, size=110)
|
|
42
|
+
mbutton("Start", lambda: ring.animate_to(100, 1000))
|
|
43
|
+
fade_in(500)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Easing modes: `linear`, `ease_in`, `ease_out`, `ease_in_out`.
|
|
47
|
+
|
|
48
|
+
## Pages / Router
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from easypyui import *
|
|
52
|
+
|
|
53
|
+
app("Pages", 500, 350)
|
|
54
|
+
|
|
55
|
+
def home(ui):
|
|
56
|
+
title("Home")
|
|
57
|
+
on("Settings", lambda: go("settings"))
|
|
58
|
+
|
|
59
|
+
def settings(ui):
|
|
60
|
+
title("Settings")
|
|
61
|
+
on("Back", lambda: go("home"))
|
|
62
|
+
|
|
63
|
+
route("home", home)
|
|
64
|
+
route("settings", settings)
|
|
65
|
+
go("home")
|
|
66
|
+
run()
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Background Tasks
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
def work():
|
|
73
|
+
import time
|
|
74
|
+
time.sleep(2)
|
|
75
|
+
return "Done!"
|
|
76
|
+
|
|
77
|
+
run_task(work, done=lambda result: notify(result), loading_text="Working...")
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Native tkinter access
|
|
81
|
+
|
|
82
|
+
Most EasyPyUI wrapper objects expose `.tk`:
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
btn = mbutton("Test", lambda: print("clicked"))
|
|
86
|
+
btn.tk.bind("<Button-2>", lambda event: print("middle click"))
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Classic API
|
|
90
|
+
|
|
91
|
+
The older API remains available, including `window()`, `text()`, `inputbox()`,
|
|
92
|
+
`button()`, `table()`, `canvas()` and `start()`.
|
|
93
|
+
|
|
94
|
+
## Requirements
|
|
95
|
+
|
|
96
|
+
- Python 3.9+
|
|
97
|
+
- tkinter support in your Python installation
|
|
98
|
+
- No mandatory third-party runtime GUI dependency
|
|
99
|
+
|
|
100
|
+
## License
|
|
101
|
+
|
|
102
|
+
MIT License
|
|
103
|
+
|
|
104
|
+
## Version
|
|
105
|
+
|
|
106
|
+
Current release: **2.4.0**
|
|
107
|
+
|
|
108
|
+
2.2.1 is a PyPI metadata and documentation update for the 2.2 series.
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
## 2.3 Productivity APIs
|
|
112
|
+
|
|
113
|
+
### Form validation
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
f = form()
|
|
117
|
+
|
|
118
|
+
email = f.add(
|
|
119
|
+
"Email",
|
|
120
|
+
minput("Email"),
|
|
121
|
+
[Validator.required(), Validator.email()]
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
password_box = f.add(
|
|
125
|
+
"Password",
|
|
126
|
+
minput("Password", password=True),
|
|
127
|
+
[Validator.required(), Validator.min_length(8)]
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
def submit():
|
|
131
|
+
ok, errors = f.validate()
|
|
132
|
+
if ok:
|
|
133
|
+
notify(str(f.values()))
|
|
134
|
+
|
|
135
|
+
mbutton("Submit", submit)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Reactive State
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
count = state(0)
|
|
142
|
+
|
|
143
|
+
label_widget = text("0")
|
|
144
|
+
count.bind_text(label_widget)
|
|
145
|
+
|
|
146
|
+
mbutton("+1", lambda: count.update(lambda n: n + 1))
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Keyboard shortcuts
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
shortcut("ctrl+s", save)
|
|
153
|
+
shortcut("ctrl+q", close)
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Debounce / Throttle
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
search = debounce(300, do_search)
|
|
160
|
+
resize_handler = throttle(100, update_layout)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
## 2.4 Large Application Widgets
|
|
165
|
+
|
|
166
|
+
- `datatable()` — search, sorting and pagination
|
|
167
|
+
- `modal()` — modal window
|
|
168
|
+
- `drawer()` — collapsible side drawer
|
|
169
|
+
- `accordion()` — expandable sections
|
|
170
|
+
- `datepicker()` — date value input
|
|
171
|
+
- `notifications()` — notification center
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
table = datatable(["name", "score"], ["Name", "Score"], page_size=5)
|
|
175
|
+
table.set_rows([
|
|
176
|
+
("Noah", 100),
|
|
177
|
+
("Daniel", 95),
|
|
178
|
+
])
|
|
179
|
+
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from .core import App, Value, Widget, Table, Canvas, Container, Row
|
|
2
2
|
|
|
3
|
-
__version__ = "2.
|
|
3
|
+
__version__ = "2.4.0"
|
|
4
4
|
|
|
5
5
|
_current = None
|
|
6
6
|
|
|
@@ -103,3 +103,19 @@ def progress_ring(*a, **k): return _app().progress_ring(*a, **k)
|
|
|
103
103
|
def fade_in(*a, **k): return _app().fade_in(*a, **k)
|
|
104
104
|
def fade_out(*a, **k): return _app().fade_out(*a, **k)
|
|
105
105
|
def animate(*a, **k): return _app().animate(*a, **k)
|
|
106
|
+
|
|
107
|
+
from .productivity import Validator, Form, State
|
|
108
|
+
|
|
109
|
+
def form(): return _app().form()
|
|
110
|
+
def state(value=None): return _app().state(value)
|
|
111
|
+
def shortcut(keys, callback): return _app().shortcut(keys, callback)
|
|
112
|
+
def debounce(wait_ms, func): return _app().debounce(wait_ms, func)
|
|
113
|
+
def throttle(wait_ms, func): return _app().throttle(wait_ms, func)
|
|
114
|
+
|
|
115
|
+
from .largeapp import Modal, Drawer, Accordion, DataTable, DatePicker, NotificationCenter
|
|
116
|
+
def modal(*a, **k): return _app().modal(*a, **k)
|
|
117
|
+
def drawer(*a, **k): return _app().drawer(*a, **k)
|
|
118
|
+
def accordion(*a, **k): return _app().accordion(*a, **k)
|
|
119
|
+
def datatable(*a, **k): return _app().datatable(*a, **k)
|
|
120
|
+
def datepicker(*a, **k): return _app().datepicker(*a, **k)
|
|
121
|
+
def notifications(): return _app().notifications()
|
|
@@ -1190,3 +1190,9 @@ install_modern_api(App, _Builder)
|
|
|
1190
1190
|
|
|
1191
1191
|
from .animation import install_animation_api
|
|
1192
1192
|
install_animation_api(App, _Builder)
|
|
1193
|
+
|
|
1194
|
+
from .productivity import install_productivity_api
|
|
1195
|
+
install_productivity_api(App, _Builder)
|
|
1196
|
+
|
|
1197
|
+
from .largeapp import install_largeapp_api
|
|
1198
|
+
install_largeapp_api(App, _Builder)
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import tkinter as tk
|
|
2
|
+
from tkinter import ttk
|
|
3
|
+
from datetime import date
|
|
4
|
+
|
|
5
|
+
class Modal:
|
|
6
|
+
def __init__(self, app, title="Modal", width=420, height=260):
|
|
7
|
+
self.app=app
|
|
8
|
+
self.win=tk.Toplevel(app.root)
|
|
9
|
+
self.win.title(title); self.win.geometry(f"{width}x{height}")
|
|
10
|
+
self.win.transient(app.root); self.win.grab_set()
|
|
11
|
+
self.body=tk.Frame(self.win, padx=16, pady=16)
|
|
12
|
+
self.body.pack(fill="both", expand=True)
|
|
13
|
+
self.tk=self.win
|
|
14
|
+
def close(self):
|
|
15
|
+
try: self.win.grab_release()
|
|
16
|
+
except Exception: pass
|
|
17
|
+
self.win.destroy()
|
|
18
|
+
|
|
19
|
+
class Drawer:
|
|
20
|
+
def __init__(self, app, width=260, side="left"):
|
|
21
|
+
self.app=app; self.width=width; self.side=side
|
|
22
|
+
self.frame=tk.Frame(app.root, bd=1, relief="solid")
|
|
23
|
+
self.opened=False; self.tk=self.frame
|
|
24
|
+
def open(self):
|
|
25
|
+
self.frame.pack(side=self.side, fill="y", before=self.app.body)
|
|
26
|
+
self.opened=True; return self
|
|
27
|
+
def close(self):
|
|
28
|
+
self.frame.pack_forget(); self.opened=False; return self
|
|
29
|
+
def toggle(self): return self.close() if self.opened else self.open()
|
|
30
|
+
|
|
31
|
+
class Accordion:
|
|
32
|
+
def __init__(self,parent):
|
|
33
|
+
self.frame=ttk.Frame(parent); self.frame.pack(fill="x", pady=3); self.sections=[]
|
|
34
|
+
self.tk=self.frame
|
|
35
|
+
def add(self,title,builder=None,opened=False):
|
|
36
|
+
head=ttk.Button(self.frame,text=title)
|
|
37
|
+
body=ttk.Frame(self.frame,padding=8)
|
|
38
|
+
def toggle():
|
|
39
|
+
if body.winfo_ismapped(): body.pack_forget()
|
|
40
|
+
else: body.pack(fill="x")
|
|
41
|
+
head.configure(command=toggle); head.pack(fill="x")
|
|
42
|
+
if builder: builder(body)
|
|
43
|
+
if opened: body.pack(fill="x")
|
|
44
|
+
self.sections.append((head,body)); return body
|
|
45
|
+
|
|
46
|
+
class DataTable:
|
|
47
|
+
def __init__(self,parent,columns,headings=None,page_size=10):
|
|
48
|
+
self.columns=list(columns); self.page_size=max(1,page_size); self.page=0
|
|
49
|
+
self.all_rows=[]; self.filtered=[]; self.sort_col=None; self.sort_reverse=False
|
|
50
|
+
self.frame=ttk.Frame(parent)
|
|
51
|
+
bar=ttk.Frame(self.frame); bar.pack(fill="x",pady=(0,5))
|
|
52
|
+
self.query=tk.StringVar()
|
|
53
|
+
ent=ttk.Entry(bar,textvariable=self.query); ent.pack(side="left",fill="x",expand=True)
|
|
54
|
+
ttk.Button(bar,text="搜尋",command=self.refresh).pack(side="left",padx=4)
|
|
55
|
+
self.tree=ttk.Treeview(self.frame,columns=self.columns,show="headings")
|
|
56
|
+
self.tree.pack(fill="both",expand=True)
|
|
57
|
+
hs=headings or self.columns
|
|
58
|
+
for col,h in zip(self.columns,hs):
|
|
59
|
+
self.tree.heading(col,text=h,command=lambda c=col:self.sort(c))
|
|
60
|
+
nav=ttk.Frame(self.frame); nav.pack(fill="x",pady=(5,0))
|
|
61
|
+
ttk.Button(nav,text="上一頁",command=self.prev).pack(side="left")
|
|
62
|
+
self.info=ttk.Label(nav,text="1 / 1"); self.info.pack(side="left",padx=8)
|
|
63
|
+
ttk.Button(nav,text="下一頁",command=self.next).pack(side="left")
|
|
64
|
+
self.tk=self.tree
|
|
65
|
+
def pack(self,**kw): self.frame.pack(**kw); return self
|
|
66
|
+
def grid(self,**kw): self.frame.grid(**kw); return self
|
|
67
|
+
def set_rows(self,rows): self.all_rows=[tuple(x) for x in rows]; self.page=0; self.refresh(); return self
|
|
68
|
+
def add(self,row): self.all_rows.append(tuple(row)); self.refresh(); return self
|
|
69
|
+
def refresh(self):
|
|
70
|
+
q=self.query.get().lower().strip()
|
|
71
|
+
self.filtered=[r for r in self.all_rows if not q or q in " ".join(map(str,r)).lower()]
|
|
72
|
+
if self.sort_col in self.columns:
|
|
73
|
+
i=self.columns.index(self.sort_col)
|
|
74
|
+
self.filtered.sort(key=lambda r:str(r[i]).lower(),reverse=self.sort_reverse)
|
|
75
|
+
pages=max(1,(len(self.filtered)+self.page_size-1)//self.page_size)
|
|
76
|
+
self.page=min(self.page,pages-1)
|
|
77
|
+
for x in self.tree.get_children(): self.tree.delete(x)
|
|
78
|
+
start=self.page*self.page_size
|
|
79
|
+
for row in self.filtered[start:start+self.page_size]: self.tree.insert("","end",values=row)
|
|
80
|
+
self.info.configure(text=f"{self.page+1} / {pages}")
|
|
81
|
+
return self
|
|
82
|
+
def sort(self,col):
|
|
83
|
+
if self.sort_col==col: self.sort_reverse=not self.sort_reverse
|
|
84
|
+
else: self.sort_col=col; self.sort_reverse=False
|
|
85
|
+
return self.refresh()
|
|
86
|
+
def next(self):
|
|
87
|
+
pages=max(1,(len(self.filtered)+self.page_size-1)//self.page_size)
|
|
88
|
+
if self.page+1<pages: self.page+=1; self.refresh()
|
|
89
|
+
def prev(self):
|
|
90
|
+
if self.page>0: self.page-=1; self.refresh()
|
|
91
|
+
def selected(self):
|
|
92
|
+
ids=self.tree.selection()
|
|
93
|
+
return self.tree.item(ids[0],"values") if ids else None
|
|
94
|
+
def rows(self): return list(self.filtered)
|
|
95
|
+
|
|
96
|
+
class DatePicker:
|
|
97
|
+
def __init__(self,parent,default=None):
|
|
98
|
+
self.var=tk.StringVar(value=default or date.today().isoformat())
|
|
99
|
+
self.entry=ttk.Entry(parent,textvariable=self.var,width=14)
|
|
100
|
+
self.tk=self.entry
|
|
101
|
+
def get(self): return self.var.get()
|
|
102
|
+
def set(self,value): self.var.set(str(value)); return self
|
|
103
|
+
def pack(self,**kw): self.entry.pack(**kw); return self
|
|
104
|
+
def grid(self,**kw): self.entry.grid(**kw); return self
|
|
105
|
+
|
|
106
|
+
class NotificationCenter:
|
|
107
|
+
def __init__(self,app):
|
|
108
|
+
self.app=app; self.items=[]
|
|
109
|
+
def add(self,text,kind="info",title=None):
|
|
110
|
+
item={"text":text,"kind":kind,"title":title}
|
|
111
|
+
self.items.append(item); self.app.notify(text,title=title); return item
|
|
112
|
+
def clear(self): self.items.clear(); return self
|
|
113
|
+
def unread(self): return len(self.items)
|
|
114
|
+
|
|
115
|
+
def install_largeapp_api(App,Builder):
|
|
116
|
+
def modal(self,title="Modal",width=420,height=260): return Modal(self,title,width,height)
|
|
117
|
+
def drawer(self,width=260,side="left"): return Drawer(self,width,side)
|
|
118
|
+
def notifications(self):
|
|
119
|
+
if not hasattr(self,"_notifications"): self._notifications=NotificationCenter(self)
|
|
120
|
+
return self._notifications
|
|
121
|
+
App.modal=modal; App.drawer=drawer; App.notifications=notifications
|
|
122
|
+
|
|
123
|
+
def accordion(self,layout="pack",**kwargs):
|
|
124
|
+
w=Accordion(self._parent); return w
|
|
125
|
+
def datatable(self,columns,headings=None,page_size=10,layout="pack",**kwargs):
|
|
126
|
+
w=DataTable(self._parent,columns,headings,page_size)
|
|
127
|
+
opts=kwargs.pop("layout_opts",{})
|
|
128
|
+
if layout=="grid": w.grid(**opts)
|
|
129
|
+
elif layout!="none":
|
|
130
|
+
d={"fill":"both","expand":True,"pady":5}; d.update(opts); w.pack(**d)
|
|
131
|
+
return w
|
|
132
|
+
def datepicker(self,default=None,layout="pack",**kwargs):
|
|
133
|
+
w=DatePicker(self._parent,default); opts=kwargs.pop("layout_opts",{})
|
|
134
|
+
if layout=="grid": w.grid(**opts)
|
|
135
|
+
elif layout!="none":
|
|
136
|
+
d={"anchor":"w","pady":4}; d.update(opts); w.pack(**d)
|
|
137
|
+
return w
|
|
138
|
+
Builder.accordion=accordion; Builder.datatable=datatable; Builder.datepicker=datepicker
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import time
|
|
3
|
+
|
|
4
|
+
class Validator:
|
|
5
|
+
@staticmethod
|
|
6
|
+
def required(message="此欄位必填"):
|
|
7
|
+
return lambda value: (bool(str(value).strip()), message)
|
|
8
|
+
|
|
9
|
+
@staticmethod
|
|
10
|
+
def min_length(length, message=None):
|
|
11
|
+
msg = message or f"至少需要 {length} 個字元"
|
|
12
|
+
return lambda value: (len(str(value)) >= length, msg)
|
|
13
|
+
|
|
14
|
+
@staticmethod
|
|
15
|
+
def max_length(length, message=None):
|
|
16
|
+
msg = message or f"最多 {length} 個字元"
|
|
17
|
+
return lambda value: (len(str(value)) <= length, msg)
|
|
18
|
+
|
|
19
|
+
@staticmethod
|
|
20
|
+
def email(message="Email 格式不正確"):
|
|
21
|
+
pattern = re.compile(r"^[^@\s]+@[^@\s]+\.[^@\s]+$")
|
|
22
|
+
return lambda value: (bool(pattern.match(str(value))), message)
|
|
23
|
+
|
|
24
|
+
@staticmethod
|
|
25
|
+
def number(message="必須是數字"):
|
|
26
|
+
def check(value):
|
|
27
|
+
try:
|
|
28
|
+
float(value); return True, message
|
|
29
|
+
except (TypeError, ValueError):
|
|
30
|
+
return False, message
|
|
31
|
+
return check
|
|
32
|
+
|
|
33
|
+
class Form:
|
|
34
|
+
def __init__(self, app):
|
|
35
|
+
self.app = app
|
|
36
|
+
self.fields = {}
|
|
37
|
+
|
|
38
|
+
def add(self, name, widget, validators=None):
|
|
39
|
+
self.fields[name] = (widget, list(validators or []))
|
|
40
|
+
return widget
|
|
41
|
+
|
|
42
|
+
def values(self):
|
|
43
|
+
return {name: widget.get() for name, (widget, _) in self.fields.items()}
|
|
44
|
+
|
|
45
|
+
def validate(self, show_error=True):
|
|
46
|
+
errors = {}
|
|
47
|
+
for name, (widget, validators) in self.fields.items():
|
|
48
|
+
value = widget.get()
|
|
49
|
+
for validator in validators:
|
|
50
|
+
ok, message = validator(value)
|
|
51
|
+
if not ok:
|
|
52
|
+
errors[name] = message
|
|
53
|
+
break
|
|
54
|
+
if errors and show_error:
|
|
55
|
+
text = "\n".join(f"{name}: {msg}" for name, msg in errors.items())
|
|
56
|
+
self.app.warning(text, title="資料檢查")
|
|
57
|
+
return not errors, errors
|
|
58
|
+
|
|
59
|
+
def clear(self):
|
|
60
|
+
for widget, _ in self.fields.values():
|
|
61
|
+
try: widget.set("")
|
|
62
|
+
except Exception: pass
|
|
63
|
+
return self
|
|
64
|
+
|
|
65
|
+
class State:
|
|
66
|
+
def __init__(self, value=None):
|
|
67
|
+
self._value = value
|
|
68
|
+
self._listeners = []
|
|
69
|
+
|
|
70
|
+
def get(self):
|
|
71
|
+
return self._value
|
|
72
|
+
|
|
73
|
+
def set(self, value):
|
|
74
|
+
if value == self._value:
|
|
75
|
+
return self
|
|
76
|
+
self._value = value
|
|
77
|
+
for callback in list(self._listeners):
|
|
78
|
+
callback(value)
|
|
79
|
+
return self
|
|
80
|
+
|
|
81
|
+
def update(self, func):
|
|
82
|
+
return self.set(func(self._value))
|
|
83
|
+
|
|
84
|
+
def watch(self, callback, immediate=False):
|
|
85
|
+
self._listeners.append(callback)
|
|
86
|
+
if immediate:
|
|
87
|
+
callback(self._value)
|
|
88
|
+
return lambda: self.unwatch(callback)
|
|
89
|
+
|
|
90
|
+
def unwatch(self, callback):
|
|
91
|
+
if callback in self._listeners:
|
|
92
|
+
self._listeners.remove(callback)
|
|
93
|
+
|
|
94
|
+
def bind_text(self, widget):
|
|
95
|
+
def apply(value):
|
|
96
|
+
try: widget.set(value)
|
|
97
|
+
except Exception:
|
|
98
|
+
try: widget.config(text=str(value))
|
|
99
|
+
except Exception: pass
|
|
100
|
+
self.watch(apply, immediate=True)
|
|
101
|
+
return self
|
|
102
|
+
|
|
103
|
+
def install_productivity_api(App, Builder):
|
|
104
|
+
def form(self):
|
|
105
|
+
return Form(self)
|
|
106
|
+
App.form = form
|
|
107
|
+
|
|
108
|
+
def state(self, value=None):
|
|
109
|
+
return State(value)
|
|
110
|
+
App.state = state
|
|
111
|
+
|
|
112
|
+
def shortcut(self, keys, callback):
|
|
113
|
+
sequence = keys
|
|
114
|
+
if not str(keys).startswith("<"):
|
|
115
|
+
parts = str(keys).lower().replace("+", "-").split("-")
|
|
116
|
+
names = {"ctrl":"Control", "control":"Control", "alt":"Alt",
|
|
117
|
+
"shift":"Shift", "enter":"Return", "esc":"Escape",
|
|
118
|
+
"escape":"Escape", "space":"space", "tab":"Tab"}
|
|
119
|
+
sequence = "<" + "-".join(names.get(x, x) for x in parts) + ">"
|
|
120
|
+
self.root.bind(sequence, lambda event: callback())
|
|
121
|
+
return self
|
|
122
|
+
App.shortcut = shortcut
|
|
123
|
+
|
|
124
|
+
def debounce(self, wait_ms, func):
|
|
125
|
+
holder = {"id": None}
|
|
126
|
+
def wrapped(*args, **kwargs):
|
|
127
|
+
if holder["id"] is not None:
|
|
128
|
+
try: self.root.after_cancel(holder["id"])
|
|
129
|
+
except Exception: pass
|
|
130
|
+
holder["id"] = self.root.after(wait_ms, lambda: func(*args, **kwargs))
|
|
131
|
+
return wrapped
|
|
132
|
+
App.debounce = debounce
|
|
133
|
+
|
|
134
|
+
def throttle(self, wait_ms, func):
|
|
135
|
+
last = {"time": 0.0}
|
|
136
|
+
def wrapped(*args, **kwargs):
|
|
137
|
+
now = time.monotonic() * 1000
|
|
138
|
+
if now - last["time"] >= wait_ms:
|
|
139
|
+
last["time"] = now
|
|
140
|
+
return func(*args, **kwargs)
|
|
141
|
+
return wrapped
|
|
142
|
+
App.throttle = throttle
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: easypyui
|
|
3
|
+
Version: 2.4.0
|
|
4
|
+
Summary: A simple and modern Python GUI library built on tkinter and ttk
|
|
5
|
+
Author: EasyPyUI
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://pypi.org/project/easypyui/
|
|
8
|
+
Keywords: gui,tkinter,ttk,desktop,ui,python-gui
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
15
|
+
Classifier: Topic :: Software Development :: User Interfaces
|
|
16
|
+
Requires-Python: >=3.9
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Dynamic: license-file
|
|
20
|
+
|
|
21
|
+
# EasyPyUI
|
|
22
|
+
|
|
23
|
+
**Build modern Python desktop GUIs with much less code than raw tkinter.**
|
|
24
|
+
|
|
25
|
+
EasyPyUI is a lightweight GUI library built on Python's standard `tkinter` and `ttk`.
|
|
26
|
+
It provides a short beginner-friendly API while keeping access to native Tk widgets.
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install easypyui
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Quick Start
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
from easypyui import *
|
|
38
|
+
|
|
39
|
+
app("Hello EasyPyUI", 500, 400, appearance="dark")
|
|
40
|
+
title("Hello!")
|
|
41
|
+
|
|
42
|
+
name = minput("Name", placeholder="Your name")
|
|
43
|
+
mbutton("Say hello", lambda: notify("Hello " + name.get()))
|
|
44
|
+
|
|
45
|
+
fade_in(400)
|
|
46
|
+
run()
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Modern UI
|
|
50
|
+
|
|
51
|
+
EasyPyUI 2.x includes `mbutton()`, `minput()`, `mswitch()`, `mcard()`,
|
|
52
|
+
Light/Dark appearance and custom accent colors.
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
app("Dashboard", 700, 500, appearance="dark", accent="#8b5cf6")
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Animation
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
ring = progress_ring(20, size=110)
|
|
62
|
+
mbutton("Start", lambda: ring.animate_to(100, 1000))
|
|
63
|
+
fade_in(500)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Easing modes: `linear`, `ease_in`, `ease_out`, `ease_in_out`.
|
|
67
|
+
|
|
68
|
+
## Pages / Router
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
from easypyui import *
|
|
72
|
+
|
|
73
|
+
app("Pages", 500, 350)
|
|
74
|
+
|
|
75
|
+
def home(ui):
|
|
76
|
+
title("Home")
|
|
77
|
+
on("Settings", lambda: go("settings"))
|
|
78
|
+
|
|
79
|
+
def settings(ui):
|
|
80
|
+
title("Settings")
|
|
81
|
+
on("Back", lambda: go("home"))
|
|
82
|
+
|
|
83
|
+
route("home", home)
|
|
84
|
+
route("settings", settings)
|
|
85
|
+
go("home")
|
|
86
|
+
run()
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Background Tasks
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
def work():
|
|
93
|
+
import time
|
|
94
|
+
time.sleep(2)
|
|
95
|
+
return "Done!"
|
|
96
|
+
|
|
97
|
+
run_task(work, done=lambda result: notify(result), loading_text="Working...")
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Native tkinter access
|
|
101
|
+
|
|
102
|
+
Most EasyPyUI wrapper objects expose `.tk`:
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
btn = mbutton("Test", lambda: print("clicked"))
|
|
106
|
+
btn.tk.bind("<Button-2>", lambda event: print("middle click"))
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Classic API
|
|
110
|
+
|
|
111
|
+
The older API remains available, including `window()`, `text()`, `inputbox()`,
|
|
112
|
+
`button()`, `table()`, `canvas()` and `start()`.
|
|
113
|
+
|
|
114
|
+
## Requirements
|
|
115
|
+
|
|
116
|
+
- Python 3.9+
|
|
117
|
+
- tkinter support in your Python installation
|
|
118
|
+
- No mandatory third-party runtime GUI dependency
|
|
119
|
+
|
|
120
|
+
## License
|
|
121
|
+
|
|
122
|
+
MIT License
|
|
123
|
+
|
|
124
|
+
## Version
|
|
125
|
+
|
|
126
|
+
Current release: **2.4.0**
|
|
127
|
+
|
|
128
|
+
2.2.1 is a PyPI metadata and documentation update for the 2.2 series.
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
## 2.3 Productivity APIs
|
|
132
|
+
|
|
133
|
+
### Form validation
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
f = form()
|
|
137
|
+
|
|
138
|
+
email = f.add(
|
|
139
|
+
"Email",
|
|
140
|
+
minput("Email"),
|
|
141
|
+
[Validator.required(), Validator.email()]
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
password_box = f.add(
|
|
145
|
+
"Password",
|
|
146
|
+
minput("Password", password=True),
|
|
147
|
+
[Validator.required(), Validator.min_length(8)]
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
def submit():
|
|
151
|
+
ok, errors = f.validate()
|
|
152
|
+
if ok:
|
|
153
|
+
notify(str(f.values()))
|
|
154
|
+
|
|
155
|
+
mbutton("Submit", submit)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Reactive State
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
count = state(0)
|
|
162
|
+
|
|
163
|
+
label_widget = text("0")
|
|
164
|
+
count.bind_text(label_widget)
|
|
165
|
+
|
|
166
|
+
mbutton("+1", lambda: count.update(lambda n: n + 1))
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Keyboard shortcuts
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
shortcut("ctrl+s", save)
|
|
173
|
+
shortcut("ctrl+q", close)
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Debounce / Throttle
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
search = debounce(300, do_search)
|
|
180
|
+
resize_handler = throttle(100, update_layout)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
## 2.4 Large Application Widgets
|
|
185
|
+
|
|
186
|
+
- `datatable()` — search, sorting and pagination
|
|
187
|
+
- `modal()` — modal window
|
|
188
|
+
- `drawer()` — collapsible side drawer
|
|
189
|
+
- `accordion()` — expandable sections
|
|
190
|
+
- `datepicker()` — date value input
|
|
191
|
+
- `notifications()` — notification center
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
table = datatable(["name", "score"], ["Name", "Score"], page_size=5)
|
|
195
|
+
table.set_rows([
|
|
196
|
+
("Noah", 100),
|
|
197
|
+
("Daniel", 95),
|
|
198
|
+
])
|
|
199
|
+
```
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
LICENSE
|
|
1
2
|
README.md
|
|
2
3
|
pyproject.toml
|
|
3
4
|
easypyui/__init__.py
|
|
4
5
|
easypyui/animation.py
|
|
5
6
|
easypyui/core.py
|
|
7
|
+
easypyui/largeapp.py
|
|
6
8
|
easypyui/modern.py
|
|
9
|
+
easypyui/productivity.py
|
|
7
10
|
easypyui.egg-info/PKG-INFO
|
|
8
11
|
easypyui.egg-info/SOURCES.txt
|
|
9
12
|
easypyui.egg-info/dependency_links.txt
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "easypyui"
|
|
7
|
+
version = "2.4.0"
|
|
8
|
+
description = "A simple and modern Python GUI library built on tkinter and ttk"
|
|
9
|
+
readme = {file = "README.md", content-type = "text/markdown"}
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = {text = "MIT"}
|
|
12
|
+
authors = [{name = "EasyPyUI"}]
|
|
13
|
+
keywords = ["gui", "tkinter", "ttk", "desktop", "ui", "python-gui"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
21
|
+
"Topic :: Software Development :: User Interfaces"
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[project.urls]
|
|
25
|
+
Homepage = "https://pypi.org/project/easypyui/"
|
|
26
|
+
|
|
27
|
+
[tool.setuptools.packages.find]
|
|
28
|
+
where = ["."]
|
|
29
|
+
include = ["easypyui*"]
|
easypyui-2.2.0/PKG-INFO
DELETED
easypyui-2.2.0/README.md
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
# EasyPyUI 2.2 — Animation
|
|
2
|
-
|
|
3
|
-
2.2 加入輕量動畫引擎,沒有新增第三方依賴。
|
|
4
|
-
|
|
5
|
-
## 新功能
|
|
6
|
-
|
|
7
|
-
- `fade_in()` / `fade_out()`
|
|
8
|
-
- `animate(..., kind="slide")`
|
|
9
|
-
- easing:linear / ease_in / ease_out / ease_in_out
|
|
10
|
-
- `progress_ring()` 圓形進度
|
|
11
|
-
- `ring.animate_to(80)`
|
|
12
|
-
- Animation 可 `.cancel()`
|
|
13
|
-
- 2.1 現代 UI、2.0 Router、1.x API 全部保留
|
|
14
|
-
|
|
15
|
-
## Fade
|
|
16
|
-
|
|
17
|
-
```python
|
|
18
|
-
from easypyui import *
|
|
19
|
-
|
|
20
|
-
app("動畫", 500, 400, appearance="dark")
|
|
21
|
-
title("EasyPyUI 2.2")
|
|
22
|
-
mbutton("淡出", lambda: fade_out(500))
|
|
23
|
-
fade_in(500)
|
|
24
|
-
run()
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
## Progress Ring
|
|
28
|
-
|
|
29
|
-
```python
|
|
30
|
-
ring = progress_ring(20, size=110)
|
|
31
|
-
mbutton("到 90%", lambda: ring.animate_to(90, 700))
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
## Slide
|
|
35
|
-
|
|
36
|
-
```python
|
|
37
|
-
btn = mbutton("滑入", lambda: None, layout="none")
|
|
38
|
-
|
|
39
|
-
animate(
|
|
40
|
-
btn,
|
|
41
|
-
kind="slide",
|
|
42
|
-
from_x=-160,
|
|
43
|
-
from_y=180,
|
|
44
|
-
to_x=170,
|
|
45
|
-
to_y=180,
|
|
46
|
-
duration=600,
|
|
47
|
-
easing="ease_out"
|
|
48
|
-
)
|
|
49
|
-
```
|
easypyui-2.2.0/pyproject.toml
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
[build-system]
|
|
2
|
-
requires = ["setuptools>=68", "wheel"]
|
|
3
|
-
build-backend = "setuptools.build_meta"
|
|
4
|
-
|
|
5
|
-
[project]
|
|
6
|
-
name = "easypyui"
|
|
7
|
-
version = "2.2.0"
|
|
8
|
-
description = "A concise modern GUI wrapper for tkinter and ttk"
|
|
9
|
-
requires-python = ">=3.9"
|
|
10
|
-
license = {text = "MIT"}
|
|
11
|
-
keywords = ["gui", "tkinter", "ttk", "easy", "desktop"]
|
|
12
|
-
|
|
13
|
-
[tool.setuptools]
|
|
14
|
-
packages = ["easypyui"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|