easykinter 0.0.1__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.
- easykinter-0.0.1/PKG-INFO +11 -0
- easykinter-0.0.1/README.md +0 -0
- easykinter-0.0.1/pyproject.toml +22 -0
- easykinter-0.0.1/setup.cfg +4 -0
- easykinter-0.0.1/src/easykinter/EasyKinter.py +932 -0
- easykinter-0.0.1/src/easykinter/__init__.py +8 -0
- easykinter-0.0.1/src/easykinter.egg-info/PKG-INFO +11 -0
- easykinter-0.0.1/src/easykinter.egg-info/SOURCES.txt +9 -0
- easykinter-0.0.1/src/easykinter.egg-info/dependency_links.txt +1 -0
- easykinter-0.0.1/src/easykinter.egg-info/requires.txt +1 -0
- easykinter-0.0.1/src/easykinter.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: easykinter
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: A wrapper to make Tkinter 90% less boring.
|
|
5
|
+
Author-email: Starly <starly.alt.acc1@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
9
|
+
Requires-Python: >=3.12
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: pillow>=10.0.0
|
|
File without changes
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "easykinter"
|
|
7
|
+
version = "0.0.1"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name="Starly", email="starly.alt.acc1@gmail.com" },
|
|
10
|
+
]
|
|
11
|
+
description = "A wrapper to make Tkinter 90% less boring."
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
requires-python = ">=3.12"
|
|
14
|
+
dependencies = ["pillow>=10.0.0"]
|
|
15
|
+
license = { text = "MIT" }
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Operating System :: Microsoft :: Windows",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[tool.setuptools.packages.find]
|
|
22
|
+
where = ["src"]
|
|
@@ -0,0 +1,932 @@
|
|
|
1
|
+
import tkinter as tk
|
|
2
|
+
import warnings as warn
|
|
3
|
+
from PIL import Image as img, ImageTk as imgtk
|
|
4
|
+
import atexit as _atexit
|
|
5
|
+
|
|
6
|
+
#region all the nice and dandy EasyKinter functions
|
|
7
|
+
|
|
8
|
+
#region Code that Automatically Runs mainloop() DO NOT TOUCH
|
|
9
|
+
#verifying if mainloop was called
|
|
10
|
+
_MainloopCalled = False
|
|
11
|
+
_OriginalMainloop = tk.Tk.mainloop
|
|
12
|
+
|
|
13
|
+
def _newmainloop(self, n=0):
|
|
14
|
+
global _MainloopCalled
|
|
15
|
+
_MainloopCalled = True
|
|
16
|
+
return _OriginalMainloop(self, n)
|
|
17
|
+
|
|
18
|
+
tk.Tk.mainloop = _newmainloop
|
|
19
|
+
|
|
20
|
+
#now call the function if mainloop wasn't written, and we're good to go.
|
|
21
|
+
def _auto_run():
|
|
22
|
+
global _MainloopCalled
|
|
23
|
+
if not _MainloopCalled and hasattr(tk, "_default_root") and tk._default_root:
|
|
24
|
+
try:
|
|
25
|
+
tk._default_root.mainloop()
|
|
26
|
+
except Exception:
|
|
27
|
+
pass
|
|
28
|
+
_atexit.register(_auto_run)
|
|
29
|
+
#hell yeah.
|
|
30
|
+
#endregion
|
|
31
|
+
|
|
32
|
+
#region Create Root Window Function
|
|
33
|
+
def CreateRoot(Title="Root Tkinter Window", SizeX=250, SizeY=250, PosX=None, PosY = None, HideWindow=False):
|
|
34
|
+
"""
|
|
35
|
+
# This function creates a new Root window and returns the value of the fully created window when done.
|
|
36
|
+
|
|
37
|
+
**Title** = What the Tk window's name will be upon creation.
|
|
38
|
+
|
|
39
|
+
**Geometry Configs**:
|
|
40
|
+
SizeX = Dictates the width of your window.
|
|
41
|
+
SizeY = Dictates the height of your window.
|
|
42
|
+
|
|
43
|
+
PosX = Position in your screen where the window is created, cannot be higher than the size of your monitor. (Width)
|
|
44
|
+
PosY = Position in your screen where the window is created, cannot be higher than the size of your monitor. (Height)
|
|
45
|
+
|
|
46
|
+
**Extra**:
|
|
47
|
+
HideWindow = Tells the program if your window will be hidden upon creation.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
if tk._default_root is not None:
|
|
51
|
+
raise RuntimeError(f"Root window could not be created as a root instance already exists.")
|
|
52
|
+
|
|
53
|
+
else:
|
|
54
|
+
newRoot = tk.Tk()
|
|
55
|
+
newRoot.title(Title)
|
|
56
|
+
newRoot.geometry(f"{SizeX}x{SizeY}{f'+{PosX}' if PosX is not None else ''}{f'+{PosY}' if PosY is not None else ''}")
|
|
57
|
+
|
|
58
|
+
if HideWindow:
|
|
59
|
+
newRoot.withdraw()
|
|
60
|
+
newRoot.update_idletasks()
|
|
61
|
+
|
|
62
|
+
return newRoot
|
|
63
|
+
# endregion
|
|
64
|
+
|
|
65
|
+
#region Create Toplevel Window Function
|
|
66
|
+
def CreateToplevel(Title="Toplevel Tkinter Window", Parent=None, Child=None, SizeX=250, SizeY=250, PosX=None, PosY=None):
|
|
67
|
+
"""
|
|
68
|
+
# This function creates a new Toplevel window and returns the value of the fully created window when done.
|
|
69
|
+
|
|
70
|
+
**Title** = What the TopLevel window's name will be upon creation.
|
|
71
|
+
|
|
72
|
+
**Geometry Configs**:
|
|
73
|
+
SizeX = Dictates the width of your window.
|
|
74
|
+
SizeY = Dictates the height of your window.
|
|
75
|
+
|
|
76
|
+
PosX = Position in your screen where the window is created, cannot be higher than the size of your monitor. (Width)
|
|
77
|
+
PosY = Position in your screen where the window is created, cannot be higher than the size of your monitor. (Height)
|
|
78
|
+
|
|
79
|
+
**Extra**:
|
|
80
|
+
Parent = Tells the program what parent this toplevel window will have. This can greatly affect the window's behavior.
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
WindowName = tk.Toplevel(Parent)
|
|
84
|
+
WindowName.title(Title)
|
|
85
|
+
WindowName.geometry(f"{SizeX}x{SizeY}{f'+{PosX}' if PosX is not None else ''}{f'+{PosY}' if PosY is not None else ''}")
|
|
86
|
+
|
|
87
|
+
if Child is not None:
|
|
88
|
+
if not isinstance(Child (tk.Toplevel, tk.Tk)):
|
|
89
|
+
raise ValueError(f"Could not link {WindowName} with {Child} as {Child} is not a Toplevel or Tk window. Did you try checking the 'Child=' value?")
|
|
90
|
+
|
|
91
|
+
else:
|
|
92
|
+
Child.transient(WindowName)
|
|
93
|
+
WindowName.update_idletasks()
|
|
94
|
+
|
|
95
|
+
return WindowName
|
|
96
|
+
#endregion
|
|
97
|
+
|
|
98
|
+
#region W.I.P Customization Function
|
|
99
|
+
def ExtraCustomize(TargetWindow, background="#f0f0f0", hideTitleBar=False, ResizableWidth=None, ResizableHeight=None):
|
|
100
|
+
"""
|
|
101
|
+
# (W.I.P) This function allows you to fully customize and color any window you want.
|
|
102
|
+
|
|
103
|
+
**TargetWindow** = The name of the window or widget that will be targetted.
|
|
104
|
+
|
|
105
|
+
**Coloring Configs:**
|
|
106
|
+
background = What the color of the widget's background will be set to.
|
|
107
|
+
|
|
108
|
+
**Extra:**
|
|
109
|
+
hideTitleBar = Will apply overridedirect(True) to window if value is set to true.
|
|
110
|
+
ResizableWidth = Dictates wether your window can be resized horizontally.
|
|
111
|
+
ResizableHeight = Dictates if your window can be resized vertically.
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
if TargetWindow is None:
|
|
115
|
+
raise ValueError(f"Expected a Tkinter window/frame, but got None instead.")
|
|
116
|
+
|
|
117
|
+
elif not isinstance(TargetWindow, (tk.Tk, tk.Toplevel)):
|
|
118
|
+
raise TypeError(f"Expected a Tkinter window, but got {type(TargetWindow).__name__} instead.")
|
|
119
|
+
|
|
120
|
+
else:
|
|
121
|
+
TargetWindow.configure(bg=background)
|
|
122
|
+
|
|
123
|
+
if hideTitleBar:
|
|
124
|
+
TargetWindow.overrideredirect(True)
|
|
125
|
+
|
|
126
|
+
if ResizableHeight is not None or ResizableWidth is not None:
|
|
127
|
+
if isinstance(TargetWindow, (tk.Tk, tk.Toplevel)):
|
|
128
|
+
TargetWindow.resizable(ResizableWidth, ResizableHeight)
|
|
129
|
+
|
|
130
|
+
else:
|
|
131
|
+
warn.warn(f"Resize attributes were skipped as the target was a '{type(TargetWindow).__name__}' and it cannot have a Resizeable attrbute. Have you tried using tk.Toplevel or tk.Tk?", category=RuntimeWarning)
|
|
132
|
+
#endregion
|
|
133
|
+
|
|
134
|
+
#region Creating Customizeable Labels and Auto-Pack Function
|
|
135
|
+
def CreateLabel(ForWindow, Text="New Label", font="Arial", FontSize=12, LabelImage=None, ImageCompound="Center", optionalCustomization = "", BgColor="#f0f0f0", FgColor="#000000", PackType=None, Anchor="center", PadX=0, PadY=0, Side=tk.TOP, Row=0, Column=0, Sticky=None, X=None, Y=None, RelX=None, RelY=None):
|
|
136
|
+
"""
|
|
137
|
+
# This function creates and returns a custom label that can be also packed within the same function.
|
|
138
|
+
|
|
139
|
+
**ForWindow** = This dictates that target where the label will be created for.
|
|
140
|
+
|
|
141
|
+
**Formatting:**
|
|
142
|
+
text = What text the label will contain.
|
|
143
|
+
font = What custom font the label will use.
|
|
144
|
+
FontSize = The size of the font used.
|
|
145
|
+
OptionalCustomization = Dictates special font formatting such as bold, italic, or underline.
|
|
146
|
+
(Options: 'bold', 'italic', 'underline', 'overstrike')
|
|
147
|
+
LabelImage = What custom image the label will have. (Must be a tk.PhotoImage object.)
|
|
148
|
+
ImageCompound = What direction the image will be placed on top of the text.
|
|
149
|
+
(Options: 'top', 'left', 'center', 'right', 'bottom', 'none')
|
|
150
|
+
|
|
151
|
+
**Extra Customization:**
|
|
152
|
+
BgColor = What the background of the label will be colored as.
|
|
153
|
+
FgColor = The color of the text within the label.
|
|
154
|
+
Anchor = Where the text will be anchored, affects where it will be packed.
|
|
155
|
+
|
|
156
|
+
# Packing Types and their parameters:
|
|
157
|
+
|
|
158
|
+
**Pack (The default type, packs label and contains borders.):**
|
|
159
|
+
PadX = Width for the padding of the label.
|
|
160
|
+
PadY = Height for the padding of the label.
|
|
161
|
+
Side = Side where the label will go to in the widget, is relative.
|
|
162
|
+
Sticky = Position where the label will stick to in the widget, is not relative.
|
|
163
|
+
|
|
164
|
+
**Grid (for mounting grids and organized sheets):**
|
|
165
|
+
Row = The row where your element will be placed. (Horizontal)
|
|
166
|
+
Column = the column where your element will be placed. (Vertical)
|
|
167
|
+
PadX = the padding width your element will have, affects elements in and outside of the grid.
|
|
168
|
+
PadY = the padding height your element will have, affects elements in and outside of the grid.
|
|
169
|
+
|
|
170
|
+
**Place (for accurately placing elements in absolute or relative positions):**
|
|
171
|
+
X = The absolute position of the element in your window, cannot be moved. (Width)
|
|
172
|
+
Y = The absolute position of the element in your window, cannot be moved. (Height)
|
|
173
|
+
RelX = The relative position of the element in your window, can be moved. (Width)
|
|
174
|
+
RelY = The relative position of the element in your window, can be moved. (Height)
|
|
175
|
+
"""
|
|
176
|
+
|
|
177
|
+
if not isinstance(ForWindow, (tk.Tk, tk.Frame, tk.Toplevel)):
|
|
178
|
+
raise TypeError(f"Expected a Tkinter window/frame, but got {type(ForWindow).__name__} instead.")
|
|
179
|
+
|
|
180
|
+
if ImageCompound is not None:
|
|
181
|
+
ImageCompound = ImageCompound.lower()
|
|
182
|
+
|
|
183
|
+
if ImageCompound == "top":
|
|
184
|
+
ImageCompound = tk.TOP
|
|
185
|
+
|
|
186
|
+
elif ImageCompound == "left":
|
|
187
|
+
ImageCompound = tk.LEFT
|
|
188
|
+
|
|
189
|
+
elif ImageCompound == "center":
|
|
190
|
+
ImageCompound = tk.CENTER
|
|
191
|
+
|
|
192
|
+
elif ImageCompound == "right":
|
|
193
|
+
ImageCompound = tk.RIGHT
|
|
194
|
+
|
|
195
|
+
elif ImageCompound == "bottom":
|
|
196
|
+
ImageCompound = tk.BOTTOM
|
|
197
|
+
|
|
198
|
+
newLabel = tk.Label(ForWindow, image=LabelImage, compound=ImageCompound, text=Text, font=(font, FontSize, optionalCustomization), bg=BgColor, fg=FgColor)
|
|
199
|
+
|
|
200
|
+
if PackType is not None:
|
|
201
|
+
PackType = PackType.lower()
|
|
202
|
+
|
|
203
|
+
if PackType is not None and PackType not in ["place", "grid", "pack"]:
|
|
204
|
+
raise ValueError(f"Expected a proper packing style, but got {PackType} instead. Current packing types are 'Pack', 'Grid', or 'Place'")
|
|
205
|
+
|
|
206
|
+
elif PackType == "pack":
|
|
207
|
+
newLabel.pack(anchor=Anchor, padx=PadX, pady=PadY, side=Side, sticky=Sticky)
|
|
208
|
+
|
|
209
|
+
elif PackType == "place":
|
|
210
|
+
newLabel.place(x=X, y=Y, relx=RelX, rely=RelY, anchor=Anchor)
|
|
211
|
+
|
|
212
|
+
elif PackType == "grid":
|
|
213
|
+
newLabel.grid(row=Row, column=Column, padx=PadX, pady=PadY)
|
|
214
|
+
|
|
215
|
+
return newLabel
|
|
216
|
+
#endregion
|
|
217
|
+
|
|
218
|
+
#region Creating Text Entry Widget and Custom Keybinding Function
|
|
219
|
+
def CreateEntry(ForWindow, CustomFunc=None, CustomFuncKeybind=None, PlaceHolderEnabled=True, PlaceHolderText="Insert text here...", PlaceHolderTextColor="Gray", Width=None, Font="Arial", FontSize=12, FontProperties=None, borderwidth=None, bg=None, fg="#000000", PackType=None, Anchor="center", PadX=0, PadY=0, Side=tk.TOP, Row=0, Column=0, Sticky=None, X=None, Y=None, RelX=None, RelY=None):
|
|
220
|
+
"""
|
|
221
|
+
# This function automatically creates and customizes an Entry widget in tkinter. Can also additionally trigger a funtion or be automatically packed.
|
|
222
|
+
|
|
223
|
+
**Logic parameters:**
|
|
224
|
+
ForWindow = Defines the target window where the entry will be created.
|
|
225
|
+
CustomFunc = Sets a function to automatically trigger when the set keybind is activated. Must be added alongside **CustomFuncKeybind.**
|
|
226
|
+
CustomFuncKeybind = Sets a keybind for a function to trigger when entering said keybind in the entry widget. Must be added alongside **CustomFunc.**
|
|
227
|
+
PlaceHolderEnabled = Dictates wether the Entry widget will have a pre-built placeholder.
|
|
228
|
+
|
|
229
|
+
**UX and Customization:**
|
|
230
|
+
PlaceHolderText = Defines what text will appear in the placeholder UI.
|
|
231
|
+
PlaceHolderTextColor = Defines what color of the placeholder text will be.
|
|
232
|
+
Width = Defines the width of the input box.
|
|
233
|
+
Font = Defines the custom font the text will have in the input.
|
|
234
|
+
FontSize: = Defines the size of the text in the Entry box. Also affects Entry box height.
|
|
235
|
+
FontPorperties = Wether a custom font property will be applied.
|
|
236
|
+
(Current properties availiable = 'Bold', 'Italic', 'Underline', 'Overstrike')
|
|
237
|
+
bg = What the background of the label will be colored as.
|
|
238
|
+
fg = The color of the text within the label.
|
|
239
|
+
|
|
240
|
+
# Packing Types and their parameters:
|
|
241
|
+
|
|
242
|
+
**Pack (The default type, packs label and contains borders.):**
|
|
243
|
+
PadX = Width for the padding of the label.
|
|
244
|
+
PadY = Height for the padding of the label.
|
|
245
|
+
Side = Side where the label will go to in the widget, is relative.
|
|
246
|
+
Sticky = Position where the label will stick to in the widget, is not relative.
|
|
247
|
+
|
|
248
|
+
**Grid (for mounting grids and organized sheets):**
|
|
249
|
+
Row = The row where your element will be placed. (Horizontal)
|
|
250
|
+
Column = the column where your element will be placed. (Vertical)
|
|
251
|
+
PadX = the padding width your element will have, affects elements in and outside of the grid.
|
|
252
|
+
PadY = the padding height your element will have, affects elements in and outside of the grid.
|
|
253
|
+
|
|
254
|
+
**Place (for accurately placing elements in absolute or relative positions):**
|
|
255
|
+
X = The absolute position of the element in your window, cannot be moved. (Width)
|
|
256
|
+
Y = The absolute position of the element in your window, cannot be moved. (Height)
|
|
257
|
+
RelX = The relative position of the element in your window, can be moved. (Width)
|
|
258
|
+
RelY = The relative position of the element in your window, can be moved. (Height)
|
|
259
|
+
"""
|
|
260
|
+
|
|
261
|
+
def on_entry_click(event=None):
|
|
262
|
+
if NewEntry.get() == PlaceHolderText:
|
|
263
|
+
NewEntry.delete(0, tk.END)
|
|
264
|
+
NewEntry.config(fg=fg.lower())
|
|
265
|
+
|
|
266
|
+
def on_focus_out(event=None):
|
|
267
|
+
if not NewEntry.get():
|
|
268
|
+
NewEntry.insert(0, PlaceHolderText)
|
|
269
|
+
NewEntry.config(fg=PlaceHolderTextColor.lower())
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
NewEntry = tk.Entry(ForWindow, width=Width if Width is not None else 20, font=(Font.capitalize(), FontSize, FontProperties.lower() if FontProperties else "normal"), bg=bg, fg=fg, borderwidth=borderwidth if borderwidth is not None else 1)
|
|
273
|
+
|
|
274
|
+
if CustomFuncKeybind is not None and CustomFunc is not None:
|
|
275
|
+
def eventNeedlessFunction(event):
|
|
276
|
+
CustomFunc()
|
|
277
|
+
|
|
278
|
+
NewEntry.bind(CustomFuncKeybind, eventNeedlessFunction)
|
|
279
|
+
|
|
280
|
+
elif CustomFuncKeybind is not None or CustomFunc is not None:
|
|
281
|
+
raise ValueError("The function could not be called as the Function Keybind values have not been filled properly. Did you try checking both 'CustomFunc__' values?")
|
|
282
|
+
|
|
283
|
+
if PlaceHolderEnabled:
|
|
284
|
+
NewEntry.bind("<FocusIn>", on_entry_click)
|
|
285
|
+
NewEntry.bind("<FocusOut>", on_focus_out)
|
|
286
|
+
on_focus_out()
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
if PackType is not None:
|
|
290
|
+
PackType = PackType.lower()
|
|
291
|
+
|
|
292
|
+
if PackType is not None and PackType not in ["place", "grid", "pack"]:
|
|
293
|
+
raise ValueError(f"Expected a proper packing style, but got {PackType} instead. Current packing types are 'Pack', 'Grid', or 'Place'")
|
|
294
|
+
|
|
295
|
+
elif PackType == "pack":
|
|
296
|
+
NewEntry.pack(anchor=Anchor, padx=PadX, pady=PadY, side=Side, sticky=Sticky)
|
|
297
|
+
|
|
298
|
+
elif PackType == "place":
|
|
299
|
+
NewEntry.place(x=X, y=Y, relx=RelX, rely=RelY, anchor=Anchor)
|
|
300
|
+
|
|
301
|
+
elif PackType == "grid":
|
|
302
|
+
NewEntry.grid(row=Row if Row is not None else 0, column=Column if Column is not None else 0, padx=PadX, pady=PadY)
|
|
303
|
+
|
|
304
|
+
return NewEntry
|
|
305
|
+
#endregion
|
|
306
|
+
|
|
307
|
+
#region Creating Button Widget and Custom Keybinding Function
|
|
308
|
+
def CreateButton(ForWindow, Text="New Button", Image=None, Compound="center", font="Arial", FontSize=12, optionalCustomization = "", CommandFunc=None, ButtonWidth=100, ButtonHeight=30, BgColor="#f0f0f0", FgColor="#000000", PackType=None, Anchor="center", PadX=0, PadY=0, Side=tk.TOP, Row=0, Column=0, Sticky=None, X=None, Y=None, RelX=None, RelY=None):
|
|
309
|
+
"""
|
|
310
|
+
# This function creates and returns a custom button that can be also packed within the same function.
|
|
311
|
+
|
|
312
|
+
**ForWindow** = This dictates that target where the button will be created for.
|
|
313
|
+
|
|
314
|
+
**Button Contents:**
|
|
315
|
+
Text = What custom text will the button have.
|
|
316
|
+
Image = What custom image the button will have.
|
|
317
|
+
|
|
318
|
+
**Formatting:**
|
|
319
|
+
Compound = What direction the image will be placed on top of the text.
|
|
320
|
+
(Options: 'top', 'left', 'center', 'right', 'bottom', 'none')
|
|
321
|
+
font = What custom font the button will use.
|
|
322
|
+
FontSize = The size of the font used.
|
|
323
|
+
OptionalCustomization = Dictates special font formatting such as bold, italic, or underline.
|
|
324
|
+
(Options: 'bold', 'italic', 'underline', 'overstrike')
|
|
325
|
+
|
|
326
|
+
**Extra Customization:**
|
|
327
|
+
BgColor = What the background of the button will be colored as.
|
|
328
|
+
FgColor = The color of the text within the button.
|
|
329
|
+
Anchor = Where the text will be anchored, affects where it will be packed.
|
|
330
|
+
|
|
331
|
+
# Packing Types and their parameters:
|
|
332
|
+
|
|
333
|
+
**Pack (The default type, packs label and contains borders.):**
|
|
334
|
+
PadX = Width for the padding of the label.
|
|
335
|
+
PadY = Height for the padding of the label.
|
|
336
|
+
Side = Side where the label will go to in the widget, is relative.
|
|
337
|
+
Sticky = Position where the label will stick to in the widget, is not relative.
|
|
338
|
+
|
|
339
|
+
**Grid (for mounting grids and organized sheets):**
|
|
340
|
+
Row = The row where your element will be placed. (Horizontal)
|
|
341
|
+
Column = the column where your element will be placed. (Vertical)
|
|
342
|
+
PadX = the padding width your element will have, affects elements in and outside of the grid.
|
|
343
|
+
PadY = the padding height your element will have, affects elements in and outside of the grid.
|
|
344
|
+
|
|
345
|
+
**Place (for accurately placing elements in absolute or relative positions):**
|
|
346
|
+
X = The absolute position of the element in your window, cannot be moved. (Width)
|
|
347
|
+
Y = The absolute position of the element in your window, cannot be moved. (Height)
|
|
348
|
+
RelX = The relative position of the element in your window, can be moved. (Width)
|
|
349
|
+
RelY = The relative position of the element in your window, can be moved. (Height)
|
|
350
|
+
"""
|
|
351
|
+
|
|
352
|
+
if not isinstance(ForWindow, (tk.Tk, tk.Frame, tk.Toplevel)):
|
|
353
|
+
raise TypeError(f"Expected a Tkinter window/frame, but got {type(ForWindow).__name__} instead.")
|
|
354
|
+
|
|
355
|
+
TransparentPixel = "R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
|
|
356
|
+
TrasnparentImage = tk.PhotoImage(data=TransparentPixel)
|
|
357
|
+
|
|
358
|
+
Compound = Compound.lower()
|
|
359
|
+
|
|
360
|
+
if Image is None:
|
|
361
|
+
newButton = tk.Button(ForWindow, command=CommandFunc, image=TrasnparentImage, text=Text, compound=Compound, font=(font, FontSize, optionalCustomization), width=ButtonWidth, height=ButtonHeight, bg=BgColor, fg=FgColor)
|
|
362
|
+
newButton.image = TrasnparentImage
|
|
363
|
+
|
|
364
|
+
else:
|
|
365
|
+
newButton = tk.Button(ForWindow, command=CommandFunc, image=Image, text=Text, compound=Compound, font=(font, FontSize, optionalCustomization), width=ButtonWidth, height=ButtonHeight, bg=BgColor, fg=FgColor)
|
|
366
|
+
newButton.image = Image
|
|
367
|
+
|
|
368
|
+
if PackType is not None:
|
|
369
|
+
PackType = PackType.lower()
|
|
370
|
+
|
|
371
|
+
if PackType is not None and PackType not in ["place", "grid", "pack"]:
|
|
372
|
+
raise ValueError(f"Expected a proper packing style, but got {PackType} instead. Current packing types are 'Pack', 'Grid', or 'Place'")
|
|
373
|
+
|
|
374
|
+
elif PackType == "pack":
|
|
375
|
+
newButton.pack(anchor=Anchor, padx=PadX, pady=PadY, side=Side, sticky=Sticky)
|
|
376
|
+
|
|
377
|
+
elif PackType == "place":
|
|
378
|
+
newButton.place(x=X, y=Y, relx=RelX, rely=RelY, anchor=Anchor)
|
|
379
|
+
|
|
380
|
+
elif PackType == "grid":
|
|
381
|
+
newButton.grid(row=Row, column=Column, padx=PadX, pady=PadY)
|
|
382
|
+
|
|
383
|
+
return newButton
|
|
384
|
+
#endregion
|
|
385
|
+
|
|
386
|
+
#region Creating AplhaNumerical Input Widgets and Miscellaneous
|
|
387
|
+
def CreateMiscInputs(ForWindow, InputType, Border=2, CustomFont="Arial", CustomFontSize=12, CustomFontExtra="", BgColor="#f0f0f0", FgColor="#000000", CommandKeybind=None, CommandFunction=None, SpinboxFrom=-100, SpinboxTo=100, ScaleLength=100, ScaleRepeatDelay=1, ScaleResolution=1, PackType=None, PadX=None, PadY=None, Anchor="center", Side=tk.TOP, Row=0, Column=0, Sticky=None, X=None, Y=None, RelX=None, RelY=None):
|
|
388
|
+
"""
|
|
389
|
+
# This function can create, pack, and set a command for other, miscellaneous Aplhanumerical input widgets and returns the widget.
|
|
390
|
+
|
|
391
|
+
**Essential Parameters:**
|
|
392
|
+
ForWindow = This parameter dictates what window the custom widget will be created for.
|
|
393
|
+
InputType = This parameter dictates what custom widget will be created.
|
|
394
|
+
(Options: Text, Spinbox, Scale)
|
|
395
|
+
|
|
396
|
+
**Customization Parameters:**
|
|
397
|
+
Border = This parameter defines the border of the created widget.
|
|
398
|
+
CustomFont = This parameter defines the Font that will be used in the created widget.
|
|
399
|
+
CustomFontSize = This parameter defines the custom Font Size that the widget will have.
|
|
400
|
+
CustomFontExtra = This parameter defines any extra properties the font can have, such as Bold.
|
|
401
|
+
(Options: 'Bold', 'Italic', 'Underline', 'Overstrike'.)
|
|
402
|
+
BgColor = Defines the background color of the widget.
|
|
403
|
+
FgColor = Defines the foreground color of the widget.
|
|
404
|
+
|
|
405
|
+
**Custom Keybind Parameters:**
|
|
406
|
+
CommandKeybind = What the custom keybind of said widget will be. Must have CommandFunction to work.
|
|
407
|
+
CommandFunction = The command that will be executed when pressing the respective keybind. Must have CommandKeybind to work.
|
|
408
|
+
|
|
409
|
+
**SpinBox Specific Parameters:**
|
|
410
|
+
SpinboxFrom = The minimum value that the SpinBox can have.
|
|
411
|
+
SpinboxTo = The maximum value that the spinbox can have.
|
|
412
|
+
|
|
413
|
+
**Scale Specific Parameters:**
|
|
414
|
+
ScaleLength = The length of the scale in pixels. (Width, X value.)
|
|
415
|
+
ScaleRepeatDelay = The frequency that the scale will show numbers based on their position.
|
|
416
|
+
(For example, a ScaleRepeatDelay of 0.5 would show numbers every 0.5.)
|
|
417
|
+
ScaleResolution = The rounding that the scale will have upon selecting a value, where it snaps your cursor to said value.
|
|
418
|
+
(For example, setting to 1 snaps your selector to every integer, skipping decimals. Setting to -1 disables rounding.)
|
|
419
|
+
|
|
420
|
+
# Packing Types and their parameters:
|
|
421
|
+
|
|
422
|
+
**Pack (The default type, packs label and contains borders.):**
|
|
423
|
+
PadX = Width for the padding of the label.
|
|
424
|
+
PadY = Height for the padding of the label.
|
|
425
|
+
Side = Side where the label will go to in the widget, is relative.
|
|
426
|
+
Sticky = Position where the label will stick to in the widget, is not relative.
|
|
427
|
+
|
|
428
|
+
**Grid (for mounting grids and organized sheets):**
|
|
429
|
+
Row = The row where your element will be placed. (Horizontal)
|
|
430
|
+
Column = the column where your element will be placed. (Vertical)
|
|
431
|
+
PadX = the padding width your element will have, affects elements in and outside of the grid.
|
|
432
|
+
PadY = the padding height your element will have, affects elements in and outside of the grid.
|
|
433
|
+
|
|
434
|
+
**Place (for accurately placing elements in absolute or relative positions):**
|
|
435
|
+
X = The absolute position of the element in your window, cannot be moved. (Width)
|
|
436
|
+
Y = The absolute position of the element in your window, cannot be moved. (Height)
|
|
437
|
+
RelX = The relative position of the element in your window, can be moved. (Width)
|
|
438
|
+
RelY = The relative position of the element in your window, can be moved. (Height)
|
|
439
|
+
"""
|
|
440
|
+
|
|
441
|
+
if not ForWindow or not InputType:
|
|
442
|
+
raise ValueError("Could not create miscellaneous input types as one of the necessary values wew not given. Did you try filling the primary essential values?")
|
|
443
|
+
|
|
444
|
+
else:
|
|
445
|
+
InputType = InputType.lower()
|
|
446
|
+
|
|
447
|
+
if InputType == "text":
|
|
448
|
+
NewInput = tk.Text(ForWindow, bg=BgColor, fg=FgColor, bd=Border, font=(CustomFont.capitalize(), CustomFontSize, CustomFontExtra.capitalize()))
|
|
449
|
+
|
|
450
|
+
elif InputType == "spinbox":
|
|
451
|
+
NewInput = tk.Spinbox(ForWindow, bg=BgColor, fg=FgColor, bd=Border, font=(CustomFont.capitalize(), CustomFontSize, CustomFontExtra.capitalize()), from_=SpinboxFrom, to=SpinboxTo)
|
|
452
|
+
|
|
453
|
+
elif InputType == "scale":
|
|
454
|
+
NewInput = NewInput = tk.Scale(ForWindow, bg=BgColor, fg=FgColor, bd=Border, font=(CustomFont.capitalize(), CustomFontSize, CustomFontExtra.capitalize()), repeatdelay=ScaleRepeatDelay, resolution=ScaleResolution, length=ScaleLength)
|
|
455
|
+
|
|
456
|
+
if CommandKeybind is not None and CommandFunction is not None:
|
|
457
|
+
def eventNeedlessFunction(event):
|
|
458
|
+
CommandFunction()
|
|
459
|
+
|
|
460
|
+
NewInput.bind(CommandKeybind, eventNeedlessFunction)
|
|
461
|
+
|
|
462
|
+
elif CommandKeybind is not None or CommandFunction is not None:
|
|
463
|
+
raise ValueError("Could not bind a command to this widget as one of the proper 'Command__' were not filled. Did you try checking both the 'Command__' parameters?")
|
|
464
|
+
|
|
465
|
+
if PackType is not None:
|
|
466
|
+
PackType = PackType.lower()
|
|
467
|
+
|
|
468
|
+
if PackType is not None and PackType not in ["place", "grid", "pack"]:
|
|
469
|
+
raise ValueError(f"Expected a proper packing style, but got {PackType} instead. Current packing types are 'Pack', 'Grid', or 'Place'")
|
|
470
|
+
|
|
471
|
+
elif PackType == "pack":
|
|
472
|
+
NewInput.pack(anchor=Anchor, padx=PadX, pady=PadY, side=Side, sticky=Sticky)
|
|
473
|
+
|
|
474
|
+
elif PackType == "place":
|
|
475
|
+
NewInput.place(x=X, y=Y, relx=RelX, rely=RelY, anchor=Anchor)
|
|
476
|
+
|
|
477
|
+
elif PackType == "grid":
|
|
478
|
+
NewInput.grid(row=Row if Row is not None else 0, column=Column if Column is not None else 0, padx=PadX, pady=PadY)
|
|
479
|
+
|
|
480
|
+
return NewInput
|
|
481
|
+
#endregion
|
|
482
|
+
|
|
483
|
+
#region Creating Boolean Input Widgets and Miscellaneous
|
|
484
|
+
def CreateBoolInputs(ForWindow, WidgetType, Border=2, CustomText="", CustomFont="Arial", CustomFontSize=12, CustomFontExtra="", BgColor="#f0f0f0", FgColor="#000000", CommandKeybind=None, CommandFunction=None, CheckBoxState="NORMAL", CheckBoxOnValue=1, CheckBoxOffValue=0,PackType=None, PadX=None, PadY=None, Anchor="center", Side=tk.TOP, Row=0, Column=0, Sticky=None, X=None, Y=None, RelX=None, RelY=None):
|
|
485
|
+
"""
|
|
486
|
+
# Creates a miscellaneous input from the Bool type.
|
|
487
|
+
|
|
488
|
+
**Essential Parameters:**
|
|
489
|
+
ForWindow = This parameter dictates what window the custom widget will be created for.
|
|
490
|
+
WidgetType = This parameter dictates what custom widget will be created.
|
|
491
|
+
(Options: Text, Spinbox, Scale)
|
|
492
|
+
|
|
493
|
+
**Customization Parameters:**
|
|
494
|
+
Border = This parameter defines the border of the created widget.
|
|
495
|
+
CustomFont = This parameter defines the Font that will be used in the created widget.
|
|
496
|
+
CustomFontSize = This parameter defines the custom Font Size that the widget will have.
|
|
497
|
+
CustomFontExtra = This parameter defines any extra properties the font can have, such as Bold.
|
|
498
|
+
(Options: 'Bold', 'Italic', 'Underline', 'Overstrike'.)
|
|
499
|
+
BgColor = Defines the background color of the widget.
|
|
500
|
+
FgColor = Defines the foreground color of the widget.
|
|
501
|
+
|
|
502
|
+
**Custom Keybind Parameters:**
|
|
503
|
+
CommandKeybind = What the custom keybind of said widget will be. Must have CommandFunction to work.
|
|
504
|
+
CommandFunction = The command that will be executed when pressing the respective keybind. Must have CommandKeybind to work.
|
|
505
|
+
|
|
506
|
+
**CheckBox Specific Parameters:**
|
|
507
|
+
CheckBoxState = 'NORMAL' for the checkbox to function normally. 'DISABLED' for the checkbox to not work.
|
|
508
|
+
CheckBoxOnValue = The value that the checkbox will return when on.
|
|
509
|
+
CheckBoxOffValue = The value that the checkbox will return when off.
|
|
510
|
+
|
|
511
|
+
# Packing Types and their parameters:
|
|
512
|
+
|
|
513
|
+
**Pack (The default type, packs label and contains borders.):**
|
|
514
|
+
PadX = Width for the padding of the label.
|
|
515
|
+
PadY = Height for the padding of the label.
|
|
516
|
+
Side = Side where the label will go to in the widget, is relative.
|
|
517
|
+
Sticky = Position where the label will stick to in the widget, is not relative.
|
|
518
|
+
|
|
519
|
+
**Grid (for mounting grids and organized sheets):**
|
|
520
|
+
Row = The row where your element will be placed. (Horizontal)
|
|
521
|
+
Column = the column where your element will be placed. (Vertical)
|
|
522
|
+
PadX = the padding width your element will have, affects elements in and outside of the grid.
|
|
523
|
+
PadY = the padding height your element will have, affects elements in and outside of the grid.
|
|
524
|
+
|
|
525
|
+
**Place (for accurately placing elements in absolute or relative positions):**
|
|
526
|
+
X = The absolute position of the element in your window, cannot be moved. (Width)
|
|
527
|
+
Y = The absolute position of the element in your window, cannot be moved. (Height)
|
|
528
|
+
RelX = The relative position of the element in your window, can be moved. (Width)
|
|
529
|
+
RelY = The relative position of the element in your window, can be moved. (Height)
|
|
530
|
+
"""
|
|
531
|
+
|
|
532
|
+
if not ForWindow or not WidgetType:
|
|
533
|
+
raise ValueError("Could not create a bool input widget as the essential parameters were not filled. Did you try checking 'ForWindow' and 'WidgetType'?")
|
|
534
|
+
|
|
535
|
+
else:
|
|
536
|
+
WidgetType = WidgetType.lower()
|
|
537
|
+
|
|
538
|
+
if WidgetType == "checkbutton":
|
|
539
|
+
NewBool = tk.Checkbutton(ForWindow, bd=Border, text=CustomText, anchor=Anchor, font=(CustomFont.capitalize(), CustomFontSize, CustomFontExtra.capitalize()), bg=BgColor, fg=FgColor, state=CheckBoxState, onvalue=CheckBoxOnValue, offvalue=CheckBoxOffValue)
|
|
540
|
+
|
|
541
|
+
elif WidgetType == "radiobutton":
|
|
542
|
+
NewBool = tk.Radiobutton(ForWindow, bd=Border, anchor=Anchor, text=CustomText, font=(CustomFont.capitalize(), CustomFontSize, CustomFontExtra.capitalize()), bg=BgColor, fg=FgColor)
|
|
543
|
+
|
|
544
|
+
elif WidgetType == "listbox":
|
|
545
|
+
NewBool = tk.Listbox(ForWindow, bd=Border, text=CustomText, font=(CustomFont.capitalize(), CustomFontSize, CustomFontExtra.capitalize()), bg=BgColor, fg=FgColor)
|
|
546
|
+
|
|
547
|
+
if CommandKeybind is not None and CommandFunction is not None:
|
|
548
|
+
def eventNeedlessFunction(event):
|
|
549
|
+
CommandFunction()
|
|
550
|
+
|
|
551
|
+
NewBool.bind(CommandKeybind, eventNeedlessFunction)
|
|
552
|
+
|
|
553
|
+
elif CommandKeybind is not None or CommandFunction is not None:
|
|
554
|
+
raise ValueError("Could not bind a command to this widget as one of the proper 'Command__' were not filled. Did you try checking both the 'Command__' parameters?")
|
|
555
|
+
|
|
556
|
+
if PackType is not None:
|
|
557
|
+
PackType = PackType.lower()
|
|
558
|
+
|
|
559
|
+
if PackType is not None and PackType not in ["place", "grid", "pack"]:
|
|
560
|
+
raise ValueError(f"Expected a proper packing style, but got {PackType} instead. Current packing types are 'Pack', 'Grid', or 'Place'")
|
|
561
|
+
|
|
562
|
+
elif PackType == "pack":
|
|
563
|
+
NewBool.pack(anchor=Anchor, padx=PadX, pady=PadY, side=Side, sticky=Sticky)
|
|
564
|
+
|
|
565
|
+
elif PackType == "place":
|
|
566
|
+
NewBool.place(x=X, y=Y, relx=RelX, rely=RelY, anchor=Anchor)
|
|
567
|
+
|
|
568
|
+
elif PackType == "grid":
|
|
569
|
+
NewBool.grid(row=Row if Row is not None else 0, column=Column if Column is not None else 0, padx=PadX, pady=PadY)
|
|
570
|
+
|
|
571
|
+
return NewBool
|
|
572
|
+
#endregion
|
|
573
|
+
|
|
574
|
+
#region Better Bind With Additional Improved Features
|
|
575
|
+
def BetterBind(TargetWindow, KeyToBind, FunctionToBind, After=1, RepeatTimes=0, RepeatDelay=1):
|
|
576
|
+
"""
|
|
577
|
+
# A function that improves Tkinter's .bind() feature, improving it with a new nit-picked quality of life add-ons.
|
|
578
|
+
|
|
579
|
+
**Necessary parameters:**
|
|
580
|
+
TargetWindow = The Tkinter element you want to bind your function and keybind to.
|
|
581
|
+
FunctionToBind = The function you wish to bind to a key, that runs within the element.
|
|
582
|
+
KeyToBind = The Keybind you want to set to your element to run your script.
|
|
583
|
+
(Luckily, here's a couple references!)
|
|
584
|
+
https://web.archive.org/web/20190512164300id_/http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/event-types.html
|
|
585
|
+
https://web.archive.org/web/20190515021108id_/http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/key-names.html
|
|
586
|
+
https://www.tcl-lang.org/man/tcl8.4/TkCmd/keysyms.htm
|
|
587
|
+
|
|
588
|
+
**Additional parameters:**
|
|
589
|
+
After = Dictates the time in **milliseconds** the function will take to run.
|
|
590
|
+
RepeatTimes = The amount of times the function will repeat after running once.
|
|
591
|
+
(Note: You can repeat infinitely by typing 'inf' or -1.)
|
|
592
|
+
RepeatDelay = The delay in **milliseconds** that a function will take to repeat.
|
|
593
|
+
"""
|
|
594
|
+
|
|
595
|
+
if TargetWindow and KeyToBind and FunctionToBind:
|
|
596
|
+
#this is for a single key
|
|
597
|
+
if len(KeyToBind) == 1:
|
|
598
|
+
FinalKey = KeyToBind
|
|
599
|
+
|
|
600
|
+
#this is for the war veterans whose died plenty for not using <>
|
|
601
|
+
elif KeyToBind.startswith("<") and KeyToBind.endswith(">"):
|
|
602
|
+
KeyToBind = KeyToBind.title()
|
|
603
|
+
|
|
604
|
+
FinalKey = f"{KeyToBind}"
|
|
605
|
+
|
|
606
|
+
#and for the people with dementia
|
|
607
|
+
else:
|
|
608
|
+
KeyToBind = KeyToBind.title()
|
|
609
|
+
|
|
610
|
+
FinalKey = f"<{KeyToBind}>"
|
|
611
|
+
|
|
612
|
+
def EventlessBind(event):
|
|
613
|
+
def StartFunctions():
|
|
614
|
+
FunctionRunCount = 0
|
|
615
|
+
|
|
616
|
+
def repeatingFunction():
|
|
617
|
+
nonlocal FunctionRunCount, RepeatTimes
|
|
618
|
+
FunctionToBind()
|
|
619
|
+
|
|
620
|
+
if isinstance(RepeatTimes, str) and RepeatTimes.lower() == "inf":
|
|
621
|
+
RepeatTimes = -1
|
|
622
|
+
|
|
623
|
+
elif isinstance(RepeatTimes, str) and RepeatTimes != "inf":
|
|
624
|
+
raise TypeError("The function oculd not be repeated as the value typed for RepeatTimes is invalid. Did you try checking the RepeatTimes value type?")
|
|
625
|
+
|
|
626
|
+
FunctionRunCount += 1
|
|
627
|
+
|
|
628
|
+
if FunctionRunCount <= RepeatTimes or RepeatTimes == -1:
|
|
629
|
+
TargetWindow.after(RepeatDelay, repeatingFunction)
|
|
630
|
+
|
|
631
|
+
repeatingFunction()
|
|
632
|
+
|
|
633
|
+
TargetWindow.after(After, StartFunctions)
|
|
634
|
+
|
|
635
|
+
TargetWindow.bind(FinalKey, EventlessBind)
|
|
636
|
+
|
|
637
|
+
else:
|
|
638
|
+
raise ValueError("Could not bind function to an element as one of the necessary parameters were not filled. Have you tried filling all '__ToBind' parameters?")
|
|
639
|
+
#endregion
|
|
640
|
+
|
|
641
|
+
#region Moving Windows and Changing Their Geometry Smoothly Function
|
|
642
|
+
def BetterGeometry(TargetWindow, SizeX=0, SizeY=0, PosX=None, PosY=None, SizeSmooth=0, PosSmooth=0, Delay=1):
|
|
643
|
+
"""
|
|
644
|
+
# This function is a better way to set a window's geometry, both in size and position on screen, smoothing included.
|
|
645
|
+
|
|
646
|
+
**TargetWindow** = The window in which position or size will be altered.
|
|
647
|
+
|
|
648
|
+
**Geometry Positions:**
|
|
649
|
+
SizeX = The final width that the window will have.
|
|
650
|
+
SizeY = The final height that the window will have.
|
|
651
|
+
PosX = The final width position that the window will have.
|
|
652
|
+
PosY = The final height position that the window will have.
|
|
653
|
+
|
|
654
|
+
**Additional Parameters:**
|
|
655
|
+
SizeSmooth = How much the change from the current size to the final size will be smoothed. **The lesser the number, the smoother.**
|
|
656
|
+
PosSmooth = How much the movement from the current position to the final position will be smoothed. **The lesser the number, the smoother.**
|
|
657
|
+
Delay = The delay in time that will take for the window to go to it's next position. **The lesser the number, the smoother.**
|
|
658
|
+
"""
|
|
659
|
+
|
|
660
|
+
TargetWindowX = TargetWindow.winfo_width()
|
|
661
|
+
TargetWindowY = TargetWindow.winfo_height()
|
|
662
|
+
|
|
663
|
+
if SizeSmooth == 0 and PosSmooth == 0:
|
|
664
|
+
TargetWindow.geometry(f"{SizeX if SizeX != 0 else TargetWindowX}x{SizeY if SizeY != 0 else TargetWindowY}{f'+{PosX}' if PosX is not None else ''}{f'+{PosY}' if PosY is not None else ''}")
|
|
665
|
+
|
|
666
|
+
else:
|
|
667
|
+
s_smooth = max(1, int(SizeSmooth)) if SizeSmooth and SizeSmooth > 0 else 0
|
|
668
|
+
p_smooth = max(1, int(PosSmooth)) if PosSmooth and PosSmooth > 0 else 0
|
|
669
|
+
|
|
670
|
+
curX, curY = TargetWindow.winfo_x(), TargetWindow.winfo_y()
|
|
671
|
+
curW, curH = TargetWindow.winfo_width(), TargetWindow.winfo_height()
|
|
672
|
+
|
|
673
|
+
tX = int(PosX) if PosX is not None else curX
|
|
674
|
+
tY = int(PosY) if PosY is not None else curY
|
|
675
|
+
tW = int(SizeX) if (SizeX is not None and SizeX != 0) else curW
|
|
676
|
+
tH = int(SizeY) if (SizeY is not None and SizeY != 0) else curH
|
|
677
|
+
|
|
678
|
+
def TheTrueSmooth():
|
|
679
|
+
nonlocal curX, curY, curW, curH
|
|
680
|
+
|
|
681
|
+
if p_smooth > 0:
|
|
682
|
+
if curX != tX:
|
|
683
|
+
diffX = tX - curX
|
|
684
|
+
if abs(diffX) <= p_smooth: curX = tX
|
|
685
|
+
else: curX += p_smooth if diffX > 0 else -p_smooth
|
|
686
|
+
if curY != tY:
|
|
687
|
+
diffY = tY - curY
|
|
688
|
+
if abs(diffY) <= p_smooth: curY = tY
|
|
689
|
+
else: curY += p_smooth if diffY > 0 else -p_smooth
|
|
690
|
+
|
|
691
|
+
if s_smooth > 0:
|
|
692
|
+
if curW != tW:
|
|
693
|
+
diffW = tW - curW
|
|
694
|
+
if abs(diffW) <= s_smooth: curW = tW
|
|
695
|
+
else: curW += s_smooth if diffW > 0 else -s_smooth
|
|
696
|
+
if curH != tH:
|
|
697
|
+
diffH = tH - curH
|
|
698
|
+
if abs(diffH) <= s_smooth: curH = tH
|
|
699
|
+
else: curH += s_smooth if diffH > 0 else -s_smooth
|
|
700
|
+
|
|
701
|
+
TargetWindow.geometry(f"{curW}x{curH}+{curX}+{curY}")
|
|
702
|
+
|
|
703
|
+
if curX != tX or curY != tY or curW != tW or curH != tH:
|
|
704
|
+
TargetWindow.after(Delay, TheTrueSmooth)
|
|
705
|
+
|
|
706
|
+
TheTrueSmooth()
|
|
707
|
+
#endregion
|
|
708
|
+
|
|
709
|
+
#endregion
|
|
710
|
+
|
|
711
|
+
############################################
|
|
712
|
+
# time to move on to tk.Canvas, my nightmare
|
|
713
|
+
############################################
|
|
714
|
+
|
|
715
|
+
#region ahhh... tk.Canvas, where hell sets loose
|
|
716
|
+
|
|
717
|
+
#region Function that Automatically Creates and Packs Canvas
|
|
718
|
+
def CreateCanvas(TargetWindow, SizeX=250, SizeY=250, BgColor="#f0f0f0", PackType=None, Anchor="center", PadX=0, PadY=0, Side=tk.TOP, Row=0, Column=0, Sticky=None, X=None, Y=None, RelX=None, RelY=None):
|
|
719
|
+
"""
|
|
720
|
+
# Creates a tk.Canvas element and automatically packs it.
|
|
721
|
+
|
|
722
|
+
**Necessary parameters:**
|
|
723
|
+
TargetWindow = The window that will be receiving the Canvas.
|
|
724
|
+
|
|
725
|
+
**Geometry Parameters:**
|
|
726
|
+
SizeX = The width in pixels of the Canvas Widget.
|
|
727
|
+
SizeY = The height in pixels of the Canvas Widget.
|
|
728
|
+
|
|
729
|
+
**Extra Customization:**
|
|
730
|
+
BgColor = What the background of the label will be colored as.
|
|
731
|
+
|
|
732
|
+
# Packing Types and their parameters:
|
|
733
|
+
|
|
734
|
+
**Pack (The default type, packs label and contains borders.):**
|
|
735
|
+
PadX = Width for the padding of the label.
|
|
736
|
+
PadY = Height for the padding of the label.
|
|
737
|
+
Side = Side where the label will go to in the widget, is relative.
|
|
738
|
+
Sticky = Position where the label will stick to in the widget, is not relative.
|
|
739
|
+
|
|
740
|
+
**Grid (for mounting grids and organized sheets):**
|
|
741
|
+
Row = The row where your element will be placed. (Horizontal)
|
|
742
|
+
Column = the column where your element will be placed. (Vertical)
|
|
743
|
+
PadX = the padding width your element will have, affects elements in and outside of the grid.
|
|
744
|
+
PadY = the padding height your element will have, affects elements in and outside of the grid.
|
|
745
|
+
|
|
746
|
+
**Place (for accurately placing elements in absolute or relative positions):**
|
|
747
|
+
X = The absolute position of the element in your window, cannot be moved. (Width)
|
|
748
|
+
Y = The absolute position of the element in your window, cannot be moved. (Height)
|
|
749
|
+
RelX = The relative position of the element in your window, can be moved. (Width)
|
|
750
|
+
RelY = The relative position of the element in your window, can be moved. (Height)
|
|
751
|
+
"""
|
|
752
|
+
|
|
753
|
+
if not isinstance(TargetWindow, (tk.Tk, tk.Toplevel)):
|
|
754
|
+
raise TypeError(f"Expected a Tkinter window, but got {type(TargetWindow).__name__} instead.")
|
|
755
|
+
|
|
756
|
+
NewCanvas = tk.Canvas(TargetWindow, width=SizeX, height=SizeY, bg=BgColor)
|
|
757
|
+
|
|
758
|
+
if PackType is not None:
|
|
759
|
+
PackType = PackType.lower()
|
|
760
|
+
|
|
761
|
+
if PackType is not None and PackType not in ["place", "grid", "pack"]:
|
|
762
|
+
raise ValueError(f"Expected a proper packing style, but got {PackType} instead. Current packing types are 'Pack', 'Grid', or 'Place'")
|
|
763
|
+
|
|
764
|
+
elif PackType == "pack":
|
|
765
|
+
NewCanvas.pack(anchor=Anchor, padx=PadX, pady=PadY, side=Side, sticky=Sticky)
|
|
766
|
+
|
|
767
|
+
elif PackType == "place":
|
|
768
|
+
NewCanvas.place(x=X, y=Y, relx=RelX, rely=RelY, anchor=Anchor)
|
|
769
|
+
|
|
770
|
+
elif PackType == "grid":
|
|
771
|
+
NewCanvas.grid(row=Row, column=Column, padx=PadX, pady=PadY)
|
|
772
|
+
|
|
773
|
+
return NewCanvas
|
|
774
|
+
#endregion
|
|
775
|
+
|
|
776
|
+
#region Creating Various Shapes in Canvas
|
|
777
|
+
def CreateCanvasShapes(ForCanvas, Shape, PosX, PosY, SquareSize=25, RectangleX=20, RectangleY=40, CircleSize=25, OvalX=20, OvalY=40, color="blue", OutlineColor="black", ActiveColor=None):
|
|
778
|
+
"""
|
|
779
|
+
# Creates a custom shape for a tk.Canvas and automatically centers it in the give ncoordinates and size.
|
|
780
|
+
|
|
781
|
+
**Necessary parameters:**
|
|
782
|
+
ForCanvas = Dictates the used tk.Canvas that the shape will be placed in.
|
|
783
|
+
Shape = What shape will be placed in the tk.Canvas.
|
|
784
|
+
(Options: Square, Rectangle, Circle, Oval)
|
|
785
|
+
|
|
786
|
+
**Geometry parameters:**
|
|
787
|
+
PosX = The horizontal position in pixels where the shape will be placed.
|
|
788
|
+
PosY = The vertical position in pixels where the shape will be placed.
|
|
789
|
+
|
|
790
|
+
**Customization parameters:**
|
|
791
|
+
Color = The color that the shape will be filled with.
|
|
792
|
+
OutlineColor = The color of the shape's border.
|
|
793
|
+
ActiveColor = The color that the shappe will have when it's hovered on, changes back to normal when hovering off.
|
|
794
|
+
|
|
795
|
+
# Shape specific parameters
|
|
796
|
+
|
|
797
|
+
**Square parameters:**
|
|
798
|
+
SquareSize = The size in pixels that the square will have.
|
|
799
|
+
|
|
800
|
+
**Circle parameters:**
|
|
801
|
+
CircleSize = The size in pixels that the circle will have.
|
|
802
|
+
|
|
803
|
+
**Rectangle parameters:**
|
|
804
|
+
RectangleX = The width of the rectangle in pixels.
|
|
805
|
+
RectangleY = The height of the rectangle in pixels.
|
|
806
|
+
|
|
807
|
+
**Oval parameters:**
|
|
808
|
+
OvalX = The width of the oval in pixels.
|
|
809
|
+
OvalY = The height of the oval in pixels.
|
|
810
|
+
"""
|
|
811
|
+
|
|
812
|
+
if not isinstance(ForCanvas, tk.Canvas):
|
|
813
|
+
raise ValueError(f"Could not create custom canvas shape as {ForCanvas} is not a valid canvas. Did you try checking the tk.Canvas given?")
|
|
814
|
+
|
|
815
|
+
Shape = Shape.lower()
|
|
816
|
+
color = color.lower()
|
|
817
|
+
OutlineColor = OutlineColor.lower()
|
|
818
|
+
ActiveColor = color if ActiveColor is None else ActiveColor.lower()
|
|
819
|
+
|
|
820
|
+
if Shape == "square":
|
|
821
|
+
offset = SquareSize // 2
|
|
822
|
+
NewShape = ForCanvas.create_rectangle(PosX - offset, PosY - offset, PosX + offset, PosY + offset, fill=color, outline=OutlineColor, activefill=ActiveColor)
|
|
823
|
+
|
|
824
|
+
elif Shape == "rectangle":
|
|
825
|
+
Xoffset = RectangleX // 2
|
|
826
|
+
Yoffset = RectangleY // 2
|
|
827
|
+
|
|
828
|
+
x1 = PosX - Yoffset
|
|
829
|
+
y1 = PosY - Xoffset
|
|
830
|
+
x2 = PosX + Yoffset
|
|
831
|
+
y2 = PosY + Xoffset
|
|
832
|
+
|
|
833
|
+
NewShape = ForCanvas.create_rectangle(x1, y1, x2, y2, fill=color, outline=OutlineColor, activefill=ActiveColor)
|
|
834
|
+
|
|
835
|
+
elif Shape == "circle":
|
|
836
|
+
offset = CircleSize // 2
|
|
837
|
+
NewShape = ForCanvas.create_oval(PosX - offset, PosY - offset, PosX + offset, PosY + offset, fill=color, outline=OutlineColor, activefill=ActiveColor)
|
|
838
|
+
|
|
839
|
+
elif Shape == "oval":
|
|
840
|
+
Xoffset = OvalX // 2
|
|
841
|
+
Yoffset = OvalY // 2
|
|
842
|
+
|
|
843
|
+
x1 = PosX - Xoffset
|
|
844
|
+
y1 = PosY - Yoffset
|
|
845
|
+
x2 = PosX + Yoffset
|
|
846
|
+
y2 = PosY + Xoffset
|
|
847
|
+
|
|
848
|
+
NewShape = ForCanvas.create_oval(x1, y1, x2, y2, fill=color, outline=OutlineColor, activefill=ActiveColor)
|
|
849
|
+
|
|
850
|
+
else:
|
|
851
|
+
raise ValueError(f"Could not create custom canvas shape as {Shape} is not a valid option. Did you try checking the allowed shapes in the doctype?")
|
|
852
|
+
|
|
853
|
+
return NewShape
|
|
854
|
+
#endregion
|
|
855
|
+
|
|
856
|
+
#endregion
|
|
857
|
+
|
|
858
|
+
#############################################
|
|
859
|
+
# time to do some extra stuff that's cool too
|
|
860
|
+
#############################################
|
|
861
|
+
|
|
862
|
+
#region Messing with Pillow and Image Rendering
|
|
863
|
+
def BetterPhotoImage(Image, RotateDegrees=None, RotateChangeSize=False, ResizeImageX=None, ResizeImageY=None, MirrorImage=None):
|
|
864
|
+
"""
|
|
865
|
+
# Renders and modifies a custom image using PIL and returns the modified Tkinter ready image.
|
|
866
|
+
|
|
867
|
+
**Required parameters:**
|
|
868
|
+
Image = The file name of the image being used.
|
|
869
|
+
|
|
870
|
+
**Image customization parameters:**
|
|
871
|
+
RotateDegrees = The angle, in degrees, where the image will be rotated. **Must be an integer.**
|
|
872
|
+
RotateChangeSize = Wether when rotating an image, the size will maintain for be affected. **Does not work without RotateDegrees.**
|
|
873
|
+
MirrorImage = If the image will be mirrored vertically or horizontally.
|
|
874
|
+
(Options: 'None', 'Vertical', 'Horizontal')
|
|
875
|
+
|
|
876
|
+
**Resizing parameters:**
|
|
877
|
+
ResizeImageX = Value, in pixels, what the window's width will be resized to.
|
|
878
|
+
ResizeImageY = Valye, in pixels, what the window's height will be resized to.
|
|
879
|
+
"""
|
|
880
|
+
#function to tell wether the image is actually valid
|
|
881
|
+
def is_valid_image_pillow(imgg):
|
|
882
|
+
try:
|
|
883
|
+
with img.open(imgg) as immg:
|
|
884
|
+
immg.verify()
|
|
885
|
+
return True
|
|
886
|
+
except (IOError, SyntaxError):
|
|
887
|
+
return False
|
|
888
|
+
#liar liar pants on fire i guess
|
|
889
|
+
|
|
890
|
+
#back to the actual code
|
|
891
|
+
if is_valid_image_pillow(Image):
|
|
892
|
+
NewImage = img.open(Image)
|
|
893
|
+
|
|
894
|
+
if NewImage.mode != 'RGBA':
|
|
895
|
+
NewImage = NewImage.convert('RGBA')
|
|
896
|
+
|
|
897
|
+
IMGwidth, IMGheight = NewImage.size
|
|
898
|
+
|
|
899
|
+
if RotateDegrees is not None:
|
|
900
|
+
if not isinstance(RotateDegrees, bool):
|
|
901
|
+
warn.warn(f"'RotateChangeSize' value was {RotateChangeSize} and as it is not True or False, was defaulted to false.")
|
|
902
|
+
RotateChangeSize = False
|
|
903
|
+
|
|
904
|
+
if not isinstance(RotateDegrees, int):
|
|
905
|
+
raise TypeError("Could not properly rotate image as the value given was not a proper integer. Did you try ")
|
|
906
|
+
|
|
907
|
+
NewImage = NewImage.rotate(RotateDegrees, expand=RotateChangeSize, fillcolor=(0, 0, 0, 0))
|
|
908
|
+
IMGwidth, IMGheight = NewImage.size
|
|
909
|
+
|
|
910
|
+
if ResizeImageX is not None or ResizeImageY is not None:
|
|
911
|
+
NewImage = NewImage.resize((ResizeImageX if ResizeImageX is not None else IMGwidth, ResizeImageY if ResizeImageY is not None else IMGheight))
|
|
912
|
+
|
|
913
|
+
if MirrorImage is not None:
|
|
914
|
+
MirrorImage = MirrorImage.lower()
|
|
915
|
+
|
|
916
|
+
if MirrorImage == "vertical":
|
|
917
|
+
NewImage = NewImage.transpose(Image.FLIP_TOP_BOTTOM)
|
|
918
|
+
|
|
919
|
+
elif MirrorImage == "horizontal":
|
|
920
|
+
NewImage = NewImage.transpose(Image.FLIP_TOP_BOTTOM)
|
|
921
|
+
|
|
922
|
+
else:
|
|
923
|
+
raise ValueError("Could not flip image as the given value was not 'vertical' or 'horizontal'. Did you try checking the 'MirrorImage' value?")
|
|
924
|
+
|
|
925
|
+
NewImage = imgtk.PhotoImage(NewImage)
|
|
926
|
+
|
|
927
|
+
return NewImage
|
|
928
|
+
|
|
929
|
+
else:
|
|
930
|
+
raise ValueError("Could not create a ImageTk image as the file given was not a proper image file/format. Did you try checking the 'Image' parameter given?")
|
|
931
|
+
|
|
932
|
+
#endregion
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: easykinter
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: A wrapper to make Tkinter 90% less boring.
|
|
5
|
+
Author-email: Starly <starly.alt.acc1@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
9
|
+
Requires-Python: >=3.12
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: pillow>=10.0.0
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/easykinter/EasyKinter.py
|
|
4
|
+
src/easykinter/__init__.py
|
|
5
|
+
src/easykinter.egg-info/PKG-INFO
|
|
6
|
+
src/easykinter.egg-info/SOURCES.txt
|
|
7
|
+
src/easykinter.egg-info/dependency_links.txt
|
|
8
|
+
src/easykinter.egg-info/requires.txt
|
|
9
|
+
src/easykinter.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pillow>=10.0.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
easykinter
|