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/FileHelper.py +159 -38
- crystalwindow/Icons/file_icons.png +0 -0
- crystalwindow/__init__.py +16 -27
- crystalwindow/assets.py +250 -59
- crystalwindow/clock.py +21 -63
- 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.6.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.6.dist-info/RECORD +0 -42
- {crystalwindow-4.6.dist-info → crystalwindow-4.7.dist-info}/WHEEL +0 -0
- {crystalwindow-4.6.dist-info → crystalwindow-4.7.dist-info}/licenses/LICENSE +0 -0
- {crystalwindow-4.6.dist-info → crystalwindow-4.7.dist-info}/top_level.txt +0 -0
crystalwindow/FileHelper.py
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"""
|
|
2
2
|
CrystalWindow FileHelper
|
|
3
3
|
------------------------
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
Handles saving/loading text, JSON, and pickle files.
|
|
5
|
+
Uses Tk file dialogs, auto-creates 'saves/' folder,
|
|
6
|
+
and supports image fallback + resizing.
|
|
6
7
|
"""
|
|
7
8
|
|
|
8
9
|
import os
|
|
@@ -10,30 +11,160 @@ import json
|
|
|
10
11
|
import pickle
|
|
11
12
|
import tkinter as tk
|
|
12
13
|
from tkinter import filedialog
|
|
14
|
+
from PIL import Image, ImageTk
|
|
15
|
+
from .assets import load_image
|
|
13
16
|
|
|
14
17
|
|
|
15
18
|
class FileHelper:
|
|
16
|
-
"""CrystalWindow integrated file helper
|
|
19
|
+
"""CrystalWindow integrated file helper."""
|
|
17
20
|
|
|
18
21
|
def __init__(self, default_save_folder="saves"):
|
|
22
|
+
self.default_save_folder = default_save_folder
|
|
23
|
+
self.last_dir = "."
|
|
24
|
+
os.makedirs(self.default_save_folder, exist_ok=True)
|
|
25
|
+
|
|
26
|
+
# ======================================================================
|
|
27
|
+
# INTERNAL TK ROOT
|
|
28
|
+
# ======================================================================
|
|
29
|
+
def _make_root(self):
|
|
30
|
+
"""Create hidden Tk root without any custom icon."""
|
|
31
|
+
root = tk.Tk()
|
|
32
|
+
root.withdraw()
|
|
33
|
+
return root
|
|
34
|
+
|
|
35
|
+
# ======================================================================
|
|
36
|
+
# SAFE RESIZE HANDLER
|
|
37
|
+
# ======================================================================
|
|
38
|
+
def _resize_any(self, img, size):
|
|
39
|
+
"""Resize PIL image safely and return Tk PhotoImage."""
|
|
40
|
+
if size is None:
|
|
41
|
+
# If already a PhotoImage, keep as is
|
|
42
|
+
if isinstance(img, Image.Image):
|
|
43
|
+
return ImageTk.PhotoImage(img)
|
|
44
|
+
return img
|
|
45
|
+
|
|
46
|
+
if isinstance(img, Image.Image):
|
|
47
|
+
pil = img.resize(size, Image.Resampling.LANCZOS)
|
|
48
|
+
return ImageTk.PhotoImage(pil)
|
|
49
|
+
|
|
50
|
+
print("[WARN] Unknown image type, cannot resize.")
|
|
51
|
+
return img
|
|
52
|
+
|
|
53
|
+
# ======================================================================
|
|
54
|
+
# IMAGE OPENING WITH RETRY + FALLBACK + RESIZE
|
|
55
|
+
# ======================================================================
|
|
56
|
+
def open_img(
|
|
57
|
+
self,
|
|
58
|
+
start_in=None,
|
|
59
|
+
retry=False,
|
|
60
|
+
fallback_size=(64, 64),
|
|
61
|
+
fallback_color=(255, 0, 255),
|
|
62
|
+
resize=None,
|
|
63
|
+
return_pil=False
|
|
64
|
+
):
|
|
19
65
|
"""
|
|
20
|
-
|
|
66
|
+
Open an image using a dialog.
|
|
21
67
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
68
|
+
Params:
|
|
69
|
+
retry=True → reopen dialog until image selected
|
|
70
|
+
fallback_size → fallback square size when cancelled
|
|
71
|
+
fallback_color → fallback color
|
|
72
|
+
resize=(w, h) → resize final image
|
|
73
|
+
|
|
74
|
+
Returns dict:
|
|
75
|
+
- Always returns `image` as a Tk-compatible image (ImageTk.PhotoImage or PhotoImage)
|
|
76
|
+
- If `return_pil=True` and Pillow is available, also returns `pil` with the PIL Image
|
|
77
|
+
{ "path": str|None, "image": PhotoImage, "pil": PIL.Image.Image|None, "cancelled": bool }
|
|
25
78
|
"""
|
|
26
|
-
self.default_save_folder = default_save_folder
|
|
27
|
-
os.makedirs(self.default_save_folder, exist_ok=True)
|
|
28
79
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
80
|
+
while True:
|
|
81
|
+
root = self._make_root()
|
|
82
|
+
folder = start_in if start_in else self.last_dir
|
|
83
|
+
|
|
84
|
+
path = filedialog.askopenfilename(
|
|
85
|
+
title="Select image",
|
|
86
|
+
initialdir=folder,
|
|
87
|
+
filetypes=[
|
|
88
|
+
("Image files", "*.png;*.jpg;*.jpeg;*.gif;*.bmp"),
|
|
89
|
+
("All files", "*.*")
|
|
90
|
+
]
|
|
91
|
+
)
|
|
92
|
+
root.destroy()
|
|
93
|
+
|
|
94
|
+
# ------------------------ CANCELLED ------------------------
|
|
95
|
+
if not path:
|
|
96
|
+
if retry:
|
|
97
|
+
print("⚠️ No image selected. Trying again...")
|
|
98
|
+
continue
|
|
99
|
+
|
|
100
|
+
print("⚠️ No image selected. Using fallback.")
|
|
101
|
+
# Create a PIL fallback if Pillow available, then make a Tk image
|
|
102
|
+
if Image is not None:
|
|
103
|
+
w, h = fallback_size if fallback_size else (64, 64)
|
|
104
|
+
pil_fb = Image.new("RGB", (w, h), fallback_color)
|
|
105
|
+
if resize:
|
|
106
|
+
pil_fb = pil_fb.resize(resize, Image.Resampling.LANCZOS)
|
|
107
|
+
tk_fb = ImageTk.PhotoImage(pil_fb)
|
|
108
|
+
if return_pil:
|
|
109
|
+
return {"path": None, "image": tk_fb, "pil": pil_fb, "cancelled": True}
|
|
110
|
+
return {"path": None, "image": tk_fb, "pil": None, "cancelled": True}
|
|
111
|
+
|
|
112
|
+
# Fallback to existing asset loader (returns PhotoImage)
|
|
113
|
+
fb = load_image(None, size=fallback_size, color=fallback_color)
|
|
114
|
+
if resize:
|
|
115
|
+
fb = self._resize_any(fb, resize)
|
|
116
|
+
return {"path": None, "image": fb, "pil": None, "cancelled": True}
|
|
117
|
+
|
|
118
|
+
# ------------------------ LOAD IMAGE -----------------------
|
|
119
|
+
self.last_dir = os.path.dirname(path)
|
|
120
|
+
|
|
121
|
+
try:
|
|
122
|
+
# If Pillow is available, open with PIL and create a Tk PhotoImage for compatibility
|
|
123
|
+
if Image is not None:
|
|
124
|
+
pil_img = Image.open(path).convert("RGBA")
|
|
125
|
+
if resize:
|
|
126
|
+
pil_img = pil_img.resize(resize, Image.Resampling.LANCZOS)
|
|
127
|
+
tk_img = ImageTk.PhotoImage(pil_img)
|
|
128
|
+
if return_pil:
|
|
129
|
+
return {"path": path, "image": tk_img, "pil": pil_img, "cancelled": False}
|
|
130
|
+
return {"path": path, "image": tk_img, "pil": None, "cancelled": False}
|
|
131
|
+
|
|
132
|
+
# Otherwise fall back to the asset loader which returns a Tk image
|
|
133
|
+
img = load_image(path)
|
|
134
|
+
if resize:
|
|
135
|
+
img = self._resize_any(img, resize)
|
|
136
|
+
return {"path": path, "image": img, "pil": None, "cancelled": False}
|
|
137
|
+
|
|
138
|
+
except Exception as e:
|
|
139
|
+
print(f"[ERROR] Failed to load image '{path}': {e}")
|
|
140
|
+
|
|
141
|
+
if retry:
|
|
142
|
+
print("Trying again...")
|
|
143
|
+
continue
|
|
144
|
+
|
|
145
|
+
# On error, provide a fallback (PIL if available)
|
|
146
|
+
if Image is not None:
|
|
147
|
+
w, h = fallback_size if fallback_size else (64, 64)
|
|
148
|
+
pil_fb = Image.new("RGB", (w, h), fallback_color)
|
|
149
|
+
if resize:
|
|
150
|
+
pil_fb = pil_fb.resize(resize, Image.Resampling.LANCZOS)
|
|
151
|
+
tk_fb = ImageTk.PhotoImage(pil_fb)
|
|
152
|
+
if return_pil:
|
|
153
|
+
return {"path": None, "image": tk_fb, "pil": pil_fb, "cancelled": True}
|
|
154
|
+
return {"path": None, "image": tk_fb, "pil": None, "cancelled": True}
|
|
155
|
+
|
|
156
|
+
fb = load_image(None, size=fallback_size, color=fallback_color)
|
|
157
|
+
if resize:
|
|
158
|
+
fb = self._resize_any(fb, resize)
|
|
159
|
+
return {"path": None, "image": fb, "pil": None, "cancelled": True}
|
|
160
|
+
|
|
161
|
+
# ======================================================================
|
|
162
|
+
# FILE DIALOGS
|
|
163
|
+
# ======================================================================
|
|
32
164
|
def ask_save_file(self, default_name="save.json",
|
|
33
165
|
filetypes=[("JSON files", "*.json"), ("All files", "*.*")]):
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
root.withdraw()
|
|
166
|
+
root = self._make_root()
|
|
167
|
+
|
|
37
168
|
path = filedialog.asksaveasfilename(
|
|
38
169
|
title="Save As",
|
|
39
170
|
initialdir=self.default_save_folder,
|
|
@@ -42,13 +173,11 @@ class FileHelper:
|
|
|
42
173
|
defaultextension=filetypes[0][1]
|
|
43
174
|
)
|
|
44
175
|
root.destroy()
|
|
45
|
-
return path if path else None
|
|
176
|
+
return path if path else None
|
|
177
|
+
|
|
178
|
+
def ask_open_file(self, filetypes=[("JSON files", "*.json"), ("All files", "*.*")]):
|
|
179
|
+
root = self._make_root()
|
|
46
180
|
|
|
47
|
-
def ask_open_file(self,
|
|
48
|
-
filetypes=[("JSON files", "*.json"), ("All files", "*.*")]):
|
|
49
|
-
"""Open a Tkinter Open dialog starting in the default folder."""
|
|
50
|
-
root = tk.Tk()
|
|
51
|
-
root.withdraw()
|
|
52
181
|
path = filedialog.askopenfilename(
|
|
53
182
|
title="Open File",
|
|
54
183
|
initialdir=self.default_save_folder,
|
|
@@ -57,22 +186,20 @@ class FileHelper:
|
|
|
57
186
|
root.destroy()
|
|
58
187
|
return path if path else None
|
|
59
188
|
|
|
60
|
-
#
|
|
61
|
-
#
|
|
62
|
-
#
|
|
189
|
+
# ======================================================================
|
|
190
|
+
# PATH HANDLING
|
|
191
|
+
# ======================================================================
|
|
63
192
|
def _resolve_path(self, filename):
|
|
64
|
-
"""Return a full path, resolving relative paths to the default folder."""
|
|
65
193
|
if not filename:
|
|
66
194
|
return None
|
|
67
195
|
if os.path.isabs(filename):
|
|
68
196
|
return filename
|
|
69
197
|
return os.path.join(self.default_save_folder, filename)
|
|
70
198
|
|
|
71
|
-
#
|
|
199
|
+
# ======================================================================
|
|
72
200
|
# TEXT FILES
|
|
73
|
-
#
|
|
201
|
+
# ======================================================================
|
|
74
202
|
def save_text(self, filename, content):
|
|
75
|
-
"""Save plain text data to a file."""
|
|
76
203
|
path = self._resolve_path(filename)
|
|
77
204
|
if not path:
|
|
78
205
|
print("[CANCELLED] No save path provided.")
|
|
@@ -84,7 +211,6 @@ class FileHelper:
|
|
|
84
211
|
return path
|
|
85
212
|
|
|
86
213
|
def load_text(self, filename):
|
|
87
|
-
"""Load plain text data from a file."""
|
|
88
214
|
path = self._resolve_path(filename)
|
|
89
215
|
if path and os.path.exists(path):
|
|
90
216
|
with open(path, "r", encoding="utf-8") as f:
|
|
@@ -92,11 +218,10 @@ class FileHelper:
|
|
|
92
218
|
print(f"[WARN] Text file not found: {path}")
|
|
93
219
|
return None
|
|
94
220
|
|
|
95
|
-
#
|
|
221
|
+
# ======================================================================
|
|
96
222
|
# JSON FILES
|
|
97
|
-
#
|
|
223
|
+
# ======================================================================
|
|
98
224
|
def save_json(self, filename, data):
|
|
99
|
-
"""Save JSON-serializable data."""
|
|
100
225
|
path = self._resolve_path(filename)
|
|
101
226
|
if not path:
|
|
102
227
|
print("[CANCELLED] No save path provided.")
|
|
@@ -108,7 +233,6 @@ class FileHelper:
|
|
|
108
233
|
return path
|
|
109
234
|
|
|
110
235
|
def load_json(self, filename):
|
|
111
|
-
"""Load JSON data."""
|
|
112
236
|
path = self._resolve_path(filename)
|
|
113
237
|
if path and os.path.exists(path):
|
|
114
238
|
with open(path, "r", encoding="utf-8") as f:
|
|
@@ -116,11 +240,10 @@ class FileHelper:
|
|
|
116
240
|
print(f"[WARN] JSON file not found: {path}")
|
|
117
241
|
return None
|
|
118
242
|
|
|
119
|
-
#
|
|
243
|
+
# ======================================================================
|
|
120
244
|
# PICKLE FILES
|
|
121
|
-
#
|
|
245
|
+
# ======================================================================
|
|
122
246
|
def save_pickle(self, filename, obj):
|
|
123
|
-
"""Save a Python object using pickle."""
|
|
124
247
|
path = self._resolve_path(filename)
|
|
125
248
|
if not path:
|
|
126
249
|
print("[CANCELLED] No save path provided.")
|
|
@@ -132,11 +255,9 @@ class FileHelper:
|
|
|
132
255
|
return path
|
|
133
256
|
|
|
134
257
|
def load_pickle(self, filename):
|
|
135
|
-
"""Load a pickled Python object."""
|
|
136
258
|
path = self._resolve_path(filename)
|
|
137
259
|
if path and os.path.exists(path):
|
|
138
260
|
with open(path, "rb") as f:
|
|
139
261
|
return pickle.load(f)
|
|
140
262
|
print(f"[WARN] Pickle file not found: {path}")
|
|
141
263
|
return None
|
|
142
|
-
|
|
Binary file
|
crystalwindow/__init__.py
CHANGED
|
@@ -4,9 +4,9 @@ check_for_update("crystalwindow")
|
|
|
4
4
|
|
|
5
5
|
# === Core Systems ===
|
|
6
6
|
from .window import Window
|
|
7
|
-
from .sprites import Sprite
|
|
7
|
+
from .sprites import Sprite, CollisionHandler
|
|
8
8
|
from .tilemap import TileMap
|
|
9
|
-
from .
|
|
9
|
+
from .objects import Player, Block, Enemy
|
|
10
10
|
from .gravity import Gravity
|
|
11
11
|
from .FileHelper import FileHelper
|
|
12
12
|
from .math import Mathematics
|
|
@@ -22,6 +22,8 @@ from .assets import (
|
|
|
22
22
|
flip_vertical,
|
|
23
23
|
LoopAnim,
|
|
24
24
|
loop_image,
|
|
25
|
+
load_file,
|
|
26
|
+
Sound,
|
|
25
27
|
)
|
|
26
28
|
from .animation import Animation
|
|
27
29
|
|
|
@@ -33,8 +35,7 @@ from .gui import Button, Label, GUIManager, hex_to_rgb, Fade
|
|
|
33
35
|
from .gui_ext import Toggle, Slider
|
|
34
36
|
|
|
35
37
|
# === Time System ===
|
|
36
|
-
|
|
37
|
-
from .clock import Clock, Stopwatch, CountdownTimer, Scheduler
|
|
38
|
+
from .clock import Clock, CountdownTimer
|
|
38
39
|
|
|
39
40
|
# === Drawing Helpers ===
|
|
40
41
|
from .draw_helpers import gradient_rect, CameraShake
|
|
@@ -43,23 +44,18 @@ from .draw_text_helper import DrawTextManager
|
|
|
43
44
|
from .draw_tool import CrystalDraw
|
|
44
45
|
|
|
45
46
|
# === Misc Helpers ===
|
|
46
|
-
from .fun_helpers import random_name, DebugOverlay, random_color, random_palette, lerp
|
|
47
|
+
from .fun_helpers import random_name, DebugOverlay, random_color, random_palette, lerp, random_number
|
|
47
48
|
from .camera import Camera
|
|
48
49
|
from .color_handler import Colors, Color
|
|
49
|
-
from .websearch import
|
|
50
|
+
from .websearch import SearchResult, WebBrowse
|
|
50
51
|
|
|
51
52
|
# === 3D Engine ===
|
|
52
|
-
from .crystal3d import CW3D
|
|
53
|
+
from .crystal3d import CW3DShape, CW3D
|
|
53
54
|
|
|
54
|
-
# === AI Engine ===
|
|
55
|
-
from .ai import AI
|
|
56
|
-
|
|
57
|
-
# === Server Manager ===
|
|
58
|
-
from .cw_client import CWClient
|
|
59
55
|
|
|
60
56
|
__all__ = [
|
|
61
57
|
# --- Core ---
|
|
62
|
-
"Window", "Sprite", "TileMap", "Player", "Gravity", "FileHelper", "Mathematics",
|
|
58
|
+
"Window", "Sprite", "TileMap", "Player", "Block", "Enemy", "Gravity", "FileHelper", "Mathematics",
|
|
63
59
|
|
|
64
60
|
# --- Assets & Animation ---
|
|
65
61
|
"load_image",
|
|
@@ -72,9 +68,11 @@ __all__ = [
|
|
|
72
68
|
"Animation",
|
|
73
69
|
"LoopAnim",
|
|
74
70
|
"loop_image",
|
|
71
|
+
"load_file",
|
|
72
|
+
"Sound",
|
|
75
73
|
|
|
76
74
|
# --- Collision ---
|
|
77
|
-
"check_collision", "resolve_collision",
|
|
75
|
+
"check_collision", "resolve_collision", "CollisionHandler",
|
|
78
76
|
|
|
79
77
|
# --- GUI ---
|
|
80
78
|
"Button", "Label", "GUIManager", "random_color", "hex_to_rgb", "Fade",
|
|
@@ -83,25 +81,16 @@ __all__ = [
|
|
|
83
81
|
"Toggle", "Slider",
|
|
84
82
|
|
|
85
83
|
# --- Time System ---
|
|
86
|
-
|
|
87
|
-
"Clock",
|
|
88
|
-
"Stopwatch",
|
|
89
|
-
"CountdownTimer",
|
|
90
|
-
"Scheduler",
|
|
84
|
+
"Clock", "CountdownTimer",
|
|
91
85
|
|
|
92
86
|
# --- Drawing ---
|
|
93
87
|
"gradient_rect", "CameraShake", "DrawHelper", "DrawTextManager", "CrystalDraw",
|
|
94
88
|
|
|
95
89
|
# --- Misc ---
|
|
96
90
|
"random_name", "DebugOverlay", "Camera", "Colors", "Color",
|
|
97
|
-
"random_palette", "lerp", "
|
|
91
|
+
"random_palette", "lerp", "SearchResult", "WebBrowse", "random_number",
|
|
98
92
|
|
|
99
93
|
# --- 3D ---
|
|
100
|
-
"CW3D",
|
|
101
|
-
|
|
102
|
-
# --- AI ---
|
|
103
|
-
"AI",
|
|
94
|
+
"CW3DShape", "CW3D",
|
|
104
95
|
|
|
105
|
-
|
|
106
|
-
"CWClient",
|
|
107
|
-
]
|
|
96
|
+
]
|