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 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.
@@ -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
+ ```
@@ -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.2.0"
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
@@ -1,7 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: easypyui
3
- Version: 2.2.0
4
- Summary: A concise modern GUI wrapper for tkinter and ttk
5
- License: MIT
6
- Keywords: gui,tkinter,ttk,easy,desktop
7
- Requires-Python: >=3.9
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
- ```
@@ -1,7 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: easypyui
3
- Version: 2.2.0
4
- Summary: A concise modern GUI wrapper for tkinter and ttk
5
- License: MIT
6
- Keywords: gui,tkinter,ttk,easy,desktop
7
- Requires-Python: >=3.9
@@ -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