Skip to content

Commit 902d39b

Browse files
committed
Add parent-child support and rendering overriding
* Add parent-child support for Panel components * Add rendering overriding * Update button child component rendering
1 parent 34255d9 commit 902d39b

File tree

2 files changed

+80
-52
lines changed

2 files changed

+80
-52
lines changed

examples/test.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,6 @@ def toggle_velocity(event):
3131
if event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE:
3232
current_velocity = [0, 0] if current_velocity != [0, 0] else VELOCITY
3333

34-
def toggle_ui(event):
35-
if event.type == pygame.KEYDOWN and event.key == pygame.K_TAB:
36-
for ge in Canvas.graphic_elements:
37-
ge.visible = not ge.visible
38-
3934
def main():
4035
pygame.init()
4136

@@ -46,16 +41,23 @@ def main():
4641

4742
circle = Circle(Vector2(20, 20), 20, Color.RED)
4843

44+
def toggle_ui(event):
45+
if event.type == pygame.KEYDOWN and event.key == pygame.K_TAB:
46+
panel.visible = not panel.visible
47+
4948
def toggle_color(on):
49+
main_panel.visible = not main_panel.visible
5050
circle.color = Color.BLUE if on else Color.RED
5151

52+
main_panel = Panel()
5253
panel = Panel(Alignment.get_center_pos(main_surface.get_size(), Vector2(200, 200)), size=Vector2(200, 200), color=Color.GREY)
54+
panel.set_parent(main_panel)
5355
fps_text = Label(position=Vector2(WINDOW_SIZE[0]-25, 20), color=Color.BLACK, anchor=Alignment.MID_RIGHT)
5456
score_text = Label(font_size=60, text=score, position=Vector2(WINDOW_SIZE[0] - 25, 40), anchor=Alignment.TOP_RIGHT)
5557
button = Button(position=Vector2(25, 25), label=Label("Button", Color.BLACK, font_size=40), on_click=increment_score, disabled=False, label_alignment=Alignment.CENTER)
5658
check_box = CheckBox(position=Vector2(25, WINDOW_SIZE[1] - 75), on_value_change=toggle_color)
5759
input_box = InputBox(position=Vector2(WINDOW_SIZE[0] - 200, WINDOW_SIZE[1] - 75), on_submit=set_score)
58-
button2 = Button(image=UiImage(file_name=os.path.abspath("examples\\images\\Present_64px.png")), size=Vector2(150, 150), position=Vector2(WINDOW_SIZE[0]/2 - 75, WINDOW_SIZE[1]/2 - 75), on_click=increment_score)
60+
button2 = Button(image=UiImage(file_name=os.path.abspath("examples\\images\\Present_64px.png")), size=Vector2(150, 150), position=Vector2(WINDOW_SIZE[0]/2 - 75, WINDOW_SIZE[1]/2 - 75), on_click=increment_score, parent=panel)
5961

6062
EventManager.set_events([toggle_velocity, toggle_ui], on_quit=lambda: JsonSave.save(SAVE_FILE, "Score", score))
6163

utils/ui.py

Lines changed: 72 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ class Alignment:
3636
MID_RIGHT = "midright"
3737
CENTER = "center"
3838

39+
@staticmethod
3940
def get_center_pos(parent_size: tuple[int, int] | Vector2, element_size: tuple[int, int] | Vector2):
4041
return Vector2(parent_size[0]/2 - element_size[0]/2, parent_size[1]/2 - element_size[1]/2)
4142

@@ -93,8 +94,9 @@ def handle_events(cls) -> None:
9394

9495

9596
class Graphic:
96-
def __init__(self) -> None:
97+
def __init__(self, parent: "Panel"=None) -> None:
9798
self._visible = True
99+
self.set_parent(parent)
98100
Canvas.add_managed_object(self)
99101

100102
@property
@@ -105,6 +107,20 @@ def visible(self):
105107
def visible(self, value):
106108
self._visible = value
107109

110+
@property
111+
def is_rendered(self):
112+
if self.parent:
113+
return self.visible and self.parent.visible
114+
return self.visible
115+
116+
def _override_rendering(self):
117+
Canvas.remove_managed_object(self)
118+
119+
def set_parent(self, parent: "Panel"):
120+
self.parent = parent
121+
if parent:
122+
parent.add_child_element(self)
123+
108124
@abstractmethod
109125
def draw(self, surface: pygame.Surface):
110126
...
@@ -129,6 +145,10 @@ def update_managed_objects(cls, graphic_elements: list[Graphic]):
129145
@classmethod
130146
def add_managed_object(cls, graphic: Graphic):
131147
cls.graphic_elements.append(graphic)
148+
149+
@classmethod
150+
def remove_managed_object(cls, graphic: Graphic):
151+
cls.graphic_elements.remove(graphic)
132152

133153
@classmethod
134154
def draw(cls) -> None:
@@ -137,17 +157,52 @@ def draw(cls) -> None:
137157
if not cls.main_surface:
138158
cls.main_surface = pygame.display.get_surface()
139159
for graphic in cls.graphic_elements:
140-
if graphic.visible:
160+
if graphic.is_rendered:
141161
graphic.draw(cls.main_surface)
142162

143163

164+
class Panel(Graphic):
165+
def __init__(self, position: Vector2=Vector2(0, 0), size: Vector2=None,
166+
color: pygame.Color | tuple[int, int, int, int]=(80, 80, 80, 100)) -> None:
167+
super().__init__()
168+
self.position = position
169+
self.size = size
170+
self.color = color
171+
self.panel_surface = None
172+
self.child_elements = []
173+
174+
@property
175+
def visible(self):
176+
return self._visible
177+
178+
@visible.setter
179+
def visible(self, value):
180+
self._visible = value
181+
182+
def add_child_element(self, child_element: Graphic):
183+
self.child_elements.append(child_element)
184+
185+
def _render(self, surface: pygame.Surface):
186+
# If size is set to none, it defaults to the screen/surface size
187+
if not self.size:
188+
self.size = surface.get_size()
189+
self.panel_surface = pygame.Surface(self.size, pygame.SRCALPHA)
190+
self.panel_surface.fill(self.color)
191+
192+
def draw(self, surface: pygame.Surface):
193+
if not self.panel_surface:
194+
self._render(surface)
195+
surface.blit(self.panel_surface, self.position)
196+
197+
144198
class UiImage(Graphic):
145199
def __init__(self, image_surface: pygame.surface=None, file_name: str=None, position: Vector2=Vector2(0, 0), size: Vector2=None,
146-
color_tint: pygame.Color | tuple[int, int, int, int]=(255, 255, 255, 255), blend_mode: int=pygame.BLEND_RGBA_MULT) -> None:
200+
color_tint: pygame.Color | tuple[int, int, int, int]=(255, 255, 255, 255), blend_mode: int=pygame.BLEND_RGBA_MULT,
201+
parent: Panel=None) -> None:
147202
"""
148203
Specify either the image surface or the image path, not both.
149204
"""
150-
super().__init__()
205+
super().__init__(parent)
151206
self.position = position
152207
self.size = size
153208
self.color_tint = color_tint
@@ -182,32 +237,11 @@ def draw(self, surface: pygame.Surface):
182237
surface.blit(self.image_surface, self.position)
183238

184239

185-
class Panel(Graphic):
186-
def __init__(self, position: Vector2=Vector2(0, 0), size: Vector2=None,
187-
color: pygame.Color | tuple[int, int, int, int]=(80, 80, 80, 100)) -> None:
188-
super().__init__()
189-
self.position = position
190-
self.size = size
191-
self.color = color
192-
self.panel_surface = None
193-
194-
def _render(self, surface: pygame.Surface):
195-
# If size is set to none, it defaults to the screen/surface size
196-
if not self.size:
197-
self.size = surface.get_size()
198-
self.panel_surface = pygame.Surface(self.size, pygame.SRCALPHA)
199-
self.panel_surface.fill(self.color)
200-
201-
def draw(self, surface: pygame.Surface):
202-
if not self.panel_surface:
203-
self._render(surface)
204-
surface.blit(self.panel_surface, self.position)
205-
206-
207240
class Label(Graphic):
208241
def __init__(self, text: str | Any="", color: pygame.Color | tuple[int, int, int]=(255, 255, 255),
209-
font_name: str=None, font_size: int=28, position: Vector2=Vector2(0, 0), anchor: Alignment | str="midleft") -> None:
210-
super().__init__()
242+
font_name: str=None, font_size: int=28, position: Vector2=Vector2(0, 0), anchor: Alignment | str="midleft",
243+
parent: Panel=None) -> None:
244+
super().__init__(parent)
211245
self.font = pygame.font.Font(font_name, font_size)
212246
self.text = str(text)
213247
self.color = color
@@ -257,11 +291,13 @@ def __init__(self, image: UiImage=None, on_click: Callable=None,
257291
position: Vector2=Vector2(0, 0), size: Vector2=Vector2(150, 75),
258292
color: pygame.Color | tuple[int, int, int]=(255, 255, 255), hover_color: pygame.Color | tuple[int, int, int]=(220, 220, 220),
259293
pressed_color: pygame.Color | tuple[int, int, int]=(185, 185, 185), disabled_color: pygame.Color | tuple[int, int, int]=(165, 165, 165),
260-
border_radius: int=0, disabled: bool=False, label: Label=None, label_alignment: Alignment | str="center") -> None:
261-
Graphic.__init__(self)
294+
border_radius: int=0, disabled: bool=False, label: Label=None, label_alignment: Alignment | str="center",
295+
parent: Panel=None) -> None:
296+
Graphic.__init__(self, parent)
262297
Graphic_Event.__init__(self)
263298
self.button_image = image
264299
if self.button_image:
300+
self.button_image._override_rendering()
265301
self.button_image.position = position
266302
self.button_image.size = size
267303
self.button_image.scale_image()
@@ -282,21 +318,10 @@ def __init__(self, image: UiImage=None, on_click: Callable=None,
282318
self.label = label
283319
self.label_alignment = label_alignment
284320
if self.label:
321+
self.label._override_rendering()
285322
self.label._render()
286323
self.set_label_pos()
287324

288-
@property
289-
def visible(self):
290-
return self._visible
291-
292-
@visible.setter
293-
def visible(self, value):
294-
if self.button_image:
295-
self.button_image.visible = value
296-
if self.label:
297-
self.label.visible = value
298-
self._visible = value
299-
300325
def set_color(self, color: pygame.Color | tuple[int, int, int]):
301326
self.current_color = color
302327
if self.button_image:
@@ -395,9 +420,9 @@ def __init__(self, on_value_change: Callable=None,
395420
color: pygame.Color | tuple[int, int, int]=(255, 255, 255), hover_color: pygame.Color | tuple[int, int, int]=(220, 220, 220),
396421
pressed_color: pygame.Color | tuple[int, int, int]=(185, 185, 185), disabled_color: pygame.Color | tuple[int, int, int]=(165, 165, 165),
397422
tick_color: pygame.Color | tuple[int, int, int]=(55, 55, 55), is_on: bool=False,
398-
border_radius: int=0, disabled: bool=False, label_alignment: Alignment | str="midleft") -> None:
423+
border_radius: int=0, disabled: bool=False, label_alignment: Alignment | str="midleft", parent: Panel=None) -> None:
399424
super().__init__(None, on_value_change, position, size, color, hover_color, pressed_color, disabled_color,
400-
border_radius, disabled, None, label_alignment)
425+
border_radius, disabled, None, label_alignment, parent)
401426
self.tick_color = tick_color
402427
self.is_on = is_on
403428
self.tick_rect = pygame.Rect(Vector2(position.x + size.x/4, position.y + size.y/4), Vector2(size.x / 2, size.y / 2))
@@ -419,8 +444,9 @@ def __init__(self, on_value_change: Callable=None, on_delete: Callable=None,
419444
box_color: pygame.Color | tuple[int, int, int]=(255, 255, 255), border_thickness: int=2,
420445
border_color_active: pygame.Color | tuple[int, int, int]=(20, 20, 20),
421446
border_color_inactive: pygame.Color | tuple[int, int, int]=(100, 100, 100),
422-
text_color: pygame.Color | tuple[int, int, int]=(20, 20, 20), text: str | Any="", font_name: str=None, font_size: int=28):
423-
super().__init__(text, text_color, font_name, font_size, position)
447+
text_color: pygame.Color | tuple[int, int, int]=(20, 20, 20), text: str | Any="", font_name: str=None, font_size: int=28,
448+
parent: Panel=None) -> None:
449+
super().__init__(text, text_color, font_name, font_size, position, parent=parent)
424450
Graphic_Event.__init__(self)
425451
self.rect = pygame.Rect(position, size)
426452
self.position = position

0 commit comments

Comments
 (0)