easypyui 2.2.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.
@@ -0,0 +1,7 @@
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
@@ -0,0 +1,49 @@
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
+ ```
@@ -0,0 +1,105 @@
1
+ from .core import App, Value, Widget, Table, Canvas, Container, Row
2
+
3
+ __version__ = "2.2.0"
4
+
5
+ _current = None
6
+
7
+ def window(title="EasyPyUI", width=500, height=400, **kwargs):
8
+ global _current
9
+ _current = App(title, width, height, **kwargs)
10
+ return _current
11
+
12
+ def _app():
13
+ global _current
14
+ if _current is None:
15
+ _current = App()
16
+ return _current
17
+
18
+ def text(*a, **k): return _app().text(*a, **k)
19
+ def button(*a, **k): return _app().button(*a, **k)
20
+ def inputbox(*a, **k): return _app().input(*a, **k)
21
+ def password(*a, **k): return _app().password(*a, **k)
22
+ def textbox(*a, **k): return _app().textbox(*a, **k)
23
+ def check(*a, **k): return _app().check(*a, **k)
24
+ def radio(*a, **k): return _app().radio(*a, **k)
25
+ def select(*a, **k): return _app().select(*a, **k)
26
+ def slider(*a, **k): return _app().slider(*a, **k)
27
+ def spin(*a, **k): return _app().spin(*a, **k)
28
+ def progress(*a, **k): return _app().progress(*a, **k)
29
+ def listbox(*a, **k): return _app().listbox(*a, **k)
30
+ def table(*a, **k): return _app().table(*a, **k)
31
+ def canvas(*a, **k): return _app().canvas(*a, **k)
32
+ def separator(*a, **k): return _app().separator(*a, **k)
33
+ def group(*a, **k): return _app().group(*a, **k)
34
+ def row(*a, **k): return _app().row(*a, **k)
35
+ def frame(*a, **k): return _app().frame(*a, **k)
36
+ def scroll_area(*a, **k): return _app().scroll_area(*a, **k)
37
+ def alert(*a, **k): return _app().alert(*a, **k)
38
+ def error(*a, **k): return _app().error(*a, **k)
39
+ def warning(*a, **k): return _app().warning(*a, **k)
40
+ def ask(*a, **k): return _app().ask(*a, **k)
41
+ def prompt(*a, **k): return _app().prompt(*a, **k)
42
+ def open_file(*a, **k): return _app().open_file(*a, **k)
43
+ def open_files(*a, **k): return _app().open_files(*a, **k)
44
+ def save_file(*a, **k): return _app().save_file(*a, **k)
45
+ def folder(*a, **k): return _app().folder(*a, **k)
46
+ def pick_color(*a, **k): return _app().pick_color(*a, **k)
47
+ def later(*a, **k): return _app().later(*a, **k)
48
+ def every(*a, **k): return _app().every(*a, **k)
49
+ def start(): return _app().run()
50
+
51
+
52
+ def switch(*a, **k): return _app().switch(*a, **k)
53
+ def card(*a, **k): return _app().card(*a, **k)
54
+ def badge(*a, **k): return _app().badge(*a, **k)
55
+ def navbar(*a, **k): return _app().navbar(*a, **k)
56
+ def sidebar(*a, **k): return _app().sidebar(*a, **k)
57
+ def toast(*a, **k): return _app().toast(*a, **k)
58
+ def loading(*a, **k): return _app().loading(*a, **k)
59
+ def statusbar(*a, **k): return _app().statusbar(*a, **k)
60
+
61
+
62
+ # EasyPyUI 2.0 short aliases
63
+ def app(title="EasyPyUI", width=500, height=400, **kwargs):
64
+ return window(title, width, height, **kwargs)
65
+
66
+ def title(text_value, size=24, **kwargs):
67
+ return _app().text(text_value, size=size, bold=True, **kwargs)
68
+
69
+ def label(text_value="", **kwargs):
70
+ return _app().text(text_value, **kwargs)
71
+
72
+ def input(prompt_text=None, **kwargs):
73
+ return _app().input(prompt_text, **kwargs)
74
+
75
+ def on(text_value, action, **kwargs):
76
+ return _app().button(text_value, action, **kwargs)
77
+
78
+ def run():
79
+ return start()
80
+
81
+ def notify(*a, **k):
82
+ return _app().notify(*a, **k)
83
+
84
+ def confirm(*a, **k):
85
+ return _app().confirm(*a, **k)
86
+
87
+ def route(name, builder=None):
88
+ return _app().route(name, builder)
89
+
90
+ def go(name):
91
+ return _app().route(name)
92
+
93
+ def run_task(*a, **k):
94
+ return _app().run_task(*a, **k)
95
+
96
+
97
+ def mbutton(*a, **k): return _app().modern_button(*a, **k)
98
+ def minput(*a, **k): return _app().modern_input(*a, **k)
99
+ def mswitch(*a, **k): return _app().modern_switch(*a, **k)
100
+ def mcard(*a, **k): return _app().modern_card(*a, **k)
101
+
102
+ def progress_ring(*a, **k): return _app().progress_ring(*a, **k)
103
+ def fade_in(*a, **k): return _app().fade_in(*a, **k)
104
+ def fade_out(*a, **k): return _app().fade_out(*a, **k)
105
+ def animate(*a, **k): return _app().animate(*a, **k)
@@ -0,0 +1,122 @@
1
+ import time
2
+ import math
3
+ import tkinter as tk
4
+
5
+ EASINGS = {
6
+ "linear": lambda t: t,
7
+ "ease_in": lambda t: t*t,
8
+ "ease_out": lambda t: 1-(1-t)*(1-t),
9
+ "ease_in_out": lambda t: 2*t*t if t < .5 else 1-((-2*t+2)**2)/2,
10
+ }
11
+
12
+ def _widget(obj):
13
+ return getattr(obj, "tk", getattr(obj, "widget", obj))
14
+
15
+ class Animation:
16
+ def __init__(self, root, duration=300, fps=60, easing="ease_out"):
17
+ self.root = root
18
+ self.duration = max(1, duration)
19
+ self.interval = max(1, int(1000/fps))
20
+ self.ease = EASINGS.get(easing, EASINGS["ease_out"])
21
+ self.cancelled = False
22
+ self.after_id = None
23
+
24
+ def start(self, update, done=None):
25
+ started = time.perf_counter()
26
+ def tick():
27
+ if self.cancelled:
28
+ return
29
+ elapsed = (time.perf_counter()-started)*1000
30
+ raw = min(1.0, elapsed/self.duration)
31
+ update(self.ease(raw))
32
+ if raw >= 1:
33
+ if done: done()
34
+ else:
35
+ self.after_id = self.root.after(self.interval, tick)
36
+ tick()
37
+ return self
38
+
39
+ def cancel(self):
40
+ self.cancelled = True
41
+ if self.after_id:
42
+ try: self.root.after_cancel(self.after_id)
43
+ except Exception: pass
44
+
45
+ def fade(window, start=0.0, end=1.0, duration=300, easing="ease_out", done=None):
46
+ root = window.winfo_toplevel()
47
+ try: root.attributes("-alpha", start)
48
+ except tk.TclError: return None
49
+ return Animation(root, duration, easing=easing).start(
50
+ lambda t: root.attributes("-alpha", start+(end-start)*t), done)
51
+
52
+ def slide(widget, from_x, from_y, to_x, to_y, duration=350, easing="ease_out", done=None):
53
+ w = _widget(widget); root = w.winfo_toplevel()
54
+ w.place(x=from_x, y=from_y)
55
+ return Animation(root, duration, easing=easing).start(
56
+ lambda t: w.place(x=from_x+(to_x-from_x)*t, y=from_y+(to_y-from_y)*t), done)
57
+
58
+ def animate_value(root, start, end, setter, duration=300, easing="ease_out", done=None):
59
+ return Animation(root, duration, easing=easing).start(
60
+ lambda t: setter(start+(end-start)*t), done)
61
+
62
+ class ProgressRing:
63
+ def __init__(self, parent, value=0, maximum=100, size=90, width=8,
64
+ accent="#2563eb", track="#d1d5db", text=True):
65
+ self.value=float(value); self.maximum=float(maximum); self.size=size
66
+ self.width=width; self.accent=accent; self.track=track; self.show_text=text
67
+ bg = parent.cget("bg") if "bg" in parent.keys() else "#f5f5f5"
68
+ self.canvas=tk.Canvas(parent,width=size,height=size,highlightthickness=0,bd=0,bg=bg)
69
+ self.tk=self.widget=self.canvas
70
+ self.draw()
71
+
72
+ def draw(self):
73
+ c=self.canvas; c.delete("all"); pad=self.width/2+3
74
+ c.create_oval(pad,pad,self.size-pad,self.size-pad,outline=self.track,width=self.width)
75
+ pct=max(0,min(1,self.value/self.maximum if self.maximum else 0))
76
+ c.create_arc(pad,pad,self.size-pad,self.size-pad,start=90,extent=-360*pct,
77
+ style="arc",outline=self.accent,width=self.width)
78
+ if self.show_text:
79
+ c.create_text(self.size/2,self.size/2,text=f"{pct*100:.0f}%",font=("Arial",11,"bold"))
80
+
81
+ def get(self): return self.value
82
+ def set(self,value): self.value=float(value); self.draw(); return self
83
+ def pack(self,**kw): self.canvas.pack(**kw); return self
84
+ def grid(self,**kw): self.canvas.grid(**kw); return self
85
+ def place(self,**kw): self.canvas.place(**kw); return self
86
+
87
+ def animate_to(self,value,duration=500,easing="ease_out"):
88
+ start=self.value
89
+ return animate_value(self.canvas.winfo_toplevel(),start,float(value),self.set,duration,easing)
90
+
91
+ def install_animation_api(App, Builder):
92
+ def progress_ring(self,value=0,maximum=100,size=90,width=8,layout="pack",**kwargs):
93
+ app=self.app if hasattr(self,"app") else self
94
+ p=app.palette()
95
+ ring=ProgressRing(self._parent,value,maximum,size,width,
96
+ kwargs.pop("accent",p["accent"]),
97
+ kwargs.pop("track","#555555" if getattr(app,"_theme_name","light")=="dark" else "#d1d5db"),
98
+ kwargs.pop("text",True))
99
+ opts=kwargs.pop("layout_opts",{})
100
+ if layout=="grid": ring.grid(**opts)
101
+ elif layout=="place": ring.place(**opts)
102
+ elif layout!="none":
103
+ defaults={"anchor":"w","pady":5}; defaults.update(opts); ring.pack(**defaults)
104
+ return ring
105
+
106
+ Builder.progress_ring=progress_ring
107
+
108
+ def fade_in(self,duration=300,easing="ease_out"):
109
+ return fade(self.root,0,1,duration,easing)
110
+ def fade_out(self,duration=300,easing="ease_in",done=None):
111
+ return fade(self.root,1,0,duration,easing,done)
112
+ def animate(self,widget,kind="slide",duration=350,easing="ease_out",**kwargs):
113
+ if kind=="slide":
114
+ return slide(widget,kwargs.get("from_x",-100),kwargs.get("from_y",0),
115
+ kwargs.get("to_x",0),kwargs.get("to_y",0),duration,easing,kwargs.get("done"))
116
+ if kind=="fade":
117
+ return fade(_widget(widget),kwargs.get("start",0),kwargs.get("end",1),
118
+ duration,easing,kwargs.get("done"))
119
+ raise ValueError("kind 必須是 slide 或 fade")
120
+ App.fade_in=fade_in
121
+ App.fade_out=fade_out
122
+ App.animate=animate