crystalwindow 2.5.7__py3-none-any.whl → 2.9.8.1__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.

Potentially problematic release.


This version of crystalwindow might be problematic. Click here for more details.

@@ -0,0 +1,75 @@
1
+ class Gravity:
2
+ def __init__(self, obj, force=1, terminal_velocity=15, bouncy=False, bounce_strength=0.7):
3
+ self.obj = obj
4
+ if not hasattr(self.obj, "vel_y"):
5
+ self.obj.vel_y = 0
6
+ self.force = force
7
+ self.terminal_velocity = terminal_velocity
8
+ self.bouncy = bouncy
9
+ self.bounce_strength = bounce_strength
10
+ self.on_ground = False
11
+ self.stretch_factor = 0 # for squishy effect
12
+
13
+ # --- NEW: choose mode ---
14
+ # If object has .sprite use that, else fallback to rect mode
15
+ if hasattr(obj, "sprite"):
16
+ self.mode = "sprite"
17
+ else:
18
+ self.mode = "rect"
19
+
20
+ def get_obj_rect(self):
21
+ # helper to get x,y,w,h depending on mode
22
+ if self.mode == "sprite":
23
+ s = self.obj.sprite
24
+ x = getattr(s, "x", 0)
25
+ y = getattr(s, "y", 0)
26
+ w = getattr(s, "width", getattr(s, "w", 32))
27
+ h = getattr(s, "height", getattr(s, "h", 32))
28
+ else:
29
+ x = getattr(self.obj, "x", 0)
30
+ y = getattr(self.obj, "y", 0)
31
+ w = getattr(self.obj, "width", getattr(self.obj, "w", 32))
32
+ h = getattr(self.obj, "height", getattr(self.obj, "h", 32))
33
+ return x, y, w, h
34
+
35
+ def update(self, dt, platforms=[]):
36
+ # apply gravity
37
+ self.obj.vel_y += self.force * dt * 60
38
+ if self.obj.vel_y > self.terminal_velocity:
39
+ self.obj.vel_y = self.terminal_velocity
40
+
41
+ # move object
42
+ x, y, w, h = self.get_obj_rect()
43
+ y += self.obj.vel_y
44
+ self.on_ground = False
45
+
46
+ # check collisions
47
+ for plat in platforms:
48
+ plat_w = getattr(plat, "width", getattr(plat, "w", 0))
49
+ plat_h = getattr(plat, "height", getattr(plat, "h", 0))
50
+ if (x + w > plat.x and x < plat.x + plat_w and
51
+ y + h > plat.y and y < plat.y + plat_h):
52
+
53
+ y = plat.y - h
54
+ self.on_ground = True
55
+
56
+ if self.bouncy:
57
+ self.obj.vel_y = -self.obj.vel_y * self.bounce_strength
58
+ self.stretch_factor = min(0.5, self.stretch_factor + 0.2)
59
+ else:
60
+ self.obj.vel_y = 0
61
+ self.stretch_factor = 0
62
+
63
+ # slowly reset stretch
64
+ if self.stretch_factor > 0:
65
+ self.stretch_factor -= 0.05
66
+ if self.stretch_factor < 0:
67
+ self.stretch_factor = 0
68
+
69
+ # write back position
70
+ if self.mode == "sprite":
71
+ self.obj.sprite.x = x
72
+ self.obj.sprite.y = y
73
+ else:
74
+ self.obj.x = x
75
+ self.obj.y = y
crystalwindow/gui.py ADDED
@@ -0,0 +1,69 @@
1
+ #gui.py
2
+ import random
3
+
4
+ # ----------------- Color Helpers -----------------
5
+ def hex_to_rgb(hex_str):
6
+ """Convert hex color string to RGB tuple"""
7
+ hex_str = hex_str.lstrip("#")
8
+ return tuple(int(hex_str[i:i+2], 16) for i in (0, 2, 4))
9
+
10
+ def random_color():
11
+ """Return a random RGB color"""
12
+ return (random.randint(0,255), random.randint(0,255), random.randint(0,255))
13
+
14
+ def lerp_color(c1, c2, t):
15
+ """Linearly interpolate between two colors"""
16
+ return tuple(int(a + (b-a)*t) for a,b in zip(c1,c2))
17
+
18
+ # ----------------- GUI Elements -----------------
19
+ class Button:
20
+ def __init__(self, rect, text, color=(200,200,200), hover_color=(255,255,255), callback=None):
21
+ self.rect = rect # (x, y, w, h)
22
+ self.text = text
23
+ self.color = color
24
+ self.hover_color = hover_color
25
+ self.callback = callback
26
+ self.hovered = False
27
+
28
+ def draw(self, win):
29
+ x, y, w, h = self.rect
30
+ mx, my = win.mouse_pos
31
+ self.hovered = x <= mx <= x+w and y <= my <= y+h
32
+ cur_color = self.hover_color if self.hovered else self.color
33
+ win.draw_rect(cur_color, self.rect)
34
+ win.draw_text(self.text, pos=(x+5, y+5))
35
+
36
+ def check_click(self, win):
37
+ mx, my = win.mouse_pos
38
+ x, y, w, h = self.rect
39
+ if x <= mx <= x+w and y <= my <= y+h and win.mouse_pressed(1):
40
+ if self.callback:
41
+ self.callback()
42
+
43
+ class Label:
44
+ def __init__(self, pos, text, color=(255,255,255), font="Arial", size=16):
45
+ self.pos = pos
46
+ self.text = text
47
+ self.color = color
48
+ self.font = font
49
+ self.size = size
50
+
51
+ def draw(self, win):
52
+ win.draw_text(self.text, font=self.font, size=self.size, color=self.color, pos=self.pos)
53
+
54
+ # ----------------- Optional GUI Manager -----------------
55
+ class GUIManager:
56
+ def __init__(self):
57
+ self.elements = []
58
+
59
+ def add(self, element):
60
+ self.elements.append(element)
61
+
62
+ def draw(self, win):
63
+ for e in self.elements:
64
+ e.draw(win)
65
+
66
+ def update(self, win):
67
+ for e in self.elements:
68
+ if isinstance(e, Button):
69
+ e.check_click(win)
@@ -0,0 +1,66 @@
1
+ from .window import Window
2
+ from .assets import load_image
3
+ from .sprites import Sprite
4
+ from .gui import Button
5
+
6
+ class Toggle:
7
+ def __init__(self, rect, value=False, color=(200,200,200), hover_color=(255,255,255)):
8
+ # rect = (x, y, w, h)
9
+ self.rect = rect
10
+ self.value = value
11
+ self.color = color
12
+ self.hover_color = hover_color
13
+ self.hovered = False
14
+
15
+ def update(self, win: Window):
16
+ mx, my = win.mouse_pos
17
+ x, y, w, h = self.rect
18
+ self.hovered = x <= mx <= x+w and y <= my <= y+h
19
+ if self.hovered and win.mouse_pressed(1):
20
+ self.value = not self.value
21
+
22
+ def draw(self, win: Window):
23
+ draw_color = self.hover_color if self.hovered else self.color
24
+ win.draw_rect(draw_color, self.rect)
25
+ # optional: small “on” indicator
26
+ if self.value:
27
+ inner = (self.rect[0]+4, self.rect[1]+4, self.rect[2]-8, self.rect[3]-8)
28
+ win.draw_rect((0,255,0), inner)
29
+
30
+
31
+ class Slider:
32
+ def __init__(self, rect, min_val=0, max_val=100, value=50, color=(150,150,150), handle_radius=10):
33
+ # rect = (x, y, w, h)
34
+ self.rect = rect
35
+ self.min_val = min_val
36
+ self.max_val = max_val
37
+ self.value = value
38
+ self.color = color
39
+ self.handle_radius = handle_radius
40
+ self.dragging = False
41
+
42
+ def update(self, win: Window):
43
+ mx, my = win.mouse_pos
44
+ x, y, w, h = self.rect
45
+
46
+ handle_x = x + ((self.value - self.min_val) / (self.max_val - self.min_val)) * w
47
+ handle_y = y + h // 2
48
+
49
+ # start drag if mouse clicks inside slider bar area
50
+ if win.mouse_pressed(1):
51
+ if not self.dragging and (x <= mx <= x+w and y <= my <= y+h):
52
+ self.dragging = True
53
+ else:
54
+ self.dragging = False # stop drag when released
55
+
56
+ # update value while dragging
57
+ if self.dragging:
58
+ rel_x = max(0, min(mx - x, w)) # clamp
59
+ self.value = self.min_val + (rel_x / w) * (self.max_val - self.min_val)
60
+
61
+ def draw(self, win: Window):
62
+ x, y, w, h = self.rect
63
+ win.draw_rect(self.color, (x, y + h//2 - 2, w, 4))
64
+ handle_x = x + ((self.value - self.min_val) / (self.max_val - self.min_val)) * w
65
+ handle_y = y + h // 2
66
+ win.draw_circle((255, 0, 0), (int(handle_x), int(handle_y)), self.handle_radius)
crystalwindow/math.py ADDED
@@ -0,0 +1,40 @@
1
+ class Mathematics:
2
+ """
3
+ A class used to perform basic mathematical operations.
4
+
5
+ Methods:
6
+ --------
7
+ add(num1, num2) : Adds two numbers.
8
+ subtract(num1, num2) : Subtracts two numbers.
9
+ multiply(num1, num2) : Multiplies two numbers.
10
+ divide(num1, num2) : Divides two numbers.
11
+ """
12
+
13
+ @staticmethod
14
+ def add(x, y):
15
+ """Adds two numbers together"""
16
+ return x + y
17
+
18
+ @staticmethod
19
+ def subtract(num1, num2):
20
+ """Subtracts two numbers."""
21
+ return num1 - num2
22
+
23
+ @staticmethod
24
+ def multiply(num1, num2):
25
+ """Multiplies two numbers."""
26
+ return num1 * num2
27
+
28
+ @staticmethod
29
+ def divide(num1, num2):
30
+ """
31
+ Divides two numbers.
32
+
33
+ Raises:
34
+ ------
35
+ ZeroDivisionError : If num2 is zero.
36
+ """
37
+ if num2 == 0:
38
+ raise ZeroDivisionError("Cannot divide by zero!")
39
+ return num1 / num2
40
+
@@ -0,0 +1,106 @@
1
+ import math, os, re
2
+ from .assets import load_folder_images
3
+ from .animation import Animation
4
+
5
+ class Player:
6
+ def __init__(self, pos=(0, 0), speed=5, size=(32, 32)):
7
+ self.x, self.y = pos
8
+ self.speed = speed
9
+ self.width, self.height = size
10
+ self.animations = {}
11
+ self.current_anim = None
12
+ self.image = None
13
+ self.rect = self._make_rect()
14
+
15
+ # make a lil rect obj for collisions
16
+ def _make_rect(self):
17
+ return type("Rect", (), {"x": self.x, "y": self.y, "w": self.width, "h": self.height,
18
+ "topleft": (self.x, self.y)})()
19
+
20
+ # === animation setup ===
21
+ def idle(self, folder):
22
+ imgs = self._load_sorted(folder)
23
+ anim = Animation(imgs)
24
+ self.animations["idle"] = anim
25
+ self.current_anim = anim
26
+ return anim
27
+
28
+ class walk:
29
+ @staticmethod
30
+ def cycle(folder):
31
+ imgs = Player._load_sorted_static(folder)
32
+ anim = Animation(imgs)
33
+ anim.loop = True
34
+ return anim
35
+
36
+ @staticmethod
37
+ def once(folder):
38
+ imgs = Player._load_sorted_static(folder)
39
+ anim = Animation(imgs)
40
+ anim.loop = False
41
+ return anim
42
+
43
+ class jump:
44
+ @staticmethod
45
+ def cycle(folder):
46
+ imgs = Player._load_sorted_static(folder)
47
+ anim = Animation(imgs)
48
+ anim.loop = True
49
+ return anim
50
+
51
+ @staticmethod
52
+ def once(folder):
53
+ imgs = Player._load_sorted_static(folder)
54
+ anim = Animation(imgs)
55
+ anim.loop = False
56
+ return anim
57
+
58
+ # === main update ===
59
+ def update(self, dt, win):
60
+ moving = False
61
+ if win.is_key_pressed("left"):
62
+ self.x -= self.speed * dt * 60
63
+ moving = True
64
+ if win.is_key_pressed("right"):
65
+ self.x += self.speed * dt * 60
66
+ moving = True
67
+ if win.is_key_pressed("up"):
68
+ self.y -= self.speed * dt * 60
69
+ moving = True
70
+ if win.is_key_pressed("down"):
71
+ self.y += self.speed * dt * 60
72
+ moving = True
73
+
74
+ # update animation
75
+ if moving and "run" in self.animations:
76
+ self.current_anim = self.animations["run"]
77
+ elif not moving and "idle" in self.animations:
78
+ self.current_anim = self.animations["idle"]
79
+
80
+ if self.current_anim:
81
+ self.current_anim.update(dt)
82
+ self.image = self.current_anim.get_frame()
83
+
84
+ self.rect.x, self.rect.y = self.x, self.y
85
+ self.rect.topleft = (self.x, self.y)
86
+
87
+ def draw(self, win):
88
+ if self.image:
89
+ win.screen.blit(self.image, (self.x, self.y))
90
+
91
+ # ---------- helpers ----------
92
+ def _load_sorted(self, folder):
93
+ imgs_dict = load_folder_images(folder)
94
+ return self._sort_images(imgs_dict)
95
+
96
+ @staticmethod
97
+ def _load_sorted_static(folder):
98
+ imgs_dict = load_folder_images(folder)
99
+ return Player._sort_images(imgs_dict)
100
+
101
+ @staticmethod
102
+ def _sort_images(imgs_dict):
103
+ def extract_num(filename):
104
+ match = re.search(r'(\d+)', filename)
105
+ return int(match.group(1)) if match else 0
106
+ return [img for name, img in sorted(imgs_dict.items(), key=lambda x: extract_num(x[0]))]
@@ -0,0 +1,37 @@
1
+ #Sprites.py
2
+ from tkinter import PhotoImage
3
+
4
+ class Sprite:
5
+ def __init__(self, pos, size=None, image=None, color=(255, 0, 0)):
6
+ """
7
+ pos: (x, y) top-left
8
+ size: (w, h) if you want a plain colored rect
9
+ image: PhotoImage (Tkinter)
10
+ color: fill color if size is used
11
+ """
12
+ self.pos = pos
13
+ self.x, self.y = pos
14
+
15
+ if image is not None:
16
+ self.image = image
17
+ self.width = image.width()
18
+ self.height = image.height()
19
+ elif size is not None:
20
+ self.width, self.height = size
21
+ self.image = None
22
+ self.color = color
23
+ else:
24
+ raise ValueError("Sprite needs either size or image")
25
+
26
+ def draw(self, win):
27
+ """Draw sprite on given Window"""
28
+ if self.image:
29
+ win.canvas.create_image(self.x, self.y, anchor="nw", image=self.image)
30
+ else:
31
+ win.draw_rect(self.color, (self.x, self.y, self.width, self.height))
32
+
33
+ def move(self, dx, dy):
34
+ """Move sprite by dx/dy"""
35
+ self.x += dx
36
+ self.y += dy
37
+ self.pos = (self.x, self.y)
@@ -0,0 +1,13 @@
1
+ from .sprites import Sprite
2
+
3
+ class TileMap:
4
+ def __init__(self, tile_size):
5
+ self.tile_size = tile_size
6
+ self.tiles = []
7
+
8
+ def add_tile(self, image, x, y):
9
+ self.tiles.append(Sprite(image, x, y))
10
+
11
+ def draw(self, win):
12
+ for t in self.tiles:
13
+ win.draw_sprite(t)
@@ -0,0 +1,40 @@
1
+ from importlib.metadata import version as get_version, PackageNotFoundError
2
+ from packaging import version as parse_version
3
+ import requests
4
+ import os
5
+ import json
6
+
7
+ def check_for_update(package_name="crystalwindow"):
8
+ """
9
+ Checks PyPI for updates.
10
+ Warns every run if outdated.
11
+ Skips warning if your version is newer than PyPI.
12
+ """
13
+ try:
14
+ try:
15
+ current_version = get_version(package_name)
16
+ except PackageNotFoundError:
17
+ print(f"(⚠️ Package '{package_name}' not found)")
18
+ return
19
+
20
+ url = f"https://pypi.org/pypi/{package_name}/json"
21
+ response = requests.get(url, timeout=3)
22
+ if response.status_code != 200:
23
+ print("(⚠️ PyPI request failed, skipping version check)")
24
+ return
25
+
26
+ latest_version = response.json()["info"]["version"]
27
+
28
+ # compare versions properly
29
+ if parse_version(current_version) < parse_version(latest_version):
30
+ print(f"\n⚠️ Yo dev! '{package_name}' is outdated ({current_version})")
31
+ print(f"👉 Newest is {latest_version}! Run:")
32
+ print(f" pip install --upgrade {package_name}")
33
+ print(f"Or peep: https://pypi.org/project/{package_name}/{latest_version}/\n")
34
+ elif parse_version(current_version) > parse_version(latest_version):
35
+ print(f"🚀 Local version ({current_version}) is newer than PyPI ({latest_version}) — flex on 'em 😎")
36
+ else:
37
+ print(f"✅ Up to date! ver = {current_version}.")
38
+
39
+ except Exception as e:
40
+ print(f"(⚠️ Version check failed: {e})")