crystalwindow 4.6__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/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
- def draw_circle(self, color, pos, radius):
194
- x, y = pos
195
- self.draw_calls.append(("circle", color, x, y, radius))
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
- def draw_text(self, text, font="Arial", size=16, color=(255, 255, 255), pos=(0, 0), **kwargs):
198
- fill = f"#{color[0]:02x}{color[1]:02x}{color[2]:02x}"
199
- self.canvas.create_text(pos[0], pos[1], text=text, fill=fill, font=(font, size), anchor="nw", **kwargs)
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
- self.draw_calls.append(("text", args, kwargs))
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.6
4
- Summary: A Tkinter powered window + GUI toolkit made by Crystal (MEEEEEE)! Easier apps, smoother UI and all-in-one helpers!
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,,
@@ -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)})