crystalwindow 4.6__py3-none-any.whl → 4.8__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.
@@ -1,8 +1,9 @@
1
1
  """
2
2
  CrystalWindow FileHelper
3
3
  ------------------------
4
- A utility class that handles saving/loading text, JSON, and pickle files,
5
- with optional Tk file dialogs and a default 'saves' directory.
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 with default folders & Tk dialogs."""
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
- Initialize the FileHelper.
66
+ Open an image using a dialog.
21
67
 
22
- Args:
23
- default_save_folder (str): Folder to save files in by default.
24
- Created automatically if missing.
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
- # TK FILE DIALOGS
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
- """Open a Tkinter Save dialog starting in the default folder."""
35
- root = tk.Tk()
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 # Return None if cancelled
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
- # INTERNAL PATH HELPER
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 .player import Player
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
- # Includes upgraded Clock + all helper utilities
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 WebSearchResult, WebSearch
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
- # FULL time system is now exposed
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", "WebSearchResult", "WebSearch",
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
- # --- Server Client ---
106
- "CWClient",
107
- ]
96
+ ]