crystalwindow 1.4.7.5__py3-none-any.whl → 2.9.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/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,25 +1,37 @@
1
- import pygame as py
1
+ #Sprites.py
2
+ from tkinter import PhotoImage
2
3
 
3
4
  class Sprite:
4
- def __init__(self, image=None, x=0, y=0, w=50, h=50, color=(255,255,255)):
5
- self.image = image
6
- self.x = x
7
- self.y = y
8
- self.color = color
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
9
14
 
10
- # optional rect if no image
11
- if image:
12
- self.rect = self.image.get_rect(topleft=(x, y))
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
13
23
  else:
14
- self.rect = py.Rect(x, y, w, h)
24
+ raise ValueError("Sprite needs either size or image")
15
25
 
16
- def draw(self, screen):
26
+ def draw(self, win):
27
+ """Draw sprite on given Window"""
17
28
  if self.image:
18
- screen.blit(self.image, (self.x, self.y))
29
+ win.canvas.create_image(self.x, self.y, anchor="nw", image=self.image)
19
30
  else:
20
- py.draw.rect(screen, self.color, self.rect)
31
+ win.draw_rect(self.color, (self.x, self.y, self.width, self.height))
21
32
 
22
33
  def move(self, dx, dy):
34
+ """Move sprite by dx/dy"""
23
35
  self.x += dx
24
36
  self.y += dy
25
- 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.
9
+ Warns every run if outdated.
10
+ Only skips check when version is already latest.
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
+ cache_file = os.path.join(os.path.expanduser("~"), f".{package_name}_version_cache.json")
21
+
22
+ # load cache
23
+ if os.path.exists(cache_file):
24
+ with open(cache_file, "r") as f:
25
+ cache = json.load(f)
26
+ else:
27
+ cache = {}
28
+
29
+ # get newest version from PyPI
30
+ url = f"https://pypi.org/pypi/{package_name}/json"
31
+ response = requests.get(url, timeout=3)
32
+
33
+ if response.status_code == 200:
34
+ latest_version = response.json()["info"]["version"]
35
+
36
+ if latest_version == current_version:
37
+ # only print once per version
38
+ if cache.get("last_checked") != current_version:
39
+ print(f"✅ Up to date! ver = {current_version}.")
40
+ cache["last_checked"] = current_version
41
+ with open(cache_file, "w") as f:
42
+ json.dump(cache, f)
43
+ else:
44
+ # always warn until updated
45
+ print(f"\n⚠️ Yo dev! '{package_name}' is outdated ({current_version})")
46
+ print(f"👉 Newest is {latest_version}! Run:")
47
+ print(f" pip install --upgrade {package_name}")
48
+ print(f"Or peep: https://pypi.org/project/{package_name}/{latest_version}/\n")
49
+
50
+ else:
51
+ print("(⚠️ PyPI request failed, skipping version check)")
52
+
53
+ except Exception as e:
54
+ print(f"(⚠️ Version check failed: {e})")
crystalwindow/window.py CHANGED
@@ -1,203 +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
 
12
+ def decode_logo():
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
+
10
22
  class Window:
11
- # friendly string -> pygame constant mapping
23
+ # === Keymap to simplify key usage ===
12
24
  KEY_MAP = {
13
- "left": pygame.K_LEFT,
14
- "right": pygame.K_RIGHT,
15
- "up": pygame.K_UP,
16
- "down": pygame.K_DOWN,
17
- "space": pygame.K_SPACE,
18
- "enter": pygame.K_RETURN,
19
- "backspace": pygame.K_BACKSPACE,
20
- "tab": pygame.K_TAB,
21
- "escape": pygame.K_ESCAPE,
22
- "Rshift": pygame.K_LSHIFT,
23
- "Lctrl": pygame.K_LCTRL,
24
- "Rshift": pygame.K_RSHIFT,
25
- "Rctrl": pygame.K_RCTRL,
26
- "alt": pygame.K_LALT,
27
- "keya": pygame.K_a,
28
- "keyb": pygame.K_b,
29
- "keyc": pygame.K_c,
30
- "keyd": pygame.K_d,
31
- "keye": pygame.K_e,
32
- "keyf": pygame.K_f,
33
- "keyg": pygame.K_g,
34
- "keyh": pygame.K_h,
35
- "keyi": pygame.K_i,
36
- "keyj": pygame.K_j,
37
- "keyk": pygame.K_k,
38
- "keyl": pygame.K_l,
39
- "keym": pygame.K_m,
40
- "keyn": pygame.K_n,
41
- "keyo": pygame.K_o,
42
- "keyp": pygame.K_p,
43
- "keyq": pygame.K_q,
44
- "keyr": pygame.K_r,
45
- "keys": pygame.K_s,
46
- "keyt": pygame.K_t,
47
- "keyu": pygame.K_u,
48
- "keyv": pygame.K_v,
49
- "keyw": pygame.K_w,
50
- "keyx": pygame.K_x,
51
- "keyy": pygame.K_y,
52
- "keyz": pygame.K_z,
53
- "0": pygame.K_0,
54
- "1": pygame.K_1,
55
- "2": pygame.K_2,
56
- "3": pygame.K_3,
57
- "4": pygame.K_4,
58
- "5": pygame.K_5,
59
- "6": pygame.K_6,
60
- "7": pygame.K_7,
61
- "8": pygame.K_8,
62
- "9": pygame.K_9,
63
- "f1": pygame.K_F1,
64
- "f2": pygame.K_F2,
65
- "f3": pygame.K_F3,
66
- "f4": pygame.K_F4,
67
- "f5": pygame.K_F5,
68
- "f6": pygame.K_F6,
69
- "f7": pygame.K_F7,
70
- "f8": pygame.K_F8,
71
- "f9": pygame.K_F9,
72
- "f10": pygame.K_F10,
73
- "f11": pygame.K_F11,
74
- "f12": pygame.K_F12,
75
- "insert": pygame.K_INSERT,
76
- "delete": pygame.K_DELETE,
77
- "home": pygame.K_HOME,
78
- "end": pygame.K_END,
79
- "pageup": pygame.K_PAGEUP,
80
- "pagedown": pygame.K_PAGEDOWN,
81
- "capslock": pygame.K_CAPSLOCK,
82
- "numlock": pygame.K_NUMLOCK,
83
- "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"
84
81
  }
85
82
 
86
-
87
83
  def __init__(self, width=640, height=480, title="Game Window", icon=None):
88
- # temporarily silence pygame print
89
- with open(os.devnull, "w") as f, contextlib.redirect_stdout(f):
90
- pygame.init() # <- no more hello message
91
-
92
84
  self.width = width
93
85
  self.height = height
94
86
  self.title = title
95
- self.screen = pygame.display.set_mode((width, height))
96
- pygame.display.set_caption(title)
97
-
98
- # --- ICON HANDLING ---
99
- if icon:
100
- # user-provided icon logic
101
- if isinstance(icon, str):
102
- icon_path = icon
103
- if not os.path.isabs(icon_path):
104
- icon_path = os.path.join(os.getcwd(), icon)
105
- if os.path.exists(icon_path):
106
- img = pygame.image.load(icon_path).convert_alpha()
107
- pygame.display.set_icon(img)
108
- else:
109
- print(f"⚠️ Icon file not found: {icon_path}")
110
- else:
111
- pygame.display.set_icon(icon)
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")
99
+ img = decode_logo()
100
+ if img: self.root.iconphoto(True, img)
112
101
  else:
113
- default_icon_path = os.path.join(os.path.dirname(__file__), "icons", "default_icon.png")
114
- if os.path.exists(default_icon_path):
115
- img = pygame.image.load(default_icon_path).convert_alpha()
116
- pygame.display.set_icon(img)
117
- else:
118
- icon_bytes = base64.b64decode(DEFAULT_LOGO_BASE64)
119
- icon_file = io.BytesIO(icon_bytes)
120
- img = image.load(icon_file).convert_alpha()
121
- pygame.display.set_icon(img)
102
+ logo = decode_logo()
103
+ if logo:
104
+ self.root.iconphoto(True, logo)
122
105
 
123
- self.clock = pygame.time.Clock()
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)
109
+
110
+ # === State vars ===
124
111
  self.running = True
125
112
  self.keys = {}
126
- self.mouse_pos = (0,0)
127
- 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)
128
116
  self.draw_calls = []
129
117
 
130
- # === input helpers ===
131
- def is_key_pressed(self, key):
132
- # 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):
133
149
  if isinstance(key, str):
134
- key = self.KEY_MAP.get(key, None)
135
- if key is None:
136
- return False
150
+ key = self.KEY_MAP.get(key, key)
137
151
  return self.keys.get(key, False)
138
152
 
139
- def is_mouse_pressed(self, button=1):
140
- 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)
141
172
 
142
- # === font system ===
143
- def get_font(self, name="Arial", size=16, bold=False, italic=False):
144
- if not hasattr(self, "font_cache"):
145
- self.font_cache = {}
146
- key = (name, size, bold, italic)
147
- if key not in self.font_cache:
148
- try:
149
- font = pygame.font.SysFont(name, size, bold, italic)
150
- except:
151
- font = pygame.font.Font(None, size)
152
- self.font_cache[key] = font
153
- return self.font_cache[key]
154
-
155
- def draw_text(self, text, font="Arial", size=16, color=(255,255,255), pos=(0,0), bold=False, italic=False):
156
- f = self.get_font(font, size, bold, italic)
157
- surf = f.render(text, True, color)
158
- self.screen.blit(surf, pos)
159
-
160
- # === drawing helpers ===a
161
173
  def draw_rect(self, color, rect):
162
- self.draw_calls.append(("rect", color, rect))
174
+ x, y, w, h = rect
175
+ self.draw_calls.append(("rect", color, x, y, w, h))
163
176
 
164
177
  def draw_circle(self, color, pos, radius):
165
- self.draw_calls.append(("circle", color, pos, radius))
166
-
167
- def draw_sprite(self, sprite):
168
- self.draw_calls.append(("sprite", sprite))
169
-
170
- # === main loop ===
171
- def run(self, update_func=None):
172
- while self.running:
173
- for event in pygame.event.get():
174
- if event.type == pygame.QUIT:
175
- self.running = False
176
- elif event.type == pygame.KEYDOWN:
177
- self.keys[event.key] = True
178
- elif event.type == pygame.KEYUP:
179
- self.keys[event.key] = False
180
- elif event.type == pygame.MOUSEBUTTONDOWN:
181
- self.mouse_pressed = pygame.mouse.get_pressed()
182
- elif event.type == pygame.MOUSEBUTTONUP:
183
- self.mouse_pressed = pygame.mouse.get_pressed()
184
- elif event.type == pygame.MOUSEMOTION:
185
- self.mouse_pos = event.pos
186
-
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)
187
195
  if update_func:
188
196
  update_func(self)
189
-
190
- self.screen.fill((20,20,50))
191
197
  for call in self.draw_calls:
192
- if call[0] == "rect":
193
- pygame.draw.rect(self.screen, call[1], call[2])
194
- elif call[0] == "circle":
195
- pygame.draw.circle(self.screen, call[1], call[2], call[3])
196
- elif call[0] == "sprite":
197
- 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])
198
209
  self.draw_calls.clear()
199
-
200
- pygame.display.flip()
201
- self.clock.tick(60)
202
-
203
- 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()