crystalwindow 5.8__py3-none-any.whl → 6.0__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/__init__.py CHANGED
@@ -22,6 +22,7 @@ from .assets import (
22
22
  flip_vertical,
23
23
  LoopAnim,
24
24
  loop_images,
25
+ loop_imgs,
25
26
  load_file,
26
27
  Sound,
27
28
  )
@@ -85,6 +86,7 @@ __all__ = [
85
86
  "Animation",
86
87
  "LoopAnim",
87
88
  "loop_images",
89
+ "loop_imgs",
88
90
  "load_file",
89
91
  "Sound",
90
92
 
crystalwindow/assets.py CHANGED
@@ -1,6 +1,7 @@
1
1
  import os
2
2
  import random, json, pickle
3
3
  from tkinter import PhotoImage
4
+ import time
4
5
 
5
6
  try:
6
7
  from PIL import Image, ImageTk
@@ -159,34 +160,86 @@ def flip_vertical(img):
159
160
  # LOOPING ANIMATIONS / IMAGES
160
161
  # --------------------------------------------------------
161
162
  class LoopAnim:
162
- def __init__(self, frames, speed=0.2):
163
- self.frames = frames
164
- self.i = 0
165
- self.speed = speed
163
+ """
164
+ Flexible looping animation.
165
+
166
+ Usage patterns:
167
+ - time-based using FPS:
168
+ anim = LoopAnim(frames, fps=12)
169
+ frame = anim.next() # auto uses time delta internally
170
+ - manual dt-based (e.g., game loop provides dt):
171
+ anim = LoopAnim(frames, fps=24)
172
+ frame = anim.next(dt) # dt in seconds
173
+ - legacy speed-based (per-call increment):
174
+ anim = LoopAnim(frames, speed=0.2)
175
+ frame = anim.next() # increments by 'speed' each call
176
+
177
+ Notes:
178
+ - `frames` may be any iterable (list/tuple/set); it will be converted to a list.
179
+ - If frames is empty, returns None from `next()`.
180
+ """
181
+
182
+ def __init__(self, frames, fps=None, speed=None):
183
+ self.frames = [f for f in frames if f is not None]
184
+ self.length = len(self.frames)
185
+ self._index = 0
186
+
187
+ # time-based mode
188
+ self.fps = float(fps) if fps else None
189
+ self.frame_duration = (1.0 / self.fps) if self.fps and self.fps > 0 else None
190
+ self._acc = 0.0
191
+ self._last_time = time.time()
192
+
193
+ # legacy speed (per-call fractional index)
194
+ self.speed = float(speed) if speed else None
195
+ self._float_index = 0.0
166
196
 
167
- def next(self):
168
- """Return next frame automatically looping."""
197
+ def next(self, dt=None):
198
+ """Return next frame. If dt (seconds) provided, uses it; otherwise uses internal clock."""
169
199
  if not self.frames:
170
200
  return None
171
201
 
172
- self.i += self.speed
173
- if self.i >= len(self.frames):
174
- self.i = 0
175
-
176
- return self.frames[int(self.i)]
202
+ # Time-based FPS mode (preferred)
203
+ if self.frame_duration:
204
+ if dt is None:
205
+ now = time.time()
206
+ dt = now - self._last_time
207
+ self._last_time = now
208
+ self._acc += dt
209
+ advance = int(self._acc / self.frame_duration)
210
+ if advance > 0:
211
+ self._acc -= advance * self.frame_duration
212
+ self._index = (self._index + advance) % self.length
213
+ return self.frames[self._index]
214
+
215
+ # Legacy speed-per-call mode
216
+ if self.speed:
217
+ self._float_index += self.speed
218
+ if self._float_index >= self.length:
219
+ self._float_index = self._float_index % self.length
220
+ return self.frames[int(self._float_index)]
221
+
222
+ # Default: simple step each call
223
+ self._index = (self._index + 1) % self.length
224
+ return self.frames[self._index]
177
225
 
178
226
 
179
227
  def loop_images(*imgs, speed=0.2):
180
228
  """
181
- Usage:
182
- anim = loop_image(img1, img2, img3)
183
- anim = loop_image(load_image("p1.png"), load_image("p2.png"))
229
+ Backwards-compatible wrapper.
230
+ Accepts varargs like loop_images(img1, img2, ...) or a single iterable:
231
+ loop_images([img1, img2], speed=0.2)
232
+
233
+ This returns a LoopAnim that uses legacy per-call `speed`.
184
234
  """
185
- frames = [x for x in imgs if x is not None]
235
+ frames = []
236
+ if len(imgs) == 1 and hasattr(imgs[0], "__iter__") and not isinstance(imgs[0], (str, bytes)):
237
+ frames = [x for x in imgs[0] if x is not None]
238
+ else:
239
+ frames = [x for x in imgs if x is not None]
186
240
 
187
- # If nothing was passed → fallback
188
241
  if not frames:
189
- print("⚠️ loop_image() got no frames.")
242
+ print("⚠️ loop_images() got no frames.")
190
243
  fb = load_image("fallback.png") if os.path.exists("fallback.png") else None
191
244
  if fb is None:
192
245
  return LoopAnim([None], speed=speed)
@@ -195,6 +248,31 @@ def loop_images(*imgs, speed=0.2):
195
248
  return LoopAnim(frames, speed=speed)
196
249
 
197
250
 
251
+ def loop_imgs(*imgs, framerate=None, speed=None):
252
+ """
253
+ New helper supporting a single iterable or varargs and an explicit framerate (FPS).
254
+ Usage:
255
+ idle = [idle1, idle2, idle3]
256
+ idles = loop_imgs(idle, framerate=24) # time-based, 24 FPS
257
+ idles = loop_imgs(idle1, idle2, framerate=12)
258
+ idles = loop_imgs(idle, speed=0.2) # fallback to legacy speed-per-call
259
+ """
260
+ frames = []
261
+ if len(imgs) == 1 and hasattr(imgs[0], "__iter__") and not isinstance(imgs[0], (str, bytes)):
262
+ frames = [x for x in imgs[0] if x is not None]
263
+ else:
264
+ frames = [x for x in imgs if x is not None]
265
+
266
+ if not frames:
267
+ print("⚠️ loop_imgs() got no frames.")
268
+ fb = load_image("fallback.png") if os.path.exists("fallback.png") else None
269
+ if fb is None:
270
+ return LoopAnim([None], fps=framerate, speed=speed)
271
+ return LoopAnim([fb], fps=framerate, speed=speed)
272
+
273
+ return LoopAnim(frames, fps=framerate, speed=speed)
274
+
275
+
198
276
  # --------------------------------------------------------
199
277
  # FOLDER LOADING
200
278
  # --------------------------------------------------------
crystalwindow/sprites.py CHANGED
@@ -72,6 +72,10 @@ class Sprite:
72
72
  """Create sprite using a simple rectangle"""
73
73
  return cls(pos, size=(w, h), color=color)
74
74
 
75
+ @classmethod
76
+ def group(cls, *sprites):
77
+ """Create a list of sprites"""
78
+ return list(sprites)
75
79
 
76
80
  # === MOVE / DRAW ===
77
81
  def draw(self, win, cam=None):
@@ -0,0 +1,26 @@
1
+ from PIL import Image, ImageTk
2
+ import xml.etree.ElementTree as ET
3
+
4
+ class SpriteSheet:
5
+ def __init__(self, image_path, xml_path):
6
+ self.pil_image = Image.open(image_path).convert("RGBA")
7
+ self.frames = {}
8
+
9
+ tree = ET.parse(xml_path)
10
+ root = tree.getroot()
11
+
12
+ for sub in root.iter("SubTexture"):
13
+ name = sub.attrib["name"]
14
+ x = int(sub.attrib["x"])
15
+ y = int(sub.attrib["y"])
16
+ w = int(sub.attrib["width"])
17
+ h = int(sub.attrib["height"])
18
+
19
+ self.frames[name] = (x, y, w, h)
20
+
21
+ def get_pil(self, name):
22
+ x, y, w, h = self.frames[name]
23
+ return self.pil_image.crop((x, y, x + w, y + h))
24
+
25
+ def get_tk(self, name):
26
+ return ImageTk.PhotoImage(self.get_pil(name))
crystalwindow/window.py CHANGED
@@ -218,6 +218,14 @@ class Window:
218
218
 
219
219
  return False
220
220
 
221
+ def keys_combined(self, *keys):
222
+ for key in keys:
223
+ if isinstance(key, str):
224
+ key = self.KEY_MAP.get(key, key)
225
+ if not self.keys.get(key, False):
226
+ return False
227
+ return True
228
+
221
229
  def key_held(self, key):
222
230
  if isinstance(key, str):
223
231
  key = self.KEY_MAP.get(key, key)
@@ -533,6 +541,14 @@ class Window:
533
541
  bold = kwargs.pop("bold", False)
534
542
  italic = kwargs.pop("italic", False)
535
543
 
544
+ pil_font = None
545
+
546
+ if hasattr(font, "font"):
547
+ pil_font = font.font
548
+ font_name = font.font_name
549
+ else:
550
+ font_name = font
551
+
536
552
  # scale
537
553
  scaled_size = size
538
554
 
@@ -549,9 +565,9 @@ class Window:
549
565
 
550
566
  # tkinter font tuple
551
567
  if style:
552
- font_tuple = (font, scaled_size, style)
568
+ font_tuple = (font_name, scaled_size, style)
553
569
  else:
554
- font_tuple = (font, scaled_size)
570
+ font_tuple = (font_name, scaled_size)
555
571
 
556
572
  # ---------------- NORMAL COLOR TEXT ----------------
557
573
  if mode != "texture":
@@ -579,10 +595,19 @@ class Window:
579
595
  from PIL import Image, ImageDraw, ImageFont, ImageTk
580
596
 
581
597
  # try load font with style
582
- try:
583
- fnt = ImageFont.truetype(font + ".ttf", scaled_size)
584
- except:
585
- fnt = ImageFont.load_default()
598
+ # choose correct PIL font
599
+ if pil_font and hasattr(font, "font_path") and font.font_path:
600
+ try:
601
+ fnt = ImageFont.truetype(font.font_path, scaled_size)
602
+ except:
603
+ fnt = ImageFont.load_default()
604
+ elif pil_font:
605
+ fnt = pil_font
606
+ else:
607
+ try:
608
+ fnt = ImageFont.truetype(font_name + ".ttf", scaled_size)
609
+ except:
610
+ fnt = ImageFont.load_default()
586
611
 
587
612
  # measure text
588
613
  temp = Image.new("L", (1, 1), 0)
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: crystalwindow
3
- Version: 5.8
3
+ Version: 6.0
4
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
8
- Project-URL: Homepage, https://github.com/yourusername/crystalwindow
8
+ Project-URL: Homepage, https://github.com/CrystalBallyHereXD/crystalwindow
9
9
  Project-URL: YouTube, https://www.Youtube.com/@CrystalBallyHereXD
10
10
  Project-URL: PiWheels, https://www.piwheels.org/project/crystalwindow/
11
11
  Keywords: tkinter gui window toolkit easy crystalwindow crystal cw player moveable easygui python py file math gravity hex color
@@ -1,11 +1,11 @@
1
1
  crystalwindow/FileHelper.py,sha256=U20iwND4jX1y91TOK46e8MPH8xyw7GOrZ697nPEnOPk,10706
2
2
  crystalwindow/Fonts.py,sha256=XGqmM3GaauM5a74BtqhKk9zzKgQPcHyN3vcOP1TeoOs,954
3
3
  crystalwindow/System.py,sha256=-ecjzVlTH4ncNnZ_SDNfvqoQLeh8Zefy9laojpQKUUk,4045
4
- crystalwindow/__init__.py,sha256=whd-PzhvfO-6ALR7OpaomH0PDhfVjTP90I35DmoeYTk,3353
4
+ crystalwindow/__init__.py,sha256=C2euU6HIqgx9a6tH0FP7Rt5Uj2Je99dxYP1P5jAtkXI,3387
5
5
  crystalwindow/ai.py,sha256=YIt6dNe1QljSAlNECCVa3DMUSIqQEJRIAAbQpYqFNNo,11525
6
6
  crystalwindow/animation.py,sha256=zHjrdBXQeyNaLAuaGPldJueX05OZ5j31YR8NizmR0uQ,427
7
7
  crystalwindow/apphelper.py,sha256=CAcX5n0Jq7zdBMFZkwbMt3_M0mBVVZRoXldOcXsZan4,2924
8
- crystalwindow/assets.py,sha256=qboSNaWY3fWfnQWRZCkVzpAn1aZYmSgQWVxaE2CNXGY,11406
8
+ crystalwindow/assets.py,sha256=DkTPlywZFTgjWzxsy_byt28KYU4DYfdU_FS83akh3es,14673
9
9
  crystalwindow/camera.py,sha256=fe0DXIcZ-TSVPPHxRptFufadnkhysWRZDTJxjxf6SEo,1606
10
10
  crystalwindow/chatvpn.py,sha256=Ij3wRNrMbPINO-SX9vx8xlrKRLvG7zJgzAN2T0jzSz8,2477
11
11
  crystalwindow/clock.py,sha256=M7oMjnGNkFOcchqtQn_voWao4kFtxXpTJe2VUjqvGKQ,5041
@@ -24,11 +24,12 @@ crystalwindow/math.py,sha256=AGhLGkPWozNOMlSieF5u8jSDY0qakOPJIi5aWI9P91o,1598
24
24
  crystalwindow/messagebus.py,sha256=9K2P_TkdQ1rt-oouIkRg_XHwMTwykttBt-9gFYaItyI,696
25
25
  crystalwindow/objects.py,sha256=BWqjlxOXpaCGeaAocFL5L9Qh-a_MRjbN6Qb-Vd7R0K8,6262
26
26
  crystalwindow/scores.py,sha256=O5E-rceNEaRpsC3XZ2PpE5gsLvJk86f4ChA5UNdd4b0,1069
27
- crystalwindow/sprites.py,sha256=IADCQetFDQoat3qGpKkH93TdtqqgldfHl4N0HKX1Ajc,7480
27
+ crystalwindow/sprites.py,sha256=REkKkKi1oDQBjnjM7fX-49_zsJmTlwpDpoX8PlTohtQ,7599
28
+ crystalwindow/spritesheets.py,sha256=M_KTBAXPaM3liHf9-2rsQtXUB9GSQUkb07JS6S4-4iI,807
28
29
  crystalwindow/tilemap.py,sha256=endJ8KcbP9EjPvL9qWsOpV4jc_Re1yH080aUyDkwufA,3378
29
30
  crystalwindow/ver_warner.py,sha256=qEN3ulc1NixBy15FFx2R3Zu0DhyJTVJwiESGAPwpynM,3373
30
31
  crystalwindow/websearch.py,sha256=IgsoKt27yCBHeq8yFVfSq_8sEj5KP6mqn2yNRTsRw1A,5161
31
- crystalwindow/window.py,sha256=i9VRJyVVMQoLzaHbOuZHsdmtmwXRA9h-pL_Tc8Kb5XY,32327
32
+ crystalwindow/window.py,sha256=yo_Onk0RYEjUMhzKsNXJDBlYYFF9l9Nhbxiza3wf4UY,33128
32
33
  crystalwindow/Icons/default_icon.png,sha256=Loq27Pxb8Wb3Sz-XwtNF1RmlLNxR4TcfOWfK-1lWcII,7724
33
34
  crystalwindow/Icons/file_icons.png,sha256=kqjvz3gMaIbepW4XGrLZOjDYu-yhFbVxjvylS-0RO4U,5659
34
35
  crystalwindow/gametests/3dsquare.py,sha256=MM_RRx1mz_NeBhTAoNomzn_IAbbN4ZnQZWA9VAFL8mY,821
@@ -40,8 +41,8 @@ crystalwindow/gametests/sandbox.py,sha256=Oo2tU2N0y3BPVa6T5vs_h9N6islhQrjSrr_78X
40
41
  crystalwindow/gametests/squaremove.py,sha256=ei6DMnvcgpOhmxbGv-Yqmx5EqiZjKbVlZhI7YbT2hY8,643
41
42
  crystalwindow/gametests/testtttagain.py,sha256=oIhK9MGgMVly_W2lRwD9Hn9WyPdd8JnX2HGrLTGZdxY,373
42
43
  crystalwindow/gametests/windowtesting.py,sha256=_9X6wnV1-_X_PtNS-0zu-k209NtFIwAc4vpxLPp7V2o,97
43
- crystalwindow-5.8.dist-info/licenses/LICENSE,sha256=Gt5cJRchdNt0guxyQMHKsATN5PM5mjuDhdO6Gzs9qQc,1096
44
- crystalwindow-5.8.dist-info/METADATA,sha256=Dmffz8ly1Vcfaowu4Hj7IsXF4071v5H-tgxZKV7t-fQ,7523
45
- crystalwindow-5.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
46
- crystalwindow-5.8.dist-info/top_level.txt,sha256=PeQSld4b19XWT-zvbYkvE2Xg8sakIMbDzSzSdOSRN8o,14
47
- crystalwindow-5.8.dist-info/RECORD,,
44
+ crystalwindow-6.0.dist-info/licenses/LICENSE,sha256=Gt5cJRchdNt0guxyQMHKsATN5PM5mjuDhdO6Gzs9qQc,1096
45
+ crystalwindow-6.0.dist-info/METADATA,sha256=0LMK1XhmvQ2yKbO-ouU5m43xp-MFo2SsEM7VBmG_IfA,7529
46
+ crystalwindow-6.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
47
+ crystalwindow-6.0.dist-info/top_level.txt,sha256=PeQSld4b19XWT-zvbYkvE2Xg8sakIMbDzSzSdOSRN8o,14
48
+ crystalwindow-6.0.dist-info/RECORD,,