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.
- 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 +93 -3
- crystalwindow/websearch.py +91 -164
- crystalwindow/window.py +430 -12
- {crystalwindow-4.6.dist-info → crystalwindow-4.8.dist-info}/METADATA +6 -2
- crystalwindow-4.8.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.8.dist-info}/WHEEL +0 -0
- {crystalwindow-4.6.dist-info → crystalwindow-4.8.dist-info}/licenses/LICENSE +0 -0
- {crystalwindow-4.6.dist-info → crystalwindow-4.8.dist-info}/top_level.txt +0 -0
crystalwindow/assets.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
# assets.py
|
|
2
1
|
import os
|
|
3
|
-
import random
|
|
2
|
+
import random, json, pickle
|
|
4
3
|
from tkinter import PhotoImage
|
|
5
4
|
|
|
6
5
|
try:
|
|
@@ -15,90 +14,129 @@ ASSETS = {} # cache for all loaded images
|
|
|
15
14
|
# --------------------------------------------------------
|
|
16
15
|
# MAIN IMAGE LOADER
|
|
17
16
|
# --------------------------------------------------------
|
|
18
|
-
def load_image(path, flip_h=False, flip_v=False):
|
|
17
|
+
def load_image(path=None, flip_h=False, flip_v=False, size=None, color=None):
|
|
19
18
|
"""
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
Unified image loader w/ caching, flips, and fallback.
|
|
20
|
+
Handles:
|
|
21
|
+
- path images
|
|
22
|
+
- solid color gens
|
|
23
|
+
- missing textures
|
|
24
|
+
- Pillow and pure-tk fallback
|
|
22
25
|
"""
|
|
23
26
|
|
|
24
|
-
|
|
27
|
+
# ----------------------------------------------------
|
|
28
|
+
# NO PATH → GENERATE STATIC IMAGE
|
|
29
|
+
# ----------------------------------------------------
|
|
30
|
+
if path is None:
|
|
31
|
+
if Image is None:
|
|
32
|
+
# basic tk only
|
|
33
|
+
w, h = size if size else (64, 64)
|
|
34
|
+
img = PhotoImage(width=w, height=h)
|
|
35
|
+
return img
|
|
36
|
+
|
|
37
|
+
w, h = size if size else (64, 64)
|
|
38
|
+
|
|
39
|
+
# solid or missing texture
|
|
40
|
+
pil_img = (
|
|
41
|
+
Image.new("RGB", (w, h), color) if color
|
|
42
|
+
else generate_missing_texture((w, h))
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
# flips BEFORE resize
|
|
46
|
+
if flip_h:
|
|
47
|
+
pil_img = pil_img.transpose(Image.FLIP_LEFT_RIGHT)
|
|
48
|
+
if flip_v:
|
|
49
|
+
pil_img = pil_img.transpose(Image.FLIP_TOP_BOTTOM)
|
|
50
|
+
|
|
51
|
+
if size:
|
|
52
|
+
pil_img = pil_img.resize(size, Image.Resampling.LANCZOS)
|
|
53
|
+
|
|
54
|
+
return ImageTk.PhotoImage(pil_img)
|
|
55
|
+
|
|
56
|
+
# ----------------------------------------------------
|
|
57
|
+
# USE FULL KEY AS CACHE
|
|
58
|
+
# ----------------------------------------------------
|
|
59
|
+
key = f"{path}|h={flip_h}|v={flip_v}|size={size}|color={color}"
|
|
25
60
|
if key in ASSETS:
|
|
26
61
|
return ASSETS[key]
|
|
27
62
|
|
|
63
|
+
# ----------------------------------------------------
|
|
64
|
+
# INVALID PATH → MISSING TEXTURE
|
|
65
|
+
# ----------------------------------------------------
|
|
28
66
|
if not os.path.exists(path):
|
|
29
|
-
print(f"
|
|
30
|
-
|
|
31
|
-
ASSETS[key] =
|
|
32
|
-
return
|
|
67
|
+
print(f"⚠ Missing image: {path}")
|
|
68
|
+
fb = load_image(None, size=size, color=color)
|
|
69
|
+
ASSETS[key] = fb
|
|
70
|
+
return fb
|
|
33
71
|
|
|
34
|
-
#
|
|
35
|
-
|
|
72
|
+
# ----------------------------------------------------
|
|
73
|
+
# PIL NOT INSTALLED → PURE TK LOADING
|
|
74
|
+
# ----------------------------------------------------
|
|
75
|
+
if Image is None:
|
|
36
76
|
try:
|
|
37
77
|
img = PhotoImage(file=path)
|
|
78
|
+
# cannot flip or resize without PIL, soz
|
|
38
79
|
ASSETS[key] = img
|
|
39
80
|
return img
|
|
40
81
|
except:
|
|
41
|
-
fb =
|
|
82
|
+
fb = load_image(None, size=size, color=color)
|
|
42
83
|
ASSETS[key] = fb
|
|
43
84
|
return fb
|
|
44
85
|
|
|
45
|
-
#
|
|
86
|
+
# ----------------------------------------------------
|
|
87
|
+
# LOAD USING PIL (BEST MODE)
|
|
88
|
+
# ----------------------------------------------------
|
|
46
89
|
try:
|
|
47
|
-
pil_img = Image.open(path)
|
|
90
|
+
pil_img = Image.open(path).convert("RGBA")
|
|
48
91
|
|
|
92
|
+
# flips
|
|
49
93
|
if flip_h:
|
|
50
94
|
pil_img = pil_img.transpose(Image.FLIP_LEFT_RIGHT)
|
|
51
95
|
if flip_v:
|
|
52
96
|
pil_img = pil_img.transpose(Image.FLIP_TOP_BOTTOM)
|
|
53
97
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
98
|
+
# resize AFTER flip
|
|
99
|
+
if size:
|
|
100
|
+
pil_img = pil_img.resize(size, Image.Resampling.LANCZOS)
|
|
101
|
+
|
|
102
|
+
tk = ImageTk.PhotoImage(pil_img)
|
|
103
|
+
ASSETS[key] = tk
|
|
104
|
+
return tk
|
|
57
105
|
|
|
58
106
|
except Exception as e:
|
|
59
|
-
print(f"
|
|
60
|
-
fb =
|
|
107
|
+
print(f"⚠ Error loading {path}: {e}")
|
|
108
|
+
fb = load_image(None, size=size, color=color)
|
|
61
109
|
ASSETS[key] = fb
|
|
62
110
|
return fb
|
|
63
111
|
|
|
64
112
|
|
|
65
|
-
#
|
|
66
|
-
#
|
|
67
|
-
#
|
|
68
|
-
def
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
random.randint(50, 255),
|
|
73
|
-
random.randint(50, 255),
|
|
74
|
-
)
|
|
75
|
-
|
|
76
|
-
if Image:
|
|
77
|
-
try:
|
|
78
|
-
pil_img = Image.open(path)
|
|
79
|
-
w, h = pil_img.size
|
|
80
|
-
except:
|
|
81
|
-
w, h = 64, 64
|
|
82
|
-
else:
|
|
83
|
-
w, h = 64, 64
|
|
113
|
+
# ========================================================
|
|
114
|
+
# MISSING TEXTURE GENERATOR
|
|
115
|
+
# ========================================================
|
|
116
|
+
def generate_missing_texture(size):
|
|
117
|
+
w, h = size
|
|
118
|
+
img = Image.new("RGB", (w, h))
|
|
119
|
+
px = img.load()
|
|
84
120
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
"size": (w, h),
|
|
88
|
-
"color": rand_color
|
|
89
|
-
}
|
|
121
|
+
pink = (255, 0, 255)
|
|
122
|
+
black = (0, 0, 0)
|
|
90
123
|
|
|
124
|
+
for y in range(h):
|
|
125
|
+
for x in range(w):
|
|
126
|
+
if (x // 8 + y // 8) % 2 == 0:
|
|
127
|
+
px[x, y] = pink
|
|
128
|
+
else:
|
|
129
|
+
px[x, y] = black
|
|
91
130
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
131
|
+
return img
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
# ========================================================
|
|
135
|
+
# PYGAME-STYLE FLIP HELPERS (FOR ALREADY-TK IMAGES)
|
|
136
|
+
# ========================================================
|
|
95
137
|
def flip_image(img, flip_h=False, flip_v=False):
|
|
96
|
-
"""
|
|
97
|
-
Returns a NEW flipped PhotoImage.
|
|
98
|
-
Like pygame.transform.flip().
|
|
99
|
-
"""
|
|
100
138
|
if Image is None or ImageTk is None:
|
|
101
|
-
print("
|
|
139
|
+
print("⚠ Pillow not installed; cannot flip images.")
|
|
102
140
|
return img
|
|
103
141
|
|
|
104
142
|
pil_img = ImageTk.getimage(img)
|
|
@@ -117,9 +155,8 @@ def flip_horizontal(img):
|
|
|
117
155
|
|
|
118
156
|
def flip_vertical(img):
|
|
119
157
|
return flip_image(img, flip_v=True)
|
|
120
|
-
|
|
121
158
|
# --------------------------------------------------------
|
|
122
|
-
# LOOPING
|
|
159
|
+
# LOOPING ANIMATIONS / IMAGES
|
|
123
160
|
# --------------------------------------------------------
|
|
124
161
|
class LoopAnim:
|
|
125
162
|
def __init__(self, frames, speed=0.2):
|
|
@@ -144,8 +181,6 @@ def loop_image(*imgs, speed=0.2):
|
|
|
144
181
|
Usage:
|
|
145
182
|
anim = loop_image(img1, img2, img3)
|
|
146
183
|
anim = loop_image(load_image("p1.png"), load_image("p2.png"))
|
|
147
|
-
|
|
148
|
-
Returns a LoopAnim object.
|
|
149
184
|
"""
|
|
150
185
|
frames = [x for x in imgs if x is not None]
|
|
151
186
|
|
|
@@ -180,12 +215,168 @@ def load_folder_images(folder, nested=True):
|
|
|
180
215
|
|
|
181
216
|
return result
|
|
182
217
|
|
|
218
|
+
#=========================================================
|
|
219
|
+
# LOAD FILES
|
|
220
|
+
#=========================================================
|
|
221
|
+
def load_file(path):
|
|
222
|
+
"""
|
|
223
|
+
Smart auto-loader for multiple formats.
|
|
224
|
+
Reads:
|
|
225
|
+
- .txt, .md, .cfg → text
|
|
226
|
+
- .json → parsed json
|
|
227
|
+
- .py → raw text
|
|
228
|
+
- .bin, .dat → raw bytes
|
|
229
|
+
- .pickle, .pkl → python objects
|
|
230
|
+
- .png/.gif → forwarded to load_image()
|
|
231
|
+
- .pdf → reads metadata only (no full extract)
|
|
232
|
+
"""
|
|
233
|
+
|
|
234
|
+
if not os.path.exists(path):
|
|
235
|
+
print(f"⚠️ Missing file: {path}")
|
|
236
|
+
return None
|
|
237
|
+
|
|
238
|
+
ext = path.lower().split(".")[-1]
|
|
239
|
+
|
|
240
|
+
# ---------------- TEXT FILES ----------------
|
|
241
|
+
if ext in ("txt", "md", "cfg", "ini", "py"):
|
|
242
|
+
try:
|
|
243
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
244
|
+
return {
|
|
245
|
+
"type": "text",
|
|
246
|
+
"content": f.read()
|
|
247
|
+
}
|
|
248
|
+
except Exception as e:
|
|
249
|
+
print(f"⚠️ Error reading text file: {e}")
|
|
250
|
+
return None
|
|
251
|
+
|
|
252
|
+
# ---------------- JSON ----------------
|
|
253
|
+
if ext == "json":
|
|
254
|
+
try:
|
|
255
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
256
|
+
return {
|
|
257
|
+
"type": "json",
|
|
258
|
+
"content": json.load(f)
|
|
259
|
+
}
|
|
260
|
+
except Exception as e:
|
|
261
|
+
print(f"⚠️ JSON error: {e}")
|
|
262
|
+
return None
|
|
263
|
+
|
|
264
|
+
# ---------------- PICKLE ----------------
|
|
265
|
+
if ext in ("pickle", "pkl"):
|
|
266
|
+
try:
|
|
267
|
+
with open(path, "rb") as f:
|
|
268
|
+
return {
|
|
269
|
+
"type": "pickle",
|
|
270
|
+
"content": pickle.load(f)
|
|
271
|
+
}
|
|
272
|
+
except Exception as e:
|
|
273
|
+
print(f"⚠️ Pickle error: {e}")
|
|
274
|
+
return None
|
|
275
|
+
|
|
276
|
+
# ---------------- BINARY ----------------
|
|
277
|
+
if ext in ("bin", "dat"):
|
|
278
|
+
try:
|
|
279
|
+
with open(path, "rb") as f:
|
|
280
|
+
return {
|
|
281
|
+
"type": "binary",
|
|
282
|
+
"content": f.read()
|
|
283
|
+
}
|
|
284
|
+
except Exception as e:
|
|
285
|
+
print(f"⚠️ Binary read error: {e}")
|
|
286
|
+
return None
|
|
287
|
+
|
|
288
|
+
# ---------------- PDF ----------------
|
|
289
|
+
if ext == "pdf":
|
|
290
|
+
try:
|
|
291
|
+
with open(path, "rb") as f:
|
|
292
|
+
head = f.read(1024) # first 1KB for metadata
|
|
293
|
+
return {
|
|
294
|
+
"type": "pdf",
|
|
295
|
+
"meta_preview": head,
|
|
296
|
+
"note": "only metadata preview, not full pdf extract"
|
|
297
|
+
}
|
|
298
|
+
except Exception as e:
|
|
299
|
+
print(f"⚠️ PDF read error: {e}")
|
|
300
|
+
return None
|
|
301
|
+
|
|
302
|
+
# ---------------- IMAGE ----------------
|
|
303
|
+
if ext in ("png", "gif", "jpg", "jpeg"):
|
|
304
|
+
try:
|
|
305
|
+
return {
|
|
306
|
+
"type": "image",
|
|
307
|
+
"content": load_image(path)
|
|
308
|
+
}
|
|
309
|
+
except Exception as e:
|
|
310
|
+
print(f"⚠️ Image load error: {e}")
|
|
311
|
+
return None
|
|
312
|
+
|
|
313
|
+
# ---------------- UNKNOWN TYPE ----------------
|
|
314
|
+
try:
|
|
315
|
+
with open(path, "rb") as f:
|
|
316
|
+
raw = f.read()
|
|
317
|
+
return {
|
|
318
|
+
"type": "unknown",
|
|
319
|
+
"content": raw
|
|
320
|
+
}
|
|
321
|
+
except:
|
|
322
|
+
print("⚠️ Unknown file load failed")
|
|
323
|
+
return None
|
|
183
324
|
|
|
184
325
|
# --------------------------------------------------------
|
|
185
|
-
# MUSIC PLACEHOLDER
|
|
326
|
+
# MUSIC PLACEHOLDER (REPLACED BY NEW SOUND ENGINE)
|
|
186
327
|
# --------------------------------------------------------
|
|
328
|
+
from matplotlib.pylab import size
|
|
329
|
+
import sounddevice as sd
|
|
330
|
+
import soundfile as sf
|
|
331
|
+
import threading
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
class Sound:
|
|
335
|
+
def __init__(self, path, vol=1.0):
|
|
336
|
+
self.path = path
|
|
337
|
+
self.data, self.sr = sf.read(path, dtype="float32")
|
|
338
|
+
self.volume = vol
|
|
339
|
+
self._looping = False
|
|
340
|
+
self._thread = None
|
|
341
|
+
|
|
342
|
+
def _play_audio(self, loop=False):
|
|
343
|
+
self._looping = loop
|
|
344
|
+
while True:
|
|
345
|
+
sd.play(self.data * self.volume, self.sr)
|
|
346
|
+
sd.wait()
|
|
347
|
+
if not self._looping:
|
|
348
|
+
break
|
|
349
|
+
|
|
350
|
+
def play(self):
|
|
351
|
+
threading.Thread(target=self._play_audio, daemon=True).start()
|
|
352
|
+
|
|
353
|
+
def play_once(self):
|
|
354
|
+
self.stop()
|
|
355
|
+
threading.Thread(target=self._play_audio, daemon=True).start()
|
|
356
|
+
|
|
357
|
+
def loop(self):
|
|
358
|
+
self.stop()
|
|
359
|
+
self._thread = threading.Thread(
|
|
360
|
+
target=self._play_audio, args=(True,), daemon=True
|
|
361
|
+
)
|
|
362
|
+
self._thread.start()
|
|
363
|
+
|
|
364
|
+
def stop(self):
|
|
365
|
+
self._looping = False
|
|
366
|
+
sd.stop()
|
|
367
|
+
|
|
368
|
+
def set_volume(self, v):
|
|
369
|
+
self.volume = max(0, min(1, float(v)))
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def load_sfx(path, vol=1.0):
|
|
373
|
+
return Sound(path, vol)
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
# backwards compat but not real music engine
|
|
187
377
|
def load_music(path):
|
|
188
|
-
print(f"[assets]
|
|
378
|
+
print(f"[assets] use load_sfx() instead: {path}")
|
|
379
|
+
|
|
189
380
|
|
|
190
381
|
def play_music(loop=-1):
|
|
191
|
-
print("[assets]
|
|
382
|
+
print("[assets] play_music() disabled, use SoundObject.loop()")
|
crystalwindow/clock.py
CHANGED
|
@@ -2,74 +2,37 @@ import time
|
|
|
2
2
|
from datetime import datetime
|
|
3
3
|
from collections import deque
|
|
4
4
|
|
|
5
|
-
|
|
6
5
|
# ============================================================
|
|
7
|
-
#
|
|
6
|
+
# Timer/Wait for a specific duration
|
|
8
7
|
# ============================================================
|
|
9
|
-
class
|
|
8
|
+
class CountdownTimer:
|
|
10
9
|
def __init__(self):
|
|
11
|
-
self.start_time =
|
|
12
|
-
self.
|
|
10
|
+
self.start_time = 0.0
|
|
11
|
+
self.duration = 0.0
|
|
13
12
|
self.running = False
|
|
14
13
|
|
|
15
|
-
def start(self):
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def stop(self):
|
|
21
|
-
if self.running:
|
|
22
|
-
self.elapsed_time += time.perf_counter() - self.start_time
|
|
23
|
-
self.running = False
|
|
14
|
+
def start(self, duration: float):
|
|
15
|
+
self.duration = duration
|
|
16
|
+
self.start_time = time.perf_counter()
|
|
17
|
+
self.running = True
|
|
24
18
|
|
|
25
19
|
def reset(self):
|
|
26
|
-
self.start_time =
|
|
27
|
-
self.
|
|
20
|
+
self.start_time = 0.0
|
|
21
|
+
self.duration = 0.0
|
|
28
22
|
self.running = False
|
|
29
23
|
|
|
30
|
-
def
|
|
31
|
-
if self.running:
|
|
32
|
-
return
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
# ============================================================
|
|
37
|
-
# Countdown Timer Helper
|
|
38
|
-
# ============================================================
|
|
39
|
-
class CountdownTimer:
|
|
40
|
-
def __init__(self):
|
|
41
|
-
self.target_time = None
|
|
42
|
-
|
|
43
|
-
def start(self, seconds: float):
|
|
44
|
-
self.target_time = time.perf_counter() + seconds
|
|
45
|
-
|
|
46
|
-
def remaining(self):
|
|
47
|
-
if not self.target_time:
|
|
48
|
-
return 0
|
|
49
|
-
return max(0, self.target_time - time.perf_counter())
|
|
50
|
-
|
|
51
|
-
def done(self):
|
|
52
|
-
return self.remaining() == 0
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
# ============================================================
|
|
56
|
-
# Event Scheduler Helper
|
|
57
|
-
# ============================================================
|
|
58
|
-
class Scheduler:
|
|
59
|
-
def __init__(self):
|
|
60
|
-
self.events = [] # list of (interval, last_run, function)
|
|
61
|
-
|
|
62
|
-
def schedule(self, interval: float, func):
|
|
63
|
-
"""Run function every `interval` seconds."""
|
|
64
|
-
self.events.append([interval, time.perf_counter(), func])
|
|
24
|
+
def is_finished(self):
|
|
25
|
+
if not self.running:
|
|
26
|
+
return True
|
|
27
|
+
elapsed = time.perf_counter() - self.start_time
|
|
28
|
+
return elapsed >= self.duration
|
|
65
29
|
|
|
66
|
-
def
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
event[1] = now # update last_run
|
|
30
|
+
def get_remaining(self):
|
|
31
|
+
if not self.running:
|
|
32
|
+
return 0.0
|
|
33
|
+
elapsed = time.perf_counter() - self.start_time
|
|
34
|
+
remaining = self.duration - elapsed
|
|
35
|
+
return max(0.0, remaining)
|
|
73
36
|
|
|
74
37
|
|
|
75
38
|
# ============================================================
|
|
@@ -87,11 +50,6 @@ class Clock:
|
|
|
87
50
|
|
|
88
51
|
self.paused = False
|
|
89
52
|
|
|
90
|
-
# Utilities
|
|
91
|
-
self.stopwatch = Stopwatch()
|
|
92
|
-
self.timer = CountdownTimer()
|
|
93
|
-
self.scheduler = Scheduler()
|
|
94
|
-
|
|
95
53
|
# ----------------------------------------------------------
|
|
96
54
|
# TICK + FPS
|
|
97
55
|
# ----------------------------------------------------------
|
crystalwindow/color_handler.py
CHANGED
|
@@ -28,20 +28,14 @@ class Color:
|
|
|
28
28
|
if not name.startswith("__")}
|
|
29
29
|
|
|
30
30
|
def __init__(self, r=None, g=None, b=None, a=255, hex_value=None):
|
|
31
|
-
# -------------------------------------------------
|
|
32
|
-
# 1) raw tuple variable: Color(Blue)
|
|
33
|
-
# -------------------------------------------------
|
|
34
31
|
if isinstance(r, (tuple, list)):
|
|
35
|
-
if len(r) == 3:
|
|
32
|
+
if len(r) == 3:
|
|
36
33
|
self.r, self.g, self.b = r
|
|
37
34
|
self.a = a
|
|
38
|
-
elif len(r) == 4:
|
|
35
|
+
elif len(r) == 4:
|
|
39
36
|
self.r, self.g, self.b, self.a = r
|
|
40
37
|
return
|
|
41
38
|
|
|
42
|
-
# -------------------------------------------------
|
|
43
|
-
# 2) name string: Color("blue")
|
|
44
|
-
# -------------------------------------------------
|
|
45
39
|
if isinstance(r, str):
|
|
46
40
|
name = r.lower()
|
|
47
41
|
if name not in self.COLOR_NAMES:
|
|
@@ -54,22 +48,15 @@ class Color:
|
|
|
54
48
|
self.a = a
|
|
55
49
|
return
|
|
56
50
|
|
|
57
|
-
# -------------------------------------------------
|
|
58
|
-
# 3) HEX input
|
|
59
|
-
# -------------------------------------------------
|
|
60
51
|
if hex_value is not None:
|
|
61
52
|
self.r, self.g, self.b, self.a = self.hex_to_rgba(hex_value)
|
|
62
53
|
return
|
|
63
54
|
|
|
64
|
-
# -------------------------------------------------
|
|
65
|
-
# 4) manual RGB
|
|
66
|
-
# -------------------------------------------------
|
|
67
55
|
self.r = r or 0
|
|
68
56
|
self.g = g or 0
|
|
69
57
|
self.b = b or 0
|
|
70
58
|
self.a = a
|
|
71
59
|
|
|
72
|
-
# convert hex -> rgba
|
|
73
60
|
@staticmethod
|
|
74
61
|
def hex_to_rgba(hex_value: str):
|
|
75
62
|
hex_value = hex_value.replace("#", "")
|
|
@@ -84,23 +71,19 @@ class Color:
|
|
|
84
71
|
b = int(hex_value[4:6], 16)
|
|
85
72
|
a = int(hex_value[6:8], 16)
|
|
86
73
|
else:
|
|
87
|
-
raise ValueError("bad hex
|
|
74
|
+
raise ValueError("bad hex")
|
|
88
75
|
return r, g, b, a
|
|
89
76
|
|
|
90
|
-
# return pygame-friendly color tuple
|
|
91
77
|
def to_tuple(self):
|
|
92
78
|
return (self.r, self.g, self.b, self.a)
|
|
93
79
|
|
|
94
|
-
# rgba -> hex
|
|
95
80
|
def to_hex(self):
|
|
96
81
|
return "#{:02X}{:02X}{:02X}{:02X}".format(self.r, self.g, self.b, self.a)
|
|
97
82
|
|
|
98
|
-
# quick alpha edit
|
|
99
83
|
def set_alpha(self, a):
|
|
100
84
|
self.a = max(0, min(255, a))
|
|
101
85
|
return self
|
|
102
86
|
|
|
103
|
-
# color math
|
|
104
87
|
def brighten(self, amt=30):
|
|
105
88
|
self.r = min(255, self.r + amt)
|
|
106
89
|
self.g = min(255, self.g + amt)
|
|
@@ -115,7 +98,6 @@ class Color:
|
|
|
115
98
|
|
|
116
99
|
@staticmethod
|
|
117
100
|
def lerp(c1, c2, t: float):
|
|
118
|
-
"""blend between two colors"""
|
|
119
101
|
r = c1.r + (c2.r - c1.r) * t
|
|
120
102
|
g = c1.g + (c2.g - c1.g) * t
|
|
121
103
|
b = c1.b + (c2.b - c1.b) * t
|