crystalwindow 1.4.8__py3-none-any.whl → 2.7.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.

Potentially problematic release.


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

crystalwindow/gui_ext.py CHANGED
@@ -5,28 +5,33 @@ from .gui import Button
5
5
 
6
6
  class Toggle:
7
7
  def __init__(self, rect, value=False, color=(200,200,200), hover_color=(255,255,255)):
8
+ # rect = (x, y, w, h)
8
9
  self.rect = rect
9
10
  self.value = value
10
11
  self.color = color
11
12
  self.hover_color = hover_color
12
13
  self.hovered = False
13
14
 
14
- def update(self, win):
15
+ def update(self, win: Window):
15
16
  mx, my = win.mouse_pos
16
- x,y,w,h = self.rect
17
+ x, y, w, h = self.rect
17
18
  self.hovered = x <= mx <= x+w and y <= my <= y+h
18
- if self.hovered and win.is_mouse_pressed(1):
19
+ if self.hovered and win.mouse_pressed(1):
19
20
  self.value = not self.value
20
21
 
21
- def draw(self, win):
22
+ def draw(self, win: Window):
22
23
  draw_color = self.hover_color if self.hovered else self.color
23
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)
24
29
 
25
- from .window import Window
26
30
 
27
31
  class Slider:
28
32
  def __init__(self, rect, min_val=0, max_val=100, value=50, color=(150,150,150), handle_radius=10):
29
- self.rect = rect # x, y, w, h
33
+ # rect = (x, y, w, h)
34
+ self.rect = rect
30
35
  self.min_val = min_val
31
36
  self.max_val = max_val
32
37
  self.value = value
@@ -38,25 +43,24 @@ class Slider:
38
43
  mx, my = win.mouse_pos
39
44
  x, y, w, h = self.rect
40
45
 
41
- # Start dragging if clicked on the slider track or handle
42
46
  handle_x = x + ((self.value - self.min_val) / (self.max_val - self.min_val)) * w
43
47
  handle_y = y + h // 2
44
- if win.is_mouse_pressed(1):
45
- if (x <= mx <= x + w and y <= my <= y + h) or ((mx-handle_x)**2 + (my-handle_y)**2 <= self.handle_radius**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):
46
52
  self.dragging = True
47
53
  else:
48
- self.dragging = False
54
+ self.dragging = False # stop drag when released
49
55
 
50
- # Drag the handle
56
+ # update value while dragging
51
57
  if self.dragging:
52
- rel_x = max(0, min(mx - x, w))
58
+ rel_x = max(0, min(mx - x, w)) # clamp
53
59
  self.value = self.min_val + (rel_x / w) * (self.max_val - self.min_val)
54
60
 
55
61
  def draw(self, win: Window):
56
62
  x, y, w, h = self.rect
57
- # draw slider track
58
63
  win.draw_rect(self.color, (x, y + h//2 - 2, w, 4))
59
- # draw handle as circle
60
64
  handle_x = x + ((self.value - self.min_val) / (self.max_val - self.min_val)) * w
61
65
  handle_y = y + h // 2
62
- win.draw_circle((255,0,0), (int(handle_x), int(handle_y)), self.handle_radius)
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
+
crystalwindow/sprites.py CHANGED
@@ -1,30 +1,37 @@
1
- import pygame
1
+ #Sprites.py
2
+ from tkinter import PhotoImage
2
3
 
3
4
  class Sprite:
4
5
  def __init__(self, pos, size=None, image=None, color=(255, 0, 0)):
5
6
  """
6
- pos: (x, y) topleft
7
- size: (w, h) if you want a plain rect
8
- image: pygame.Surface if u have an image
7
+ pos: (x, y) top-left
8
+ size: (w, h) if you want a plain colored rect
9
+ image: PhotoImage (Tkinter)
9
10
  color: fill color if size is used
10
11
  """
11
12
  self.pos = pos
12
- self.x, self.y = pos # <- add this here, not in __init__.py
13
+ self.x, self.y = pos
13
14
 
14
15
  if image is not None:
15
16
  self.image = image
17
+ self.width = image.width()
18
+ self.height = image.height()
16
19
  elif size is not None:
17
- self.image = pygame.Surface(size)
18
- self.image.fill(color)
20
+ self.width, self.height = size
21
+ self.image = None
22
+ self.color = color
19
23
  else:
20
24
  raise ValueError("Sprite needs either size or image")
21
25
 
22
- self.rect = self.image.get_rect(topleft=pos)
23
-
24
26
  def draw(self, win):
25
- win.blit(self.image, self.rect)
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))
26
32
 
27
33
  def move(self, dx, dy):
34
+ """Move sprite by dx/dy"""
28
35
  self.x += dx
29
36
  self.y += dy
30
- self.rect.topleft = (self.x, self.y)
37
+ self.pos = (self.x, self.y)
@@ -0,0 +1,54 @@
1
+ from importlib.metadata import version as get_version, PackageNotFoundError
2
+ import requests
3
+ import os
4
+ import json
5
+
6
+ def check_for_update(package_name="crystalwindow"):
7
+ """
8
+ Checks PyPI for updates once per installed version.
9
+ If up to date, prints it only once.
10
+ If outdated, warns user every time until updated.
11
+ """
12
+ try:
13
+ # get current version
14
+ try:
15
+ current_version = get_version(package_name)
16
+ except PackageNotFoundError:
17
+ print(f"(⚠️ Package '{package_name}' not found)")
18
+ return
19
+
20
+ # file to track last notified version
21
+ cache_file = os.path.join(os.path.expanduser("~"), f".{package_name}_version_cache.json")
22
+
23
+ # load cache
24
+ if os.path.exists(cache_file):
25
+ with open(cache_file, "r") as f:
26
+ cache = json.load(f)
27
+ else:
28
+ cache = {}
29
+
30
+ # skip check if we already confirmed this version
31
+ if cache.get("last_checked") == current_version:
32
+ return
33
+
34
+ # get newest version from PyPI
35
+ url = f"https://pypi.org/pypi/{package_name}/json"
36
+ response = requests.get(url, timeout=3)
37
+
38
+ if response.status_code == 200:
39
+ latest_version = response.json()["info"]["version"]
40
+
41
+ if latest_version == current_version:
42
+ print(f"✅ Up to date! ver = {current_version}.")
43
+ cache["last_checked"] = current_version
44
+ else:
45
+ print(f"\n⚠️ Hey Future Dev! You're using an old version of {package_name} ({current_version})")
46
+ print(f"👉 The latest is {latest_version}! To update, run:")
47
+ print(f" pip install --upgrade {package_name}")
48
+ print(f"Or visit: https://pypi.org/project/{package_name}/{latest_version}/\n")
49
+
50
+ with open(cache_file, "w") as f:
51
+ json.dump(cache, f)
52
+
53
+ except Exception as e:
54
+ print(f"(⚠️ Version check failed: {e})")
crystalwindow/window.py CHANGED
@@ -1,211 +1,218 @@
1
- import pygame
2
- from pygame import image
3
- import os
4
- import sys
5
- import contextlib
6
- import base64, io
1
+ # === Window Management (tkinter version) ===
2
+ import tkinter as tk
3
+ import base64, io, os, sys, contextlib
7
4
 
5
+ # === Boot ===
6
+ main_file = os.path.basename(sys.argv[0])
7
+ print(f"Welcome Future Dev!, running.. '{main_file}'")
8
+
9
+ # Base64 fallback logo (optional)
8
10
  DEFAULT_LOGO_BASE64 = "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"
9
11
 
10
12
  def decode_logo():
11
- """Decode the base64 logo and return a pygame surface."""
12
- logo_data = base64.b64decode(DEFAULT_LOGO_BASE64)
13
- with io.BytesIO(logo_data) as logo_bytes:
14
- return image.load(logo_bytes)
13
+ """Decode the base64 logo and return a Tk PhotoImage."""
14
+ try:
15
+ logo_data = base64.b64decode(DEFAULT_LOGO_BASE64)
16
+ with io.BytesIO(logo_data) as buf:
17
+ return tk.PhotoImage(data=buf.read())
18
+ except Exception:
19
+ return None
20
+
21
+
15
22
  class Window:
16
- # friendly string -> pygame constant mapping
23
+ # === Keymap to simplify key usage ===
17
24
  KEY_MAP = {
18
- "left": pygame.K_LEFT,
19
- "right": pygame.K_RIGHT,
20
- "up": pygame.K_UP,
21
- "down": pygame.K_DOWN,
22
- "space": pygame.K_SPACE,
23
- "enter": pygame.K_RETURN,
24
- "backspace": pygame.K_BACKSPACE,
25
- "tab": pygame.K_TAB,
26
- "escape": pygame.K_ESCAPE,
27
- "Rshift": pygame.K_LSHIFT,
28
- "Lctrl": pygame.K_LCTRL,
29
- "Rshift": pygame.K_RSHIFT,
30
- "Rctrl": pygame.K_RCTRL,
31
- "alt": pygame.K_LALT,
32
- "keya": pygame.K_a,
33
- "keyb": pygame.K_b,
34
- "keyc": pygame.K_c,
35
- "keyd": pygame.K_d,
36
- "keye": pygame.K_e,
37
- "keyf": pygame.K_f,
38
- "keyg": pygame.K_g,
39
- "keyh": pygame.K_h,
40
- "keyi": pygame.K_i,
41
- "keyj": pygame.K_j,
42
- "keyk": pygame.K_k,
43
- "keyl": pygame.K_l,
44
- "keym": pygame.K_m,
45
- "keyn": pygame.K_n,
46
- "keyo": pygame.K_o,
47
- "keyp": pygame.K_p,
48
- "keyq": pygame.K_q,
49
- "keyr": pygame.K_r,
50
- "keys": pygame.K_s,
51
- "keyt": pygame.K_t,
52
- "keyu": pygame.K_u,
53
- "keyv": pygame.K_v,
54
- "keyw": pygame.K_w,
55
- "keyx": pygame.K_x,
56
- "keyy": pygame.K_y,
57
- "keyz": pygame.K_z,
58
- "0": pygame.K_0,
59
- "1": pygame.K_1,
60
- "2": pygame.K_2,
61
- "3": pygame.K_3,
62
- "4": pygame.K_4,
63
- "5": pygame.K_5,
64
- "6": pygame.K_6,
65
- "7": pygame.K_7,
66
- "8": pygame.K_8,
67
- "9": pygame.K_9,
68
- "f1": pygame.K_F1,
69
- "f2": pygame.K_F2,
70
- "f3": pygame.K_F3,
71
- "f4": pygame.K_F4,
72
- "f5": pygame.K_F5,
73
- "f6": pygame.K_F6,
74
- "f7": pygame.K_F7,
75
- "f8": pygame.K_F8,
76
- "f9": pygame.K_F9,
77
- "f10": pygame.K_F10,
78
- "f11": pygame.K_F11,
79
- "f12": pygame.K_F12,
80
- "insert": pygame.K_INSERT,
81
- "delete": pygame.K_DELETE,
82
- "home": pygame.K_HOME,
83
- "end": pygame.K_END,
84
- "pageup": pygame.K_PAGEUP,
85
- "pagedown": pygame.K_PAGEDOWN,
86
- "capslock": pygame.K_CAPSLOCK,
87
- "numlock": pygame.K_NUMLOCK,
88
- "scrolllock": pygame.K_SCROLLOCK,
25
+ # Arrows & Movement
26
+ "left": "Left",
27
+ "right": "Right",
28
+ "up": "Up",
29
+ "down": "Down",
30
+
31
+ # Common control keys
32
+ "space": "space",
33
+ "enter": "Return",
34
+ "backspace": "BackSpace",
35
+ "tab": "Tab",
36
+ "escape": "Escape",
37
+ "Rshift": "Shift_R",
38
+ "Lshift": "Shift_L",
39
+ "Rctrl": "Control_R",
40
+ "Lctrl": "Control_L",
41
+ "alt": "Alt_L",
42
+
43
+ # Letters
44
+ "keya": "a", "keyb": "b", "keyc": "c", "keyd": "d",
45
+ "keye": "e", "keyf": "f", "keyg": "g", "keyh": "h",
46
+ "keyi": "i", "keyj": "j", "keyk": "k", "keyl": "l",
47
+ "keym": "m", "keyn": "n", "keyo": "o", "keyp": "p",
48
+ "keyq": "q", "keyr": "r", "keys": "s", "keyt": "t",
49
+ "keyu": "u", "keyv": "v", "keyw": "w", "keyx": "x",
50
+ "keyy": "y", "keyz": "z",
51
+
52
+ # Numbers
53
+ "0": "0", "1": "1", "2": "2", "3": "3", "4": "4",
54
+ "5": "5", "6": "6", "7": "7", "8": "8", "9": "9",
55
+
56
+ # Function keys
57
+ "f1": "F1", "f2": "F2", "f3": "F3", "f4": "F4",
58
+ "f5": "F5", "f6": "F6", "f7": "F7", "f8": "F8",
59
+ "f9": "F9", "f10": "F10", "f11": "F11", "f12": "F12",
60
+
61
+ # Editing and navigation
62
+ "insert": "Insert",
63
+ "delete": "Delete",
64
+ "home": "Home",
65
+ "end": "End",
66
+ "pageup": "Prior", # "Page Up" in Tkinter is "Prior"
67
+ "pagedown": "Next", # "Page Down" is "Next"
68
+
69
+ # Lock keys
70
+ "capslock": "Caps_Lock",
71
+ "numlock": "Num_Lock",
72
+ "scrolllock": "Scroll_Lock",
73
+
74
+ # Symbols
75
+ "+": "plus",
76
+ "-": "minus",
77
+ "/": "slash",
78
+ "backslash": "backslash",
79
+ "*": "asterisk",
80
+ "=": "equal"
89
81
  }
90
82
 
91
-
92
83
  def __init__(self, width=640, height=480, title="Game Window", icon=None):
93
- # temporarily silence pygame print
94
- with open(os.devnull, "w") as f, contextlib.redirect_stdout(f):
95
- pygame.init() # <- no more hello message
96
-
97
84
  self.width = width
98
85
  self.height = height
99
86
  self.title = title
100
- self.screen = pygame.display.set_mode((width, height))
101
- pygame.display.set_caption(title)
102
-
103
- # --- ICON HANDLING ---
104
- if icon:
105
- # User provided icon logic
106
- if isinstance(icon, str): # Path provided as string
107
- icon_path = icon
108
- if not os.path.isabs(icon_path):
109
- icon_path = os.path.join(os.getcwd(), icon) # Relative path, make it absolute
110
-
111
- if os.path.exists(icon_path): # Valid path
112
- img = pygame.image.load(icon_path).convert_alpha()
113
- pygame.display.set_icon(img)
114
- else: # File not found
115
- print(f"⚠️ Icon file not found: {icon_path}")
116
- img = decode_logo() # Use default base64 logo
117
- pygame.display.set_icon(img)
118
- else:
119
- pygame.display.set_icon(icon) # Use the provided icon directly
120
- else:
121
- # No icon provided, check for default in the local directory
122
- default_icon_path = os.path.join(os.path.dirname(__file__), "icons", "default_icon.png")
123
- if os.path.exists(default_icon_path):
124
- img = pygame.image.load(default_icon_path).convert_alpha()
125
- pygame.display.set_icon(img)
126
- else:
127
- # Use base64 fallback
87
+
88
+ self.root = tk.Tk()
89
+ self.root.title(title)
90
+ self.root.protocol("WM_DELETE_WINDOW", self.quit)
91
+
92
+ # === ICON HANDLING ===
93
+ if icon and os.path.exists(icon):
94
+ try:
95
+ img = tk.PhotoImage(file=icon)
96
+ self.root.iconphoto(True, img)
97
+ except Exception:
98
+ print("⚠️ Icon load failed, using default")
128
99
  img = decode_logo()
129
- pygame.display.set_icon(img)
100
+ if img: self.root.iconphoto(True, img)
101
+ else:
102
+ logo = decode_logo()
103
+ if logo:
104
+ self.root.iconphoto(True, logo)
105
+
106
+ # === Canvas setup ===
107
+ self.canvas = tk.Canvas(self.root, width=width, height=height, bg="#141432", highlightthickness=0)
108
+ self.canvas.pack(fill="both", expand=True)
130
109
 
131
- self.clock = pygame.time.Clock()
110
+ # === State vars ===
132
111
  self.running = True
133
112
  self.keys = {}
134
- self.mouse_pos = (0,0)
135
- self.mouse_pressed = (False, False, False)
113
+ self.mouse_pos = (0, 0)
114
+ self._mouse_pressed = (False, False, False)
115
+ self._bg_color = (20, 20, 50)
136
116
  self.draw_calls = []
137
117
 
138
- # === input helpers ===
139
- def is_key_pressed(self, key):
140
- # key can be pygame constant OR string from KEY_MAP
118
+ # === Event bindings ===
119
+ self.root.bind("<KeyPress>", self._on_keydown)
120
+ self.root.bind("<KeyRelease>", self._on_keyup)
121
+ self.root.bind("<Motion>", self._on_mousemove)
122
+ self.root.bind("<ButtonPress>", self._on_mousedown)
123
+ self.root.bind("<ButtonRelease>", self._on_mouseup)
124
+
125
+ # === Input ===
126
+ def _on_keydown(self, event):
127
+ self.keys[event.keysym] = True
128
+
129
+ def _on_keyup(self, event):
130
+ self.keys[event.keysym] = False
131
+
132
+ def _on_mousemove(self, event):
133
+ self.mouse_pos = (event.x, event.y)
134
+
135
+ def _on_mousedown(self, event):
136
+ if event.num in [1, 2, 3]:
137
+ pressed = list(self._mouse_pressed)
138
+ pressed[event.num - 1] = True
139
+ self._mouse_pressed = tuple(pressed)
140
+
141
+ def _on_mouseup(self, event):
142
+ if event.num in [1, 2, 3]:
143
+ pressed = list(self._mouse_pressed)
144
+ pressed[event.num - 1] = False
145
+ self._mouse_pressed = tuple(pressed)
146
+
147
+ # === Helpers ===
148
+ def key_pressed(self, key):
141
149
  if isinstance(key, str):
142
- key = self.KEY_MAP.get(key, None)
143
- if key is None:
144
- return False
150
+ key = self.KEY_MAP.get(key, key)
145
151
  return self.keys.get(key, False)
146
152
 
147
- def is_mouse_pressed(self, button=1):
148
- return self.mouse_pressed[button-1]
153
+ def mouse_pressed(self, button=1):
154
+ return self._mouse_pressed[button - 1]
155
+
156
+ # === Drawing ===
157
+ def fill(self, color):
158
+ """Fill background with a color"""
159
+ if isinstance(color, str):
160
+ if color.startswith("#"):
161
+ fill_color = color
162
+ else:
163
+ fill_color = f"#{color}"
164
+ elif isinstance(color, tuple):
165
+ fill_color = f"#{color[0]:02x}{color[1]:02x}{color[2]:02x}"
166
+ else:
167
+ fill_color = "#000000"
168
+
169
+ self._bg_color = fill_color
170
+ self.canvas.delete("all")
171
+ self.canvas.configure(bg=fill_color)
149
172
 
150
- # === font system ===
151
- def get_font(self, name="Arial", size=16, bold=False, italic=False):
152
- if not hasattr(self, "font_cache"):
153
- self.font_cache = {}
154
- key = (name, size, bold, italic)
155
- if key not in self.font_cache:
156
- try:
157
- font = pygame.font.SysFont(name, size, bold, italic)
158
- except:
159
- font = pygame.font.Font(None, size)
160
- self.font_cache[key] = font
161
- return self.font_cache[key]
162
-
163
- def draw_text(self, text, font="Arial", size=16, color=(255,255,255), pos=(0,0), bold=False, italic=False):
164
- f = self.get_font(font, size, bold, italic)
165
- surf = f.render(text, True, color)
166
- self.screen.blit(surf, pos)
167
-
168
- # === drawing helpers ===a
169
173
  def draw_rect(self, color, rect):
170
- self.draw_calls.append(("rect", color, rect))
174
+ x, y, w, h = rect
175
+ self.draw_calls.append(("rect", color, x, y, w, h))
171
176
 
172
177
  def draw_circle(self, color, pos, radius):
173
- self.draw_calls.append(("circle", color, pos, radius))
174
-
175
- def draw_sprite(self, sprite):
176
- self.draw_calls.append(("sprite", sprite))
177
-
178
- # === main loop ===
179
- def run(self, update_func=None):
180
- while self.running:
181
- for event in pygame.event.get():
182
- if event.type == pygame.QUIT:
183
- self.running = False
184
- elif event.type == pygame.KEYDOWN:
185
- self.keys[event.key] = True
186
- elif event.type == pygame.KEYUP:
187
- self.keys[event.key] = False
188
- elif event.type == pygame.MOUSEBUTTONDOWN:
189
- self.mouse_pressed = pygame.mouse.get_pressed()
190
- elif event.type == pygame.MOUSEBUTTONUP:
191
- self.mouse_pressed = pygame.mouse.get_pressed()
192
- elif event.type == pygame.MOUSEMOTION:
193
- self.mouse_pos = event.pos
194
-
178
+ x, y = pos
179
+ self.draw_calls.append(("circle", color, x, y, radius))
180
+
181
+ def draw_text(self, text, font="Arial", size=16, color=(255, 255, 255), pos=(0, 0), **kwargs):
182
+ fill = f"#{color[0]:02x}{color[1]:02x}{color[2]:02x}"
183
+ self.canvas.create_text(pos[0], pos[1], text=text, fill=fill, font=(font, size), anchor="nw", **kwargs)
184
+
185
+ def draw_text_later(self, *args, **kwargs):
186
+ self.draw_calls.append(("text", args, kwargs))
187
+
188
+ # === Update + Loop ===
189
+ def run(self, update_func=None, bg=(20, 20, 50)):
190
+ self._bg_color = f"#{bg[0]:02x}{bg[1]:02x}{bg[2]:02x}"
191
+ def loop():
192
+ if not self.running:
193
+ return
194
+ self.fill(self._bg_color)
195
195
  if update_func:
196
196
  update_func(self)
197
-
198
- self.screen.fill((20,20,50))
199
197
  for call in self.draw_calls:
200
- if call[0] == "rect":
201
- pygame.draw.rect(self.screen, call[1], call[2])
202
- elif call[0] == "circle":
203
- pygame.draw.circle(self.screen, call[1], call[2], call[3])
204
- elif call[0] == "sprite":
205
- call[1].draw(self.screen)
198
+ ctype = call[0]
199
+ if ctype == "rect":
200
+ _, color, x, y, w, h = call
201
+ col = f"#{color[0]:02x}{color[1]:02x}{color[2]:02x}"
202
+ self.canvas.create_rectangle(x, y, x + w, y + h, fill=col, outline="")
203
+ elif ctype == "circle":
204
+ _, color, x, y, r = call
205
+ col = f"#{color[0]:02x}{color[1]:02x}{color[2]:02x}"
206
+ self.canvas.create_oval(x - r, y - r, x + r, y + r, fill=col, outline="")
207
+ elif ctype == "text":
208
+ self.draw_text(*call[1], **call[2])
206
209
  self.draw_calls.clear()
207
-
208
- pygame.display.flip()
209
- self.clock.tick(60)
210
-
211
- pygame.quit()
210
+ self.root.after(16, loop) # ~60fps
211
+ loop()
212
+ self.root.mainloop()
213
+
214
+ def quit(self):
215
+ print(f"Bye Bye!, Future Dev!. quitting '{main_file}'......")
216
+ self.running = False
217
+ self.root.destroy()
218
+ sys.exit()