crystalwindow 4.5__py3-none-any.whl → 4.7__py3-none-any.whl
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.
- crystalwindow/FileHelper.py +159 -38
- crystalwindow/Icons/file_icons.png +0 -0
- crystalwindow/__init__.py +19 -24
- crystalwindow/assets.py +250 -59
- crystalwindow/clock.py +137 -13
- crystalwindow/color_handler.py +3 -21
- crystalwindow/crystal3d.py +135 -56
- crystalwindow/fun_helpers.py +4 -0
- crystalwindow/gametests/3dsquare.py +2 -2
- crystalwindow/gametests/gravitytest.py +96 -23
- crystalwindow/gametests/guitesting.py +4 -2
- crystalwindow/gametests/squaremove.py +4 -1
- crystalwindow/gametests/testtttagain.py +17 -0
- crystalwindow/gui.py +87 -17
- crystalwindow/gui_ext.py +51 -39
- crystalwindow/objects.py +171 -0
- crystalwindow/sprites.py +174 -25
- crystalwindow/tilemap.py +1 -1
- crystalwindow/websearch.py +91 -165
- crystalwindow/window.py +430 -12
- {crystalwindow-4.5.dist-info → crystalwindow-4.7.dist-info}/METADATA +6 -2
- crystalwindow-4.7.dist-info/RECORD +43 -0
- crystalwindow/cw_client.py +0 -95
- crystalwindow/player.py +0 -106
- crystalwindow-4.5.dist-info/RECORD +0 -42
- {crystalwindow-4.5.dist-info → crystalwindow-4.7.dist-info}/WHEEL +0 -0
- {crystalwindow-4.5.dist-info → crystalwindow-4.7.dist-info}/licenses/LICENSE +0 -0
- {crystalwindow-4.5.dist-info → crystalwindow-4.7.dist-info}/top_level.txt +0 -0
crystalwindow/window.py
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# === Window Management (tkinter version) ===
|
|
2
|
+
import random
|
|
2
3
|
import tkinter as tk
|
|
3
|
-
import base64, io, os, sys, contextlib, time
|
|
4
|
-
from .websearch import WebSearch
|
|
4
|
+
import base64, io, os, sys, contextlib, time, math
|
|
5
5
|
|
|
6
|
+
from pyscreeze import center
|
|
6
7
|
|
|
7
8
|
# === Boot ===
|
|
8
9
|
main_file = os.path.basename(sys.argv[0])
|
|
@@ -20,6 +21,35 @@ def decode_logo():
|
|
|
20
21
|
except Exception:
|
|
21
22
|
return None
|
|
22
23
|
|
|
24
|
+
class _CameraShake:
|
|
25
|
+
def __init__(self):
|
|
26
|
+
self.active = False
|
|
27
|
+
self.intensity = 5
|
|
28
|
+
self.end_time = 0
|
|
29
|
+
self.offset = (0, 0)
|
|
30
|
+
|
|
31
|
+
def start(self, intensity, duration):
|
|
32
|
+
import time, random
|
|
33
|
+
self.active = True
|
|
34
|
+
self.intensity = intensity
|
|
35
|
+
self.end_time = time.time() + duration
|
|
36
|
+
|
|
37
|
+
def update(self):
|
|
38
|
+
import time, random
|
|
39
|
+
if not self.active:
|
|
40
|
+
self.offset = (0, 0)
|
|
41
|
+
return
|
|
42
|
+
|
|
43
|
+
if time.time() > self.end_time:
|
|
44
|
+
self.active = False
|
|
45
|
+
self.offset = (0, 0)
|
|
46
|
+
return
|
|
47
|
+
|
|
48
|
+
self.offset = (
|
|
49
|
+
random.randint(-self.intensity, self.intensity),
|
|
50
|
+
random.randint(-self.intensity, self.intensity)
|
|
51
|
+
)
|
|
52
|
+
|
|
23
53
|
|
|
24
54
|
class Window:
|
|
25
55
|
# === Keymap to simplify key usage ===
|
|
@@ -114,13 +144,17 @@ class Window:
|
|
|
114
144
|
self.keys = {}
|
|
115
145
|
self.mouse_pos = (0, 0)
|
|
116
146
|
self._mouse_pressed = (False, False, False)
|
|
147
|
+
self.cam_x = 0
|
|
148
|
+
self.cam_y = 0
|
|
149
|
+
self.shake_timer = 0
|
|
150
|
+
self.shake_strength = 0
|
|
151
|
+
|
|
152
|
+
self.camerashake = _CameraShake()
|
|
117
153
|
self._bg_color = (20, 20, 50)
|
|
118
154
|
self.draw_calls = []
|
|
119
155
|
self._key_last = {} # new dict to store last press time per key
|
|
120
156
|
self._key_cooldown = 0.00001 # seconds, tweak for faster/slower input
|
|
121
157
|
|
|
122
|
-
WebSearch.register_cw_tab(self)
|
|
123
|
-
|
|
124
158
|
# === Event bindings ===
|
|
125
159
|
self.root.bind("<KeyPress>", self._on_keydown)
|
|
126
160
|
self.root.bind("<KeyRelease>", self._on_keyup)
|
|
@@ -128,6 +162,15 @@ class Window:
|
|
|
128
162
|
self.root.bind("<ButtonPress>", self._on_mousedown)
|
|
129
163
|
self.root.bind("<ButtonRelease>", self._on_mouseup)
|
|
130
164
|
|
|
165
|
+
def _apply_camera_shake(self):
|
|
166
|
+
if self.shake_timer > 0:
|
|
167
|
+
self.shake_timer -= 1
|
|
168
|
+
self.cam_x = random.randint(-self.shake_strength, self.shake_strength)
|
|
169
|
+
self.cam_y = random.randint(-self.shake_strength, self.shake_strength)
|
|
170
|
+
else:
|
|
171
|
+
self.cam_x = 0
|
|
172
|
+
self.cam_y = 0
|
|
173
|
+
|
|
131
174
|
# === Input ===
|
|
132
175
|
def _on_keydown(self, event):
|
|
133
176
|
self.keys[event.keysym] = True
|
|
@@ -169,6 +212,56 @@ class Window:
|
|
|
169
212
|
return self._mouse_pressed[button - 1]
|
|
170
213
|
def add_tab(self, title, widget):
|
|
171
214
|
self.tabs.addTab(widget, title)
|
|
215
|
+
|
|
216
|
+
# ====================================
|
|
217
|
+
# Im gonna joke on the viewers here for a sec lmao
|
|
218
|
+
# ====================================
|
|
219
|
+
def f_crash(self, msg="oh no bruh... program ded 💀"):
|
|
220
|
+
"""start fake crash mode lol"""
|
|
221
|
+
import tkinter as tk
|
|
222
|
+
|
|
223
|
+
if hasattr(self, "_fcrash_window"):
|
|
224
|
+
return # already running
|
|
225
|
+
|
|
226
|
+
self._fcrash_window = tk.Toplevel(self.root)
|
|
227
|
+
self._fcrash_window.title("Fatal Error")
|
|
228
|
+
self._fcrash_window.geometry("400x200")
|
|
229
|
+
self._fcrash_window.configure(bg="#101010")
|
|
230
|
+
self._fcrash_window.attributes("-topmost", True)
|
|
231
|
+
|
|
232
|
+
tk.Label(
|
|
233
|
+
self._fcrash_window,
|
|
234
|
+
text=msg,
|
|
235
|
+
fg="red",
|
|
236
|
+
bg="#101010",
|
|
237
|
+
font=("Consolas", 14, "bold")
|
|
238
|
+
).pack(pady=20)
|
|
239
|
+
|
|
240
|
+
tk.Label(
|
|
241
|
+
self._fcrash_window,
|
|
242
|
+
text="System meltdown imminent...",
|
|
243
|
+
fg="white",
|
|
244
|
+
bg="#101010",
|
|
245
|
+
font=("Consolas", 11)
|
|
246
|
+
).pack()
|
|
247
|
+
|
|
248
|
+
# disable user clicking main window
|
|
249
|
+
self.root.attributes("-disabled", True)
|
|
250
|
+
|
|
251
|
+
def end_fcrash(self):
|
|
252
|
+
"""end fake crash prank"""
|
|
253
|
+
if hasattr(self, "_fcrash_window"):
|
|
254
|
+
self._fcrash_window.destroy()
|
|
255
|
+
del self._fcrash_window
|
|
256
|
+
|
|
257
|
+
# re-enable main window
|
|
258
|
+
self.root.attributes("-disabled", False)
|
|
259
|
+
|
|
260
|
+
# An actuall fucking Real crash that bricks your system sorry bud
|
|
261
|
+
def crash(self, msg="REAL CRASH: u messed up bruh"):
|
|
262
|
+
"""actual crash (safe)."""
|
|
263
|
+
raise RuntimeError(msg)
|
|
264
|
+
|
|
172
265
|
# === Drawing ===
|
|
173
266
|
def fill(self, color):
|
|
174
267
|
"""Fill background with a color"""
|
|
@@ -186,20 +279,345 @@ class Window:
|
|
|
186
279
|
self.canvas.delete("all")
|
|
187
280
|
self.canvas.configure(bg=fill_color)
|
|
188
281
|
|
|
282
|
+
# ------------------------------------------------------
|
|
283
|
+
# INTERNAL: color / texture resolver
|
|
284
|
+
# ------------------------------------------------------
|
|
285
|
+
def _resolve_color_or_texture(self, color):
|
|
286
|
+
"""
|
|
287
|
+
Returns (mode, value)
|
|
288
|
+
mode = "rgb", "hex", "fallback", "texture"
|
|
289
|
+
"""
|
|
290
|
+
# Texture?
|
|
291
|
+
try:
|
|
292
|
+
from PIL.ImageTk import PhotoImage as PILPhoto
|
|
293
|
+
import tkinter
|
|
294
|
+
if isinstance(color, (tkinter.PhotoImage, PILPhoto)):
|
|
295
|
+
return ("texture", color)
|
|
296
|
+
except:
|
|
297
|
+
pass
|
|
298
|
+
|
|
299
|
+
# Fallback dict
|
|
300
|
+
if isinstance(color, dict) and color.get("fallback"):
|
|
301
|
+
r, g, b = color["color"]
|
|
302
|
+
return ("rgb", f"#{r:02x}{g:02x}{b:02x}")
|
|
303
|
+
|
|
304
|
+
# Hex
|
|
305
|
+
if isinstance(color, str) and color.startswith("#"):
|
|
306
|
+
return ("hex", color)
|
|
307
|
+
|
|
308
|
+
# Color instance
|
|
309
|
+
try:
|
|
310
|
+
if hasattr(color, "to_tuple"):
|
|
311
|
+
r, g, b, *_ = color.to_tuple()
|
|
312
|
+
return ("rgb", f"#{r:02x}{g:02x}{b:02x}")
|
|
313
|
+
except:
|
|
314
|
+
pass
|
|
315
|
+
|
|
316
|
+
# RGB tuple
|
|
317
|
+
if isinstance(color, (tuple, list)) and len(color) >= 3:
|
|
318
|
+
r, g, b = color[0], color[1], color[2]
|
|
319
|
+
return ("rgb", f"#{r:02x}{g:02x}{b:02x}")
|
|
320
|
+
|
|
321
|
+
return ("rgb", "#ffffff")
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
# ------------------------------------------------------
|
|
325
|
+
# DRAW RECTANGLE
|
|
326
|
+
# ------------------------------------------------------
|
|
189
327
|
def draw_rect(self, color, rect):
|
|
328
|
+
mode, value = self._resolve_color_or_texture(color)
|
|
190
329
|
x, y, w, h = rect
|
|
191
|
-
self.draw_calls.append(("rect", color, x, y, w, h))
|
|
192
330
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
331
|
+
if mode == "texture":
|
|
332
|
+
from PIL import Image, ImageTk
|
|
333
|
+
try:
|
|
334
|
+
pil = ImageTk.getimage(value).resize((w, h))
|
|
335
|
+
except:
|
|
336
|
+
return
|
|
337
|
+
tk_tex = ImageTk.PhotoImage(pil)
|
|
338
|
+
if not hasattr(self, "_tex_cache"): self._tex_cache = []
|
|
339
|
+
self._tex_cache.append(tk_tex)
|
|
340
|
+
self.canvas.create_image(x, y, image=tk_tex, anchor="nw")
|
|
341
|
+
else:
|
|
342
|
+
self.canvas.create_rectangle(
|
|
343
|
+
x, y, x+w, y+h,
|
|
344
|
+
fill=value, outline=""
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
# ------------------------------------------------------
|
|
349
|
+
# DRAW LINE
|
|
350
|
+
# ------------------------------------------------------
|
|
351
|
+
def draw_line(self, p1, p2, color, width=2):
|
|
352
|
+
mode, value = self._resolve_color_or_texture(color)
|
|
353
|
+
|
|
354
|
+
if mode != "texture":
|
|
355
|
+
self.canvas.create_line(
|
|
356
|
+
p1[0], p1[1], p2[0], p2[1],
|
|
357
|
+
fill=value, width=width
|
|
358
|
+
)
|
|
359
|
+
return
|
|
360
|
+
|
|
361
|
+
# Texture line
|
|
362
|
+
from PIL import Image, ImageDraw, ImageTk
|
|
363
|
+
x1, y1 = p1
|
|
364
|
+
x2, y2 = p2
|
|
365
|
+
|
|
366
|
+
minx, maxx = int(min(x1, x2)), int(max(x1, x2))
|
|
367
|
+
miny, maxy = int(min(y1, y2)), int(max(y1, y2))
|
|
368
|
+
|
|
369
|
+
w = max(2, maxx - minx)
|
|
370
|
+
h = max(2, maxy - miny)
|
|
371
|
+
|
|
372
|
+
mask = Image.new("L", (w, h), 0)
|
|
373
|
+
m = ImageDraw.Draw(mask)
|
|
374
|
+
m.line([(x1-minx, y1-miny), (x2-minx, y2-miny)], fill=255, width=width)
|
|
375
|
+
|
|
376
|
+
tex = ImageTk.getimage(value).resize((w, h))
|
|
377
|
+
tex.putalpha(mask)
|
|
378
|
+
|
|
379
|
+
tk_img = ImageTk.PhotoImage(tex)
|
|
380
|
+
if not hasattr(self, "_line_tex"): self._line_tex = []
|
|
381
|
+
self._line_tex.append(tk_img)
|
|
382
|
+
|
|
383
|
+
self.canvas.create_image(minx, miny, image=tk_img, anchor="nw")
|
|
384
|
+
|
|
385
|
+
# ------------------------------------------------------
|
|
386
|
+
# DRAW CIRCLE ——— FIXED SIGNATURE
|
|
387
|
+
# ------------------------------------------------------
|
|
388
|
+
def draw_circle(self, color, center, radius, width=0):
|
|
389
|
+
mode, value = self._resolve_color_or_texture(color)
|
|
196
390
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
391
|
+
# safe unpacking
|
|
392
|
+
cx, cy = center[:2]
|
|
393
|
+
|
|
394
|
+
if mode != "texture":
|
|
395
|
+
self.canvas.create_oval(
|
|
396
|
+
cx - radius, cy - radius,
|
|
397
|
+
cx + radius, cy + radius,
|
|
398
|
+
fill=value if width == 0 else "",
|
|
399
|
+
outline=value if width > 0 else "",
|
|
400
|
+
width=width
|
|
401
|
+
)
|
|
402
|
+
return
|
|
200
403
|
|
|
404
|
+
# textured circle
|
|
405
|
+
from PIL import Image, ImageDraw, ImageTk
|
|
406
|
+
d = int(radius * 2)
|
|
407
|
+
|
|
408
|
+
mask = Image.new("L", (d, d), 0)
|
|
409
|
+
m = ImageDraw.Draw(mask)
|
|
410
|
+
m.ellipse((0, 0, d, d), fill=255)
|
|
411
|
+
|
|
412
|
+
tex = ImageTk.getimage(value).resize((d, d))
|
|
413
|
+
tex.putalpha(mask)
|
|
414
|
+
|
|
415
|
+
tk_img = ImageTk.PhotoImage(tex)
|
|
416
|
+
if not hasattr(self, "_circ_tex"):
|
|
417
|
+
self._circ_tex = []
|
|
418
|
+
self._circ_tex.append(tk_img)
|
|
419
|
+
|
|
420
|
+
self.canvas.create_image(cx - radius, cy - radius, image=tk_img, anchor="nw")
|
|
421
|
+
|
|
422
|
+
# ------------------------------------------------------
|
|
423
|
+
# DRAW POLYGON (FILL)
|
|
424
|
+
# ------------------------------------------------------
|
|
425
|
+
def draw_polygon(self, color, points):
|
|
426
|
+
mode, value = self._resolve_color_or_texture(color)
|
|
427
|
+
|
|
428
|
+
if mode != "texture":
|
|
429
|
+
self.canvas.create_polygon(points, fill=value, outline="")
|
|
430
|
+
return
|
|
431
|
+
|
|
432
|
+
from PIL import Image, ImageDraw, ImageTk
|
|
433
|
+
xs, ys = zip(*points)
|
|
434
|
+
minx, maxx = min(xs), max(xs)
|
|
435
|
+
miny, maxy = min(ys), max(ys)
|
|
436
|
+
|
|
437
|
+
w = int(maxx - minx)
|
|
438
|
+
h = int(maxy - miny)
|
|
439
|
+
if w < 2 or h < 2: return
|
|
440
|
+
|
|
441
|
+
mask = Image.new("L", (w, h), 0)
|
|
442
|
+
m = ImageDraw.Draw(mask)
|
|
443
|
+
m.polygon([(x-minx, y-miny) for x, y in points], fill=255)
|
|
444
|
+
|
|
445
|
+
tex = ImageTk.getimage(value).resize((w, h))
|
|
446
|
+
tex.putalpha(mask)
|
|
447
|
+
|
|
448
|
+
tk_img = ImageTk.PhotoImage(tex)
|
|
449
|
+
if not hasattr(self, "_poly_tex"): self._poly_tex = []
|
|
450
|
+
self._poly_tex.append(tk_img)
|
|
451
|
+
|
|
452
|
+
self.canvas.create_image(minx, miny, image=tk_img, anchor="nw")
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
# ------------------------------------------------------
|
|
456
|
+
# DRAW POLYGON OUTLINE
|
|
457
|
+
# ------------------------------------------------------
|
|
458
|
+
def draw_polygon_outline(self, color, points, width=2):
|
|
459
|
+
mode, value = self._resolve_color_or_texture(color)
|
|
460
|
+
|
|
461
|
+
if mode != "texture":
|
|
462
|
+
self.canvas.create_polygon(points, outline=value, width=width, fill="")
|
|
463
|
+
return
|
|
464
|
+
|
|
465
|
+
from PIL import Image, ImageDraw, ImageTk
|
|
466
|
+
xs, ys = zip(*points)
|
|
467
|
+
minx, maxx = min(xs), max(xs)
|
|
468
|
+
miny, maxy = min(ys), max(ys)
|
|
469
|
+
|
|
470
|
+
w = int(maxx - minx)
|
|
471
|
+
h = int(maxy - miny)
|
|
472
|
+
|
|
473
|
+
mask = Image.new("L", (w, h), 0)
|
|
474
|
+
m = ImageDraw.Draw(mask)
|
|
475
|
+
m.line([(x-minx, y-miny) for x, y in points] +
|
|
476
|
+
[(points[0][0]-minx, points[0][1]-miny)],
|
|
477
|
+
fill=255, width=width)
|
|
478
|
+
|
|
479
|
+
tex = ImageTk.getimage(value).resize((w, h))
|
|
480
|
+
tex.putalpha(mask)
|
|
481
|
+
|
|
482
|
+
tk_img = ImageTk.PhotoImage(tex)
|
|
483
|
+
if not hasattr(self, "_poly_oline_tex"): self._poly_oline_tex = []
|
|
484
|
+
self._poly_oline_tex.append(tk_img)
|
|
485
|
+
|
|
486
|
+
self.canvas.create_image(minx, miny, image=tk_img, anchor="nw")
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
# ------------------------------------------------------
|
|
490
|
+
# DRAW STAR
|
|
491
|
+
# ------------------------------------------------------
|
|
492
|
+
def draw_star(self, color, center, outer_radius, inner_radius=None,
|
|
493
|
+
points=5, stroke=None, stroke_width=2):
|
|
494
|
+
|
|
495
|
+
if inner_radius is None:
|
|
496
|
+
inner_radius = outer_radius * 0.5
|
|
497
|
+
|
|
498
|
+
cx, cy = center
|
|
499
|
+
step = math.pi / points
|
|
500
|
+
ang = -math.pi / 2
|
|
501
|
+
verts = []
|
|
502
|
+
|
|
503
|
+
for i in range(points * 2):
|
|
504
|
+
r = outer_radius if i % 2 == 0 else inner_radius
|
|
505
|
+
verts.append((cx + math.cos(ang) * r,
|
|
506
|
+
cy + math.sin(ang) * r))
|
|
507
|
+
ang += step
|
|
508
|
+
|
|
509
|
+
self.draw_polygon(color, verts)
|
|
510
|
+
if stroke:
|
|
511
|
+
self.draw_polygon_outline(stroke, verts, stroke_width)
|
|
512
|
+
|
|
513
|
+
# ------------------------------------------------------
|
|
514
|
+
# DRAW TEXT — SAFE, FIXED, TKINTER-PROOF
|
|
515
|
+
# ------------------------------------------------------
|
|
516
|
+
def draw_text(self, text, font="Arial", size=16, color=(255, 255, 255), pos=(0, 0), **kwargs):
|
|
517
|
+
# extract style kwargs safely (DO NOT send them to tkinter)
|
|
518
|
+
bold = kwargs.pop("bold", False)
|
|
519
|
+
italic = kwargs.pop("italic", False)
|
|
520
|
+
|
|
521
|
+
# scale
|
|
522
|
+
scaled_size = size
|
|
523
|
+
|
|
524
|
+
# resolve color or texture
|
|
525
|
+
mode, value = self._resolve_color_or_texture(color)
|
|
526
|
+
|
|
527
|
+
# build font style string
|
|
528
|
+
style = ""
|
|
529
|
+
if bold:
|
|
530
|
+
style += "bold "
|
|
531
|
+
if italic:
|
|
532
|
+
style += "italic"
|
|
533
|
+
style = style.strip()
|
|
534
|
+
|
|
535
|
+
# tkinter font tuple
|
|
536
|
+
if style:
|
|
537
|
+
font_tuple = (font, scaled_size, style)
|
|
538
|
+
else:
|
|
539
|
+
font_tuple = (font, scaled_size)
|
|
540
|
+
|
|
541
|
+
# ---------------- NORMAL COLOR TEXT ----------------
|
|
542
|
+
if mode != "texture":
|
|
543
|
+
fill = f"#{color[0]:02x}{color[1]:02x}{color[2]:02x}"
|
|
544
|
+
|
|
545
|
+
# HARD DELETE unsupported kwargs (just in case)
|
|
546
|
+
kwargs.pop("bold", None)
|
|
547
|
+
kwargs.pop("italic", None)
|
|
548
|
+
|
|
549
|
+
# allowed kwargs ONLY
|
|
550
|
+
ALLOWED = {"anchor", "justify", "tags"}
|
|
551
|
+
safe_kwargs = {k: v for k, v in kwargs.items() if k in ALLOWED}
|
|
552
|
+
|
|
553
|
+
self.canvas.create_text(
|
|
554
|
+
pos[0], pos[1],
|
|
555
|
+
text=text,
|
|
556
|
+
fill=fill,
|
|
557
|
+
font=font_tuple,
|
|
558
|
+
anchor="nw",
|
|
559
|
+
**safe_kwargs
|
|
560
|
+
)
|
|
561
|
+
return
|
|
562
|
+
|
|
563
|
+
# ---------------- TEXTURED TEXT ----------------
|
|
564
|
+
from PIL import Image, ImageDraw, ImageFont, ImageTk
|
|
565
|
+
|
|
566
|
+
# try load font with style
|
|
567
|
+
try:
|
|
568
|
+
fnt = ImageFont.truetype(font + ".ttf", scaled_size)
|
|
569
|
+
except:
|
|
570
|
+
fnt = ImageFont.load_default()
|
|
571
|
+
|
|
572
|
+
# measure text
|
|
573
|
+
temp = Image.new("L", (1, 1), 0)
|
|
574
|
+
d = ImageDraw.Draw(temp)
|
|
575
|
+
|
|
576
|
+
try:
|
|
577
|
+
bbox = d.textbbox((0, 0), text, font=fnt)
|
|
578
|
+
tw = bbox[2] - bbox[0]
|
|
579
|
+
th = bbox[3] - bbox[1]
|
|
580
|
+
except:
|
|
581
|
+
tw, th = d.textsize(text, font=fnt)
|
|
582
|
+
|
|
583
|
+
pad = int(size * 0.25)
|
|
584
|
+
tw += pad
|
|
585
|
+
th += pad
|
|
586
|
+
|
|
587
|
+
# alpha mask for texture
|
|
588
|
+
mask = Image.new("L", (tw, th), 0)
|
|
589
|
+
m = ImageDraw.Draw(mask)
|
|
590
|
+
m.text((pad//2, pad//2), text, font=fnt, fill=255)
|
|
591
|
+
|
|
592
|
+
# texture
|
|
593
|
+
tex = ImageTk.getimage(value).resize((tw, th))
|
|
594
|
+
tex.putalpha(mask)
|
|
595
|
+
|
|
596
|
+
tk_img = ImageTk.PhotoImage(tex)
|
|
597
|
+
if not hasattr(self, "_text_tex"):
|
|
598
|
+
self._text_tex = []
|
|
599
|
+
self._text_tex.append(tk_img)
|
|
600
|
+
|
|
601
|
+
self.canvas.create_image(
|
|
602
|
+
pos[0], pos[1],
|
|
603
|
+
image=tk_img,
|
|
604
|
+
anchor="nw"
|
|
605
|
+
)
|
|
606
|
+
|
|
607
|
+
# ------------------------------------------------------
|
|
608
|
+
# DRAW TEXT LATER (Queued) — CLEAN KWARGS BEFORE SAVE
|
|
609
|
+
# ------------------------------------------------------
|
|
201
610
|
def draw_text_later(self, *args, **kwargs):
|
|
202
|
-
|
|
611
|
+
# remove style keys NOW so they never come back
|
|
612
|
+
kwargs.pop("bold", None)
|
|
613
|
+
kwargs.pop("italic", None)
|
|
614
|
+
|
|
615
|
+
# allowed kwargs ONLY (same list as draw_text)
|
|
616
|
+
ALLOWED = {"anchor", "justify", "tags"}
|
|
617
|
+
clean_kwargs = {k: v for k, v in kwargs.items() if k in ALLOWED}
|
|
618
|
+
|
|
619
|
+
self.draw_calls.append(("text", args, clean_kwargs))
|
|
620
|
+
|
|
203
621
|
|
|
204
622
|
# === Update + Loop ===
|
|
205
623
|
def run(self, update_func=None, bg=(20, 20, 50)):
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: crystalwindow
|
|
3
|
-
Version: 4.
|
|
4
|
-
Summary: A Tkinter powered window + GUI toolkit made by Crystal (
|
|
3
|
+
Version: 4.7
|
|
4
|
+
Summary: A Tkinter powered window + GUI toolkit made by Crystal (ME)! Easier apps, smoother UI and all-in-one helpers!, Gui, Buttons, FileHelper, Sprites, Animations, Colors, Math, Gravity, Camera, 3D and more!
|
|
5
5
|
Home-page: https://pypi.org/project/crystalwindow/
|
|
6
6
|
Author: CrystalBallyHereXD
|
|
7
7
|
Author-email: mavilla.519@gmail.com
|
|
@@ -16,6 +16,9 @@ Classifier: License :: OSI Approved :: MIT License
|
|
|
16
16
|
Requires-Python: >=3.6
|
|
17
17
|
Description-Content-Type: text/markdown
|
|
18
18
|
License-File: LICENSE
|
|
19
|
+
Requires-Dist: requests
|
|
20
|
+
Requires-Dist: packaging
|
|
21
|
+
Requires-Dist: pillow
|
|
19
22
|
Dynamic: author
|
|
20
23
|
Dynamic: author-email
|
|
21
24
|
Dynamic: classifier
|
|
@@ -25,6 +28,7 @@ Dynamic: home-page
|
|
|
25
28
|
Dynamic: keywords
|
|
26
29
|
Dynamic: license-file
|
|
27
30
|
Dynamic: project-url
|
|
31
|
+
Dynamic: requires-dist
|
|
28
32
|
Dynamic: requires-python
|
|
29
33
|
Dynamic: summary
|
|
30
34
|
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
crystalwindow/FileHelper.py,sha256=U20iwND4jX1y91TOK46e8MPH8xyw7GOrZ697nPEnOPk,10706
|
|
2
|
+
crystalwindow/__init__.py,sha256=GQxj4yLpnp53i3rc7X_DY_KbYTQFr1Op8qX4-_R_RrA,2510
|
|
3
|
+
crystalwindow/ai.py,sha256=YIt6dNe1QljSAlNECCVa3DMUSIqQEJRIAAbQpYqFNNo,11525
|
|
4
|
+
crystalwindow/animation.py,sha256=zHjrdBXQeyNaLAuaGPldJueX05OZ5j31YR8NizmR0uQ,427
|
|
5
|
+
crystalwindow/assets.py,sha256=8MxPSk7W5YcBnKDh6KcLdg34uxmACf_m6feSYFCWHqA,11405
|
|
6
|
+
crystalwindow/camera.py,sha256=tbn4X-jxMIszAUg3Iu-89gJN5nij0mjPMEzGotcLbJI,712
|
|
7
|
+
crystalwindow/clock.py,sha256=JjpLOWaZlVtLd1KfvaMn5Gx5iOqyuy6oLnHaM22axKY,4827
|
|
8
|
+
crystalwindow/collision.py,sha256=hpkHTp_KparghVK-itp_rxuYdd2GbQMxICHlUBv0rSw,472
|
|
9
|
+
crystalwindow/color_handler.py,sha256=K3LipCPOjYt1NemeT-hNWf886LL59QgRN8ifHOi7-0M,3255
|
|
10
|
+
crystalwindow/crystal3d.py,sha256=8n42S71NZWgBgYwP8ZWfobXFeWvNrhJY80YGK-PzI8s,5375
|
|
11
|
+
crystalwindow/draw_helpers.py,sha256=HqjI5fTbdnA55g4LKYEuMUdIjrWaBm2U8RmeUXjcQGw,821
|
|
12
|
+
crystalwindow/draw_rects.py,sha256=o7siET3y35N2LPeNBGe8QhsQbOH8J-xF6fOUz07rymU,1484
|
|
13
|
+
crystalwindow/draw_text_helper.py,sha256=qv5fFCuTCKWeGDk9Z_ZpOzrTFP8YYwlgQrrorwrq9Hg,1298
|
|
14
|
+
crystalwindow/draw_tool.py,sha256=1YYEqjmgt4HpV3S15sTjCSmcqgHup4fD8gskTkxU0t4,1638
|
|
15
|
+
crystalwindow/fun_helpers.py,sha256=x-OF5dlMlHgvXjtT7Om8RvzKWgizSwnzeaM0_W3-SKQ,870
|
|
16
|
+
crystalwindow/gravity.py,sha256=pZJykZzO-mKVBPsZRP55nnlvUoYQfR29wPgJcI4IbBs,2904
|
|
17
|
+
crystalwindow/gui.py,sha256=Ipu3BKA6euwQJ75oq7L9V7jbbZHGawwTQjv-KEH_owE,5286
|
|
18
|
+
crystalwindow/gui_ext.py,sha256=JctpNPr7gtGdro3csyUo9ft3FFArj3JhkVlgKF0lyME,3407
|
|
19
|
+
crystalwindow/math.py,sha256=slOY3KQZ99VDMUMxsSJabMyRtJcwVzEyxR2RoXspHho,963
|
|
20
|
+
crystalwindow/objects.py,sha256=VCEvRrzPv-bWCiVw63rMF67KYluP6FKySgDOvOiN8QU,5413
|
|
21
|
+
crystalwindow/sprites.py,sha256=IADCQetFDQoat3qGpKkH93TdtqqgldfHl4N0HKX1Ajc,7480
|
|
22
|
+
crystalwindow/tilemap.py,sha256=J6u3K3n-tEFyAqVc6eBGxnG0qVeGQeE0aMc-WLapydQ,328
|
|
23
|
+
crystalwindow/ver_warner.py,sha256=qEN3ulc1NixBy15FFx2R3Zu0DhyJTVJwiESGAPwpynM,3373
|
|
24
|
+
crystalwindow/websearch.py,sha256=ZaG4rO2TRxJVv1QRYv8vcSKx1fCf5OqqhEGNns99-KU,5159
|
|
25
|
+
crystalwindow/window.py,sha256=qfz0e8dhLH0ZfoqjswFu20NJqh3Ln6rhaZ3v626Uewk,31849
|
|
26
|
+
crystalwindow/Icons/default_icon.png,sha256=Loq27Pxb8Wb3Sz-XwtNF1RmlLNxR4TcfOWfK-1lWcII,7724
|
|
27
|
+
crystalwindow/Icons/file_icons.png,sha256=kqjvz3gMaIbepW4XGrLZOjDYu-yhFbVxjvylS-0RO4U,5659
|
|
28
|
+
crystalwindow/docs/getting_started.md,sha256=e_XEhJk8eatS22MX0nRX7hQNkYkwN9both1ObabZSTw,5759
|
|
29
|
+
crystalwindow/docs/index.md,sha256=bd14uLWtRSeRBm28zngGyfGDI1J6bJRvHkQLDpYwgOE,747
|
|
30
|
+
crystalwindow/gametests/3dsquare.py,sha256=MM_RRx1mz_NeBhTAoNomzn_IAbbN4ZnQZWA9VAFL8mY,821
|
|
31
|
+
crystalwindow/gametests/__init__.py,sha256=TRwhDl8aO3Wdqu_DXi9qT35CqF86AIyLqOSAkjNMJFY,240
|
|
32
|
+
crystalwindow/gametests/__main__.py,sha256=93R7DXHRK7GPsMrZB526pkWCrKJ728E1G4wCbHk3kP0,751
|
|
33
|
+
crystalwindow/gametests/gravitytest.py,sha256=OzhdaUtjSaVgWeSqYmtWLCP_9RIrkkMg_oHfrWTEUzs,3393
|
|
34
|
+
crystalwindow/gametests/guitesting.py,sha256=7MAlFkH9cLsGeS3B5oaYWOwB1k1ek9aosqSL_UhYZgY,1919
|
|
35
|
+
crystalwindow/gametests/sandbox.py,sha256=Oo2tU2N0y3BPVa6T5vs_h9N6islhQrjSrr_78XLut5I,1007
|
|
36
|
+
crystalwindow/gametests/squaremove.py,sha256=ei6DMnvcgpOhmxbGv-Yqmx5EqiZjKbVlZhI7YbT2hY8,643
|
|
37
|
+
crystalwindow/gametests/testtttagain.py,sha256=oIhK9MGgMVly_W2lRwD9Hn9WyPdd8JnX2HGrLTGZdxY,373
|
|
38
|
+
crystalwindow/gametests/windowtesting.py,sha256=_9X6wnV1-_X_PtNS-0zu-k209NtFIwAc4vpxLPp7V2o,97
|
|
39
|
+
crystalwindow-4.7.dist-info/licenses/LICENSE,sha256=Gt5cJRchdNt0guxyQMHKsATN5PM5mjuDhdO6Gzs9qQc,1096
|
|
40
|
+
crystalwindow-4.7.dist-info/METADATA,sha256=T0kgPlByhRHl1pND0oNPapnesZKSvu9B4Urvb4tkEvo,7523
|
|
41
|
+
crystalwindow-4.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
42
|
+
crystalwindow-4.7.dist-info/top_level.txt,sha256=PeQSld4b19XWT-zvbYkvE2Xg8sakIMbDzSzSdOSRN8o,14
|
|
43
|
+
crystalwindow-4.7.dist-info/RECORD,,
|
crystalwindow/cw_client.py
DELETED
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
# cw_client.py
|
|
2
|
-
# pip install websockets
|
|
3
|
-
import asyncio
|
|
4
|
-
import websockets
|
|
5
|
-
import json
|
|
6
|
-
import threading
|
|
7
|
-
import time
|
|
8
|
-
|
|
9
|
-
class CWClient:
|
|
10
|
-
"""
|
|
11
|
-
Lightweight client wrapper. Usage:
|
|
12
|
-
c = CWClient("MyName", "ws://host:8765")
|
|
13
|
-
c.on_event = lambda ev: print("ev", ev)
|
|
14
|
-
c.start()
|
|
15
|
-
c.move(x,y)
|
|
16
|
-
c.chat("hi")
|
|
17
|
-
"""
|
|
18
|
-
def __init__(self, pid, uri="ws://127.0.0.1:8765"):
|
|
19
|
-
self.pid = pid
|
|
20
|
-
self.uri = uri
|
|
21
|
-
self._send_q = asyncio.Queue()
|
|
22
|
-
self.on_event = None # callback for incoming events: fn(dict)
|
|
23
|
-
self._running = False
|
|
24
|
-
self._loop_thread = None
|
|
25
|
-
|
|
26
|
-
async def _run(self):
|
|
27
|
-
try:
|
|
28
|
-
async with websockets.connect(self.uri) as ws:
|
|
29
|
-
# send join
|
|
30
|
-
await ws.send(json.dumps({"type":"join","id":self.pid}))
|
|
31
|
-
self._running = True
|
|
32
|
-
sender_task = asyncio.create_task(self._sender(ws))
|
|
33
|
-
try:
|
|
34
|
-
async for raw in ws:
|
|
35
|
-
try:
|
|
36
|
-
data = json.loads(raw)
|
|
37
|
-
except:
|
|
38
|
-
continue
|
|
39
|
-
if self.on_event:
|
|
40
|
-
# run callback in same thread (safe)
|
|
41
|
-
try:
|
|
42
|
-
self.on_event(data)
|
|
43
|
-
except Exception as cb_e:
|
|
44
|
-
# swallow callback errors
|
|
45
|
-
print("on_event callback error:", cb_e)
|
|
46
|
-
finally:
|
|
47
|
-
sender_task.cancel()
|
|
48
|
-
except Exception as e:
|
|
49
|
-
# notify user on connection lost
|
|
50
|
-
if self.on_event:
|
|
51
|
-
try:
|
|
52
|
-
self.on_event({"type":"sys","msg":f"connection_lost:{e}"})
|
|
53
|
-
except:
|
|
54
|
-
pass
|
|
55
|
-
finally:
|
|
56
|
-
self._running = False
|
|
57
|
-
|
|
58
|
-
async def _sender(self, ws):
|
|
59
|
-
while True:
|
|
60
|
-
try:
|
|
61
|
-
obj = await self._send_q.get()
|
|
62
|
-
await ws.send(json.dumps(obj))
|
|
63
|
-
except Exception:
|
|
64
|
-
break
|
|
65
|
-
|
|
66
|
-
def start(self):
|
|
67
|
-
if self._loop_thread and self._loop_thread.is_alive():
|
|
68
|
-
return
|
|
69
|
-
def target():
|
|
70
|
-
asyncio.run(self._run())
|
|
71
|
-
self._loop_thread = threading.Thread(target=target, daemon=True)
|
|
72
|
-
self._loop_thread.start()
|
|
73
|
-
# give a bit of time to connect
|
|
74
|
-
time.sleep(0.05)
|
|
75
|
-
|
|
76
|
-
def send(self, obj):
|
|
77
|
-
# enqueue safely from any thread
|
|
78
|
-
async def push():
|
|
79
|
-
await self._send_q.put(obj)
|
|
80
|
-
try:
|
|
81
|
-
loop = asyncio.get_running_loop()
|
|
82
|
-
# if main thread has a loop, schedule
|
|
83
|
-
asyncio.run_coroutine_threadsafe(push(), loop)
|
|
84
|
-
except RuntimeError:
|
|
85
|
-
# no running loop in this thread: create a tiny loop to push
|
|
86
|
-
def run_push():
|
|
87
|
-
asyncio.run(push())
|
|
88
|
-
threading.Thread(target=run_push, daemon=True).start()
|
|
89
|
-
|
|
90
|
-
# convenience APIs
|
|
91
|
-
def move(self, x, y):
|
|
92
|
-
self.send({"type":"move","id":self.pid,"x":int(x),"y":int(y)})
|
|
93
|
-
|
|
94
|
-
def chat(self, msg):
|
|
95
|
-
self.send({"type":"chat","id":self.pid,"msg":str(msg)})
|