PGTKernelBasic 0.22.0__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.
Kernel/KernelEvent.py ADDED
@@ -0,0 +1,129 @@
1
+ import pygame
2
+ from functools import partial
3
+
4
+ from Kernel.KernelWidget import MainScreen
5
+ from Kernel.VFlags import *
6
+
7
+
8
+ def _handle_resize(self: "EventDispatcher"):
9
+ self.screen.blank()
10
+ for widget in list(reversed(self.screen.child.values())):
11
+ widget.dispatch_resize()
12
+ def _handle_quit(self: "EventDispatcher"):
13
+ return False
14
+ mouse_event2flags = {
15
+ pygame.MOUSEBUTTONUP : {
16
+ 1: Uplclick,
17
+ 2: Upscrollmouse,
18
+ 3: Uprclick,
19
+ 4: Upscrollup,
20
+ 5: Upscrolldown,
21
+ },
22
+ pygame.MOUSEBUTTONDOWN : {
23
+ 1 : Downlclick,
24
+ 2 : Downscrollmouse,
25
+ 3 : Downrclick,
26
+ 4 : Downscrollup,
27
+ 5 : Downscrolldown,
28
+ }
29
+ }
30
+ special_event_to_handle = {
31
+ pygame.VIDEORESIZE: _handle_resize,
32
+ pygame.QUIT: _handle_quit,
33
+ }
34
+ def extra_parameter(handler, *args, **kwargs):
35
+ return partial(handler, *args, **kwargs)
36
+ class EventDispatcher:
37
+ def __init__(self, screen: MainScreen):
38
+ self.screen = screen
39
+ self.current_hovered = None
40
+ self.event = None
41
+ self.focused_widget = set()
42
+ self.pressed_key = set()
43
+ def event_passdown(self):
44
+ mouse_pos = pygame.mouse.get_pos()
45
+ for event in pygame.event.get():
46
+ self.event = event
47
+ match event.type:
48
+ case pygame.MOUSEBUTTONDOWN:
49
+ self._dispatch_mousebuttondown(mouse_pos, event)
50
+ case pygame.MOUSEBUTTONUP:
51
+ self._dispatch_mousebuttonup(mouse_pos, event)
52
+ case pygame.VIDEORESIZE | pygame.QUIT:
53
+ result_func = special_event_to_handle[event.type]
54
+ is_off = result_func(self)
55
+ if is_off is False:
56
+ return False
57
+ case pygame.TEXTINPUT:
58
+ for widget in self.focused_widget:
59
+ if hasattr(widget, 'process_addchar'):
60
+ widget.process_addchar(event.text)
61
+ case pygame.KEYDOWN:
62
+ self.pressed_key.add(event.key)
63
+ self._dispatch_key(event)
64
+ case pygame.KEYUP:
65
+ self.pressed_key.discard(event.key)
66
+ self._dispacth_hover(mouse_pos)
67
+ return True
68
+ def _dispatch_key(self, event):
69
+ match event.key:
70
+ case pygame.K_BACKSPACE:
71
+ if event.scancode == 0:
72
+ return
73
+ for widget in self.focused_widget:
74
+ if hasattr(widget, 'process_backspace'):
75
+ widget.process_backspace()
76
+ def _dispacth_hover(self, mouse_pos):
77
+ self.event = None
78
+ new_hovered = None
79
+ for widget in list(reversed(self.screen.child.values())):
80
+ if hasattr(widget, 'is_hovered') and widget.inrect(mouse_pos):
81
+ new_hovered = widget
82
+ break
83
+
84
+ if new_hovered != self.current_hovered:
85
+ if self.current_hovered:
86
+ self.current_hovered.dispatch_release(mouse_pos)()
87
+ self.current_hovered.is_hovered = False
88
+
89
+ if new_hovered:
90
+ new_hovered.dispatch_hover(mouse_pos)()
91
+ new_hovered.is_hovered = True
92
+
93
+ self.current_hovered = new_hovered
94
+ def _dispatch_mousebuttondown(self, mouse_pos, event):
95
+ for widget in list(reversed(self.screen.child.values())):
96
+ if widget.inrect(mouse_pos):
97
+ self.screen.focused = False
98
+ if hasattr(widget, 'dispatch_click'):
99
+ func = widget.dispatch_click(mouse_pos, event)
100
+ func()
101
+ return
102
+ self.screen.focused = True
103
+ def _dispatch_mousebuttonup(self, mouse_pos, event):
104
+ if not self.screen.focused:
105
+ for widget in list(reversed(self.screen.child.values())):
106
+ if widget.inrect(mouse_pos):
107
+ for w in list(self.focused_widget):
108
+ w.focused = False
109
+ self.focused_widget.clear()
110
+
111
+ widget.focused = True
112
+ self.focused_widget.add(widget)
113
+ if hasattr(widget, 'process_addchar'):
114
+ pygame.key.start_text_input()
115
+ else:
116
+ pygame.key.stop_text_input()
117
+ if hasattr(widget, 'dispatch_click'):
118
+ func = widget.dispatch_click(mouse_pos, event)
119
+ func()
120
+
121
+ return
122
+ else:
123
+ for w in list(self.focused_widget):
124
+ w.focused = False
125
+ if hasattr(w, 'process_addchar'):
126
+ pygame.key.stop_text_input()
127
+ self.focused_widget.clear()
128
+ def is_key_pressed(self, key_code):
129
+ return key_code in self.pressed_key
Kernel/KernelLayout.py ADDED
@@ -0,0 +1,58 @@
1
+ from Kernel.KernelWidget import Widget, ImmutableRect
2
+ from Kernel.ObjType import PosTuple
3
+
4
+
5
+ class GridLayout:
6
+ def __init__(self, width_grid: int, height_grid: int, size: PosTuple, pos: PosTuple | None = None, padding=20):
7
+ self.width_grid = width_grid
8
+ self.height_grid = height_grid
9
+ self.total_width, self.total_height = size
10
+ self.pos = pos if pos is not None else (0, 0)
11
+ self.padding = padding
12
+
13
+ available_width = self.total_width - (2 * padding)
14
+ available_height = self.total_height - (2 * padding)
15
+
16
+ self.cell_width = available_width / width_grid
17
+ self.cell_height = available_height / height_grid
18
+ self.edge_cell = (self.cell_width, self.cell_height)
19
+
20
+ self.cells = []
21
+
22
+ cur_pos_x = self.pos[0] + padding
23
+
24
+ for i in range(width_grid):
25
+ row_cells = []
26
+ cur_pos_y = self.pos[1] + padding
27
+
28
+ for j in range(height_grid):
29
+ cell = (cur_pos_x, cur_pos_y)
30
+ row_cells.append(cell)
31
+ cur_pos_y += self.cell_height
32
+
33
+ self.cells.append(row_cells)
34
+ cur_pos_x += self.cell_width
35
+
36
+ def setpos(self, widget: Widget, cell_pos=tuple[int, int], center = True):
37
+ xpos, ypos = cell_pos
38
+ cell_x, cell_y = self.cells[xpos][ypos]
39
+ w, h = widget.rect.w, widget.rect.h
40
+
41
+ if center:
42
+ final_x = cell_x + (self.cell_width - w) / 2
43
+ final_y = cell_y + (self.cell_height - h) / 2
44
+ else:
45
+ final_x = cell_x
46
+ final_y = cell_y
47
+ if isinstance(widget.rect, ImmutableRect):
48
+ widget.rect = ImmutableRect(final_x, final_y, w, h)
49
+ else:
50
+ widget.rect.x = final_x
51
+ widget.rect.y = final_y
52
+
53
+ class HorizontalLayout(GridLayout):
54
+ def __init__(self, length : int, size: PosTuple, pos : PosTuple, padding=20):
55
+ super().__init__(length, 1, size, pos, padding)
56
+ class VerticalLayout(GridLayout):
57
+ def __init__(self, length: int, size: PosTuple, pos: PosTuple, padding=20):
58
+ super().__init__(1, length, size, pos, padding)
@@ -0,0 +1,231 @@
1
+ #Kernel 1(build 0.09)
2
+ #Pre-alpha
3
+ from typing import Tuple, Optional
4
+ from Kernel.KernalInit import init
5
+ from Kernel.ObjType import PosTuple, RectTuple
6
+ init()
7
+ #####
8
+ #Margin
9
+ #####
10
+ class Anchor:
11
+ topleft = 'TopLeft'
12
+ topcenter = 'TopCenter'
13
+ topright = 'TopRight'
14
+ centerleft = 'CenterLeft'
15
+ center = 'Center'
16
+ centerright = 'CenterRight'
17
+ bottomleft = 'BottomLeft'
18
+ bottomcenter = 'BottomCenter'
19
+ bottomright = 'BottomRight'
20
+ class Margin:
21
+ """
22
+ Manages margins and the display position of objects on the screen.
23
+ This class supports:
24
+ - Calculating padding based on percentage or absolute value.
25
+ - Storing default anchors to position objects.
26
+ - Caching calculated position results to improve performance.
27
+ - Updating when the screen size changes.
28
+ - Returning the content area (content_rect) after padding is subtracted.
29
+ Attributes
30
+ width_screen : int
31
+ Current screen width.
32
+ height_screen : int
33
+ Current screen height.
34
+ padding : tuple[float, float]
35
+ Padding by pixels (x, y).
36
+ percentage : tuple[float, float]
37
+ Padding by percentage (0–100).
38
+ cache : dict
39
+ Stores default anchors.
40
+ cache_pos : dict
41
+ Caches the calculated position for the object.
42
+ Methods:
43
+ get_pos(obj, anchor):
44
+ Calculates the (x, y) position of the object based on the anchor.
45
+ Save_margin(anchor):
46
+ Saves the default anchor for later use.
47
+ Update_on_resize(screen):
48
+ Updates screen size and padding when resizing.
49
+ content_rect:
50
+ Returns the remaining content area after subtracting padding.
51
+ """
52
+ __slots__ = ('width_screen', 'height_screen', 'last_screen_size', 'padding', 'percentage', 'cache', 'cache_pos')
53
+ def __init__(self, screen, percentage_padding: PosTuple | None = None, padding: PosTuple | None = (0, 0)):
54
+ self.width_screen, self.height_screen = screen.get_size()
55
+ self.percentage = percentage_padding
56
+ if self.percentage:
57
+ if 0 < percentage_padding[0] > 100 or 0 < percentage_padding[1] > 100:
58
+ raise ValueError('Your padding must in range from 0 to 100')
59
+ self.padding = (self.width_screen * self.percentage[0] / 100, self.height_screen * self.percentage[1] / 100)
60
+ self.padding = padding
61
+ self.cache = {}
62
+ self.cache_pos = {}
63
+ def update_padding(self, new_padding):
64
+ self.padding = new_padding
65
+ def get_pos(self, obj: PosTuple, anchor: str | None):
66
+ if not anchor:
67
+ if 'Anchor' in self.cache:
68
+ anchor = self.cache['Anchor']
69
+
70
+ w_o, h_o = obj
71
+ if (w_o, h_o, anchor) in self.cache_pos:
72
+ return self.cache_pos[(w_o, h_o, anchor)]
73
+
74
+ w_s, h_s = self.width_screen, self.height_screen
75
+ b_x, b_y = self.padding
76
+ left = b_x
77
+ center_x = (w_s - w_o) // 2
78
+ right = w_s - w_o - b_x
79
+ top = b_y
80
+ center_y = (h_s - h_o) // 2
81
+ bottom = h_s - h_o - b_y
82
+ match anchor:
83
+ case 'TopLeft':
84
+ pos = (left, top)
85
+ case 'TopCenter':
86
+ pos = (center_x, top)
87
+ case 'TopRight':
88
+ pos = (right, top)
89
+ case 'CenterLeft':
90
+ pos = (left, center_y)
91
+ case 'Center':
92
+ pos = (center_x, center_y)
93
+ case 'CenterRight':
94
+ pos = (right, center_y)
95
+ case 'BottomLeft':
96
+ pos = (left, bottom)
97
+ case 'BottomCenter':
98
+ pos = (center_x, bottom)
99
+ case 'BottomRight':
100
+ pos = (right, bottom)
101
+ case _: # Trường hợp mặc định (tương đương else)
102
+ raise KeyError(f'Invalid anchor: {anchor}')
103
+
104
+ self.cache_pos[(w_o, h_o, anchor)] = pos
105
+ return pos
106
+ def save_margin(self, anchor: str):
107
+ if anchor in {
108
+ 'CenterRight': 0,
109
+ 'Center': 0,
110
+ 'CenterLeft' : 0,
111
+ 'TopCenter': 0,
112
+ 'TopLeft' : 0,
113
+ 'TopRight': 0,
114
+ 'BottomCenter': 0,
115
+ 'BottomLeft': 0,
116
+ 'BottomRight': 0
117
+ }:
118
+ self.cache['Anchor'] = anchor
119
+ else:
120
+ raise KeyError(f'Invalid anchor: {anchor}')
121
+ def update_on_resize(self, screen):
122
+ self.width_screen, self.height_screen = screen.get_size()
123
+ if self.percentage:
124
+ self.padding = (self.width_screen * self.percentage[0] / 100,
125
+ self.height_screen * self.percentage[1] / 100)
126
+ self.cache_pos.clear()
127
+
128
+ @property
129
+ def content_rect(self):
130
+ left, top = self.padding
131
+ width = self.width_screen - 2 * self.padding[0]
132
+ height = self.height_screen - 2 * self.padding[1]
133
+ width = max(0, width)
134
+ height = max(0, height)
135
+ return left, top, width, height
136
+
137
+ ######
138
+ #NEXT
139
+ ######
140
+ class LayoutHelper:
141
+ """
142
+ Docstring for LayoutHelper:
143
+ A utility class to calculate the position of a new object relative to an existing object's rectangle.
144
+ It supports positioning in four directions: 'Right', 'Left', 'Down', and 'Up', with optional padding.
145
+ Attributes:
146
+ screen_w : int
147
+ Current screen width.
148
+ screen_h : int
149
+ Current screen height.
150
+ Methods:
151
+ update_screen(screen):
152
+ Updates the stored screen dimensions.
153
+ Get_pos(obj_rect, next_obj_size, direction, padding=(0, 0)):
154
+ Calculates the position for the new object based on the specified direction and padding.
155
+ Getpos_up(obj_rect, next_obj_size, padding=(0, 0)):
156
+ Calculates the position above the existing object.
157
+ Getpos_down(obj_rect, next_obj_size, padding=(0, 0)):
158
+ Calculates the position below the existing object.
159
+ Getpos_right(obj_rect, next_obj_size, padding=(0, 0)):
160
+ Calculates the position to the right of the existing object.
161
+ Getpos_left(obj_rect, next_obj_size, padding=(0, 0)):
162
+ Calculates the position to the left of the existing object.
163
+ """
164
+ __slots__ = ('screen_w', 'screen_h')
165
+ def __init__(self, screen):
166
+ self.screen_w, self.screen_h = screen.get_size()
167
+ def update_screen(self, screen):
168
+ self.screen_w, self.screen_h = screen.get_size()
169
+
170
+ def get_pos(self, obj_rect: RectTuple, next_obj_size: PosTuple, direction, padding=(0, 0)) -> tuple[float, float]:
171
+ ox, oy, ow, oh = obj_rect
172
+ nw, nh = next_obj_size
173
+ px, py = padding
174
+ sw, sh = self.screen_w, self.screen_h
175
+
176
+ match direction:
177
+ case 'Right':
178
+ x, y = ox + ow + px, oy + py
179
+ case 'Left':
180
+ x, y = ox - px - nw, oy + py
181
+ case 'Down':
182
+ x, y = ox + px, oy + oh + py
183
+ case 'Up':
184
+ x, y = ox + px, oy - py - nh
185
+ case _:
186
+ raise KeyError('Invalid direction')
187
+
188
+ if x < 0 or y < 0 or (x + nw > sw) or (y + nh > sh):
189
+ raise ValueError('Out of screen')
190
+
191
+ return x, y
192
+ def getpos_up(self, obj_rect: RectTuple, next_obj_size: PosTuple, padding=(0, 0))-> tuple[float, float]:
193
+ ox, oy, ow, oh = obj_rect
194
+ nw, nh = next_obj_size
195
+ px, py = padding
196
+ sw, sh = self.screen_w, self.screen_h
197
+ x = ox + px
198
+ y = oy - py - nh
199
+ if y < 0 or x > sw - nw or x < 0:
200
+ raise ValueError('Out of screen')
201
+ return x, y
202
+ def getpos_down(self, obj_rect: RectTuple, next_obj_size: PosTuple, padding=(0, 0))-> tuple[float, float]:
203
+ ox, oy, ow, oh = obj_rect
204
+ nw, nh = next_obj_size
205
+ px, py = padding
206
+ sw, sh = self.screen_w, self.screen_h
207
+ x = ox + px
208
+ y = oy + oh + py
209
+ if y > sh - nh or x > sw - nw or x < 0:
210
+ raise ValueError('Out of screen')
211
+ return x, y
212
+ def getpos_right(self, obj_rect: RectTuple, next_obj_size: PosTuple, padding=(0, 0))-> tuple[float, float]:
213
+ ox, oy, ow, oh = obj_rect
214
+ nw, nh = next_obj_size
215
+ px, py = padding
216
+ sw, sh = self.screen_w, self.screen_h
217
+ x = ox + ow + px
218
+ y = oy + py
219
+ if x + nw > sw or y + nh > sh or y < 0:
220
+ raise ValueError('Out of screen')
221
+ return x, y
222
+ def getpos_left(self, obj_rect: RectTuple, next_obj_size: PosTuple, padding=(0, 0))-> tuple[float, float]:
223
+ ox, oy, ow, oh = obj_rect
224
+ nw, nh = next_obj_size
225
+ px, py = padding
226
+ sw, sh = self.screen_w, self.screen_h
227
+ x = ox - px - nw
228
+ y = oy + py
229
+ if x < 0 or y > sh - nh or y < 0:
230
+ raise ValueError('Out of screen')
231
+ return x, y
Kernel/KernelRun.py ADDED
@@ -0,0 +1,81 @@
1
+ from typing import Union
2
+
3
+ from collections.abc import Callable
4
+ import concurrent.futures
5
+ import pygame
6
+
7
+ from Kernel import PygameRender, MainScreen, EventDispatcher
8
+
9
+ quitnow = pygame.QUIT
10
+ class BreakThread(Exception):
11
+ def __init__(self, message):
12
+ super().__init__(message)
13
+ class Thread:
14
+ def __init__(self, screen: MainScreen, functions: list,
15
+ quitcondition: int | Callable[..., bool] | None = pygame.QUIT, fps: int| float=60):
16
+ self.functions = functions
17
+ self.quitcondition = quitcondition
18
+ self.running = True
19
+ self.fps = fps
20
+ self.clock = pygame.time.Clock()
21
+ self.break_requested = False
22
+ self.event_manager = EventDispatcher(screen)
23
+ self.dt = 1.0 / fps if fps > 0 else 0.016
24
+ def _loop_start(self):
25
+ while self.running and not self.break_requested:
26
+ self.dt = self.clock.tick(self.fps) / 1000.0
27
+ if self.quitcondition == pygame.QUIT:
28
+ self.running = self.event_manager.event_passdown()
29
+ if callable(self.quitcondition) and self.quitcondition():
30
+ self.running = False
31
+ for func in self.functions:
32
+ func()
33
+ def threadstart(self):
34
+ if self.quitcondition is not None or self.fps > 0:
35
+ try:
36
+ self._loop_start()
37
+ except BreakThread:
38
+ self.running = False
39
+ else:
40
+ for func in self.functions:
41
+ func()
42
+ self.running = False
43
+ def threadbreak(self):
44
+ self.break_requested = True
45
+ def immediate_break(self):
46
+ self.running = False
47
+ def run_parallel(self, cpu_func, *args):
48
+ with concurrent.futures.ProcessPoolExecutor() as executor:
49
+ future = executor.submit(cpu_func, *args)
50
+ return future
51
+ def check_future(self, future):
52
+ if future.done():
53
+ return future.result()
54
+ return None
55
+ def get_event(self):
56
+ return self.event_manager.event
57
+ class MainApplication(Thread):
58
+ def __init__(self, screen_size, screen_flags=0, screen_bg = (0,0,0),
59
+ fixed = False, quitcondition: int | Callable[..., bool] | None = quitnow,
60
+ fps: int | float=60, render_engine = PygameRender, caption = None, functions = None):
61
+ user_functions = functions if functions is not None else []
62
+ screen = MainScreen(screen_size, screen_flags, screen_bg, fixed)
63
+ screen.set_common_engine(render_engine)
64
+ if caption:
65
+ screen.set_caption(caption)
66
+ super().__init__(screen, user_functions, quitcondition, fps)
67
+ self.screen = screen
68
+ self.functions.append(self._main_render)
69
+ def _main_render(self):
70
+ for widget in self.screen.child.values():
71
+ widget.render()
72
+ if widget.child:
73
+ self._recursive_render(widget)
74
+ pygame.display.flip()
75
+ def add_action(self, func):
76
+ self.functions.append(func)
77
+ def _recursive_render(self, parent_widget):
78
+ for widget in parent_widget.child.values():
79
+ widget.render()
80
+ if widget.child:
81
+ self._recursive_render(widget)