batframework 1.0.8a8__py3-none-any.whl → 1.0.8a10__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.
Files changed (70) hide show
  1. batFramework/__init__.py +68 -51
  2. batFramework/action.py +126 -99
  3. batFramework/actionContainer.py +53 -9
  4. batFramework/animatedSprite.py +141 -82
  5. batFramework/audioManager.py +69 -26
  6. batFramework/camera.py +259 -69
  7. batFramework/character.py +27 -0
  8. batFramework/constants.py +16 -54
  9. batFramework/cutscene.py +39 -29
  10. batFramework/cutsceneBlocks.py +36 -43
  11. batFramework/dynamicEntity.py +18 -9
  12. batFramework/easingController.py +58 -0
  13. batFramework/entity.py +48 -97
  14. batFramework/enums.py +113 -0
  15. batFramework/fontManager.py +65 -0
  16. batFramework/gui/__init__.py +10 -2
  17. batFramework/gui/button.py +9 -78
  18. batFramework/gui/clickableWidget.py +220 -0
  19. batFramework/gui/constraints/__init__.py +1 -0
  20. batFramework/gui/constraints/constraints.py +815 -0
  21. batFramework/gui/container.py +174 -32
  22. batFramework/gui/debugger.py +131 -43
  23. batFramework/gui/dialogueBox.py +99 -0
  24. batFramework/gui/draggableWidget.py +40 -0
  25. batFramework/gui/image.py +56 -20
  26. batFramework/gui/indicator.py +38 -21
  27. batFramework/gui/interactiveWidget.py +192 -13
  28. batFramework/gui/label.py +309 -74
  29. batFramework/gui/layout.py +231 -63
  30. batFramework/gui/meter.py +74 -0
  31. batFramework/gui/radioButton.py +84 -0
  32. batFramework/gui/root.py +134 -38
  33. batFramework/gui/shape.py +237 -57
  34. batFramework/gui/slider.py +240 -0
  35. batFramework/gui/style.py +10 -0
  36. batFramework/gui/styleManager.py +48 -0
  37. batFramework/gui/textInput.py +247 -0
  38. batFramework/gui/toggle.py +101 -51
  39. batFramework/gui/widget.py +358 -250
  40. batFramework/manager.py +52 -19
  41. batFramework/object.py +123 -0
  42. batFramework/particle.py +115 -0
  43. batFramework/renderGroup.py +67 -0
  44. batFramework/resourceManager.py +100 -0
  45. batFramework/scene.py +281 -123
  46. batFramework/sceneManager.py +178 -116
  47. batFramework/scrollingSprite.py +114 -0
  48. batFramework/sprite.py +51 -0
  49. batFramework/stateMachine.py +11 -8
  50. batFramework/templates/__init__.py +2 -0
  51. batFramework/templates/character.py +44 -0
  52. batFramework/templates/states.py +166 -0
  53. batFramework/tileset.py +46 -0
  54. batFramework/time.py +145 -58
  55. batFramework/transition.py +195 -124
  56. batFramework/triggerZone.py +1 -1
  57. batFramework/utils.py +112 -147
  58. batframework-1.0.8a10.dist-info/LICENCE +21 -0
  59. batframework-1.0.8a10.dist-info/METADATA +43 -0
  60. batframework-1.0.8a10.dist-info/RECORD +62 -0
  61. batFramework/debugger.py +0 -48
  62. batFramework/easing.py +0 -71
  63. batFramework/gui/constraints.py +0 -204
  64. batFramework/gui/frame.py +0 -19
  65. batFramework/particles.py +0 -77
  66. batFramework/transitionManager.py +0 -0
  67. batframework-1.0.8a8.dist-info/METADATA +0 -53
  68. batframework-1.0.8a8.dist-info/RECORD +0 -42
  69. {batframework-1.0.8a8.dist-info → batframework-1.0.8a10.dist-info}/WHEEL +0 -0
  70. {batframework-1.0.8a8.dist-info → batframework-1.0.8a10.dist-info}/top_level.txt +0 -0
@@ -1,81 +1,249 @@
1
1
  import batFramework as bf
2
2
  from .widget import Widget
3
- from .constraints import *
4
- from typing import Self
3
+ from .constraints.constraints import *
4
+ from typing import Self, TYPE_CHECKING,override
5
+ from abc import ABC,abstractmethod
6
+ import pygame
5
7
 
6
- class Layout:
7
- def __init__(self, parent: Widget=None):
8
+ if TYPE_CHECKING:
9
+ from .container import Container
10
+
11
+
12
+ class Layout(ABC):
13
+ def __init__(self, parent: "Container" = None):
8
14
  self.parent = parent
9
- self.child_constraints : list[Constraint] = []
10
-
11
- def set_child_constraints(self,*constraints)->Self:
12
- self.child_constraints = constraints
13
- self.arrange()
15
+ self.child_constraints: list[Constraint] = []
16
+ self.children_rect = pygame.FRect(0, 0, 0, 0)
17
+
18
+ def set_child_constraints(self, *constraints) -> Self:
19
+ self.child_constraints = list(constraints)
20
+ self.notify_parent()
14
21
  return self
15
-
16
- def set_parent(self,parent:Widget):
22
+
23
+ def set_parent(self, parent: Widget):
17
24
  self.parent = parent
18
- self.arrange()
25
+ self.notify_parent()
26
+
27
+ def notify_parent(self) -> None:
28
+ if self.parent:
29
+ self.parent.dirty_children = True
30
+
31
+ def arrange(self) -> None:
32
+ return
33
+
34
+ def get_raw_size(self) -> tuple[float, float]:
35
+ """
36
+ Returns the supposed size the container should have to encapsulate perfectly all of its widgets
37
+ """
38
+ return self.parent.rect.size if self.parent else (0, 0)
39
+
40
+ def get_auto_size(self) -> tuple[float, float]:
41
+ """
42
+ Returns the final size the container should have (while keeping the the width and height if they are non-resizable)
43
+ """
44
+ target_size = list(self.get_raw_size())
45
+ if not self.parent.autoresize_w:
46
+ target_size[0] = self.parent.rect.w
47
+ if not self.parent.autoresize_h:
48
+ target_size[1] = self.parent.rect.h
49
+ return target_size
50
+
51
+ def focus_next_child(self) -> None:
52
+ pass
53
+
54
+ def focus_prev_child(self) -> None:
55
+ pass
56
+
57
+ def scroll_to_widget(self, widget: Widget) -> None:
58
+ padded = self.parent.get_padded_rect()
59
+ r = widget.rect
60
+ if padded.contains(r):
61
+ return
62
+ clamped = r.clamp(padded)
63
+ dx, dy = clamped.move(-r.x, -r.y).topleft
64
+ self.parent.scroll_by((-dx, -dy))
65
+
66
+ def handle_event(self, event):
67
+ pass
68
+
19
69
 
20
- def arrange(self)->None:
21
- raise NotImplementedError("Subclasses must implement arrange method")
70
+ class SingleAxisLayout(Layout):
71
+ def focus_next_child(self) -> None:
72
+ l = self.parent.get_interactive_children()
73
+ self.parent.focused_index = min(self.parent.focused_index + 1, len(l) - 1)
74
+ focused = l[self.parent.focused_index]
75
+ focused.get_focus()
76
+ self.scroll_to_widget(focused)
22
77
 
23
- class Column(Layout):
24
- def __init__(self,gap:int=0,shrink:bool=False):
78
+ def focus_prev_child(self) -> None:
79
+ l = self.parent.get_interactive_children()
80
+ self.parent.focused_index = max(self.parent.focused_index - 1, 0)
81
+ focused = l[self.parent.focused_index]
82
+ focused.get_focus()
83
+ self.scroll_to_widget(focused)
84
+
85
+
86
+ class Column(SingleAxisLayout):
87
+ def __init__(self, gap: int = 0, spacing: bf.spacing = bf.spacing.MANUAL):
25
88
  super().__init__()
26
89
  self.gap = gap
27
- self.shrink :bool = shrink
28
-
29
- def arrange(self)->None:
30
- if not self.parent or not self.parent.children : return
31
- if self.shrink:
32
- len_children = len(self.parent.children)
33
- parent_height = sum(c.rect.h for c in self.parent.children)
34
- parent_width = max(c.rect.w for c in self.parent.children)
35
- if self.gap : parent_height += (len_children-1) * self.gap
36
- # print(self.parent.to_string(),len_children,parent_height)
37
- c = self.parent.get_constraint("height")
38
- if not c or c.height != parent_height :
39
- self.parent.add_constraint(ConstraintHeight(parent_height))
40
- c = self.parent.get_constraint("width")
41
- if not c or c.width != parent_width :
42
- self.parent.add_constraint(ConstraintWidth(parent_width))
43
- current_y = self.parent.rect.top
90
+ self.spacing = spacing
91
+
92
+ def set_gap(self, value: float) -> Self:
93
+ self.gap = value
94
+ self.notify_parent()
95
+ return self
96
+
97
+ def set_spacing(self, spacing: bf.spacing) -> Self:
98
+ self.spacing = spacing
99
+ self.notify_parent()
100
+ return self
44
101
 
102
+ def get_raw_size(self) -> tuple[float, float]:
103
+ len_children = len(self.parent.children)
104
+ if not len_children:
105
+ return self.parent.rect.size
106
+ parent_height = sum(c.get_min_required_size()[1] for c in self.parent.children)
107
+ parent_width = max(c.get_min_required_size()[0] for c in self.parent.children)
108
+ if self.gap:
109
+ parent_height += (len_children - 1) * self.gap
110
+ target_rect = self.parent.inflate_rect_by_padding(
111
+ (0, 0, parent_width, parent_height)
112
+ )
113
+ return target_rect.size
114
+
115
+ def get_auto_size(self) -> tuple[float, float]:
116
+ target_size = list(self.get_raw_size())
117
+ if not self.parent.autoresize_w:
118
+ target_size[0] = self.parent.rect.w
119
+ if not self.parent.autoresize_h:
120
+ target_size[1] = self.parent.rect.h
121
+ return target_size
122
+
123
+ def arrange(self) -> None:
124
+ if not self.parent or not self.parent.children:
125
+ return
126
+ if self.child_constraints:
127
+ for child in self.parent.children:
128
+ child.add_constraints(*self.child_constraints)
129
+ self.child_rect = self.parent.get_padded_rect()
130
+
131
+ if self.parent.autoresize_w or self.parent.autoresize_h:
132
+ width, height = self.get_auto_size()
133
+ if self.parent.rect.size != (width, height):
134
+ self.parent.set_size((width, height))
135
+ self.parent.build()
136
+ self.arrange()
137
+ return
138
+ self.child_rect.move_ip(-self.parent.scroll.x, -self.parent.scroll.y)
139
+ y = self.child_rect.top
45
140
  for child in self.parent.children:
46
- child.set_position(self.parent.rect.x,current_y)
47
- current_y += child.rect.h + self.gap
48
- for c in self.child_constraints:
49
- if not child.has_constraint(c.name):
50
- child.add_constraint(c)
51
-
52
- class Row(Layout):
53
- def __init__(self, gap: int = 0, shrink: bool = False):
141
+ child.set_position(self.child_rect.x, y)
142
+ y += child.get_min_required_size()[1] + self.gap
143
+
144
+ def handle_event(self, event):
145
+ if self.parent.autoresize_h or not self.parent.visible:
146
+ return
147
+
148
+ if not self.parent.children:
149
+ return
150
+
151
+ if event.type == pygame.MOUSEBUTTONDOWN:
152
+ r = self.parent.get_root()
153
+ if not r:
154
+ return
155
+
156
+ if self.parent.rect.collidepoint(
157
+ r.drawing_camera.screen_to_world(pygame.mouse.get_pos())
158
+ ):
159
+ if event.button == 4:
160
+ self.parent.scroll_by((0, -10))
161
+ elif event.button == 5:
162
+ self.parent.scroll_by((0, 10))
163
+ else:
164
+ return
165
+ event.consumed = True
166
+ self.parent.clamp_scroll()
167
+
168
+
169
+ class Row(SingleAxisLayout):
170
+ def __init__(self, gap: int = 0, spacing: bf.spacing = bf.spacing.MANUAL):
54
171
  super().__init__()
55
172
  self.gap = gap
56
- self.shrink = shrink
173
+ self.spacing = spacing
174
+
175
+ def set_gap(self, value: float) -> Self:
176
+ self.gap = value
177
+ self.notify_parent()
178
+ return self
179
+
180
+ def set_spacing(self, spacing: bf.spacing) -> Self:
181
+ self.spacing = spacing
182
+ self.notify_parent()
183
+ return self
184
+
185
+ def get_raw_size(self) -> tuple[float, float]:
186
+ len_children = len(self.parent.children)
187
+ if not len_children:
188
+ return self.parent.rect.size
189
+ parent_width = sum(c.get_min_required_size()[0] for c in self.parent.children)
190
+ parent_height = max(c.get_min_required_size()[1] for c in self.parent.children)
191
+ if self.gap:
192
+ parent_width += (len_children - 1) * self.gap
193
+ target_rect = self.parent.inflate_rect_by_padding(
194
+ (0, 0, parent_width, parent_height)
195
+ )
196
+
197
+ return target_rect.size
198
+
199
+ def get_auto_size(self) -> tuple[float, float]:
200
+ target_size = list(self.get_raw_size())
201
+ if not self.parent.autoresize_w:
202
+ target_size[0] = self.parent.rect.w
203
+ if not self.parent.autoresize_h:
204
+ target_size[1] = self.parent.rect.h
205
+ return target_size
57
206
 
58
207
  def arrange(self) -> None:
59
- if not self.parent:
208
+ if not self.parent or not self.parent.children:
60
209
  return
61
- if self.shrink and self.parent.children:
62
- len_children = len(self.parent.children)
63
- parent_width = sum(c.rect.w for c in self.parent.children)
64
- parent_height = max(c.rect.h for c in self.parent.children)
65
- if self.gap:
66
- parent_width += (len_children - 1) * self.gap
67
-
68
- c = self.parent.get_constraint("width")
69
- if not c or c.width != parent_width:
70
- self.parent.add_constraint(ConstraintWidth(parent_width))
71
- c = self.parent.get_constraint("height")
72
- if not c or c.height != parent_height:
73
- self.parent.add_constraint(ConstraintHeight(parent_height))
74
-
75
- current_x = self.parent.rect.left
210
+ if self.child_constraints:
211
+ for child in self.parent.children:
212
+ child.add_constraints(*self.child_constraints)
213
+ self.child_rect = self.parent.get_padded_rect()
214
+
215
+ if self.parent.autoresize_w or self.parent.autoresize_h:
216
+ width, height = self.get_auto_size()
217
+ if self.parent.rect.size != (width, height):
218
+ self.parent.set_size((width, height))
219
+ self.parent.build()
220
+ self.arrange()
221
+ return
222
+ self.child_rect.move_ip(-self.parent.scroll.x, -self.parent.scroll.y)
223
+ x = self.child_rect.left
76
224
  for child in self.parent.children:
77
- child.set_position(current_x,self.parent.rect.y)
78
- current_x += child.rect.w + self.gap
79
- for c in self.child_constraints:
80
- if not child.has_constraint(c.name):
81
- child.add_constraint(c)
225
+ child.set_position(x, self.child_rect.y)
226
+ x += child.get_min_required_size()[0] + self.gap
227
+
228
+ def handle_event(self, event):
229
+ if self.parent.autoresize_w or not self.parent.visible:
230
+ return
231
+
232
+ if not self.parent.children:
233
+ return
234
+
235
+ if event.type == pygame.MOUSEBUTTONDOWN:
236
+ r = self.parent.get_root()
237
+ if not r:
238
+ return
239
+ if self.parent.rect.collidepoint(
240
+ r.drawing_camera.screen_to_world(pygame.mouse.get_pos())
241
+ ):
242
+ if event.button == 4:
243
+ self.parent.scroll_by((-10, 0))
244
+ elif event.button == 5:
245
+ self.parent.scroll_by((10, 0))
246
+ else:
247
+ return
248
+ event.consumed = True
249
+ self.parent.clamp_scroll()
@@ -0,0 +1,74 @@
1
+ import batFramework as bf
2
+ from .shape import Shape
3
+ from typing import Self
4
+
5
+
6
+ def custom_top_at(self, x, y):
7
+ if Shape.top_at(self, x, y) == self:
8
+ return self.parent
9
+ return None
10
+
11
+
12
+ class Meter(Shape):
13
+ def __init__(self, min_value: float = 0, max_value: float = 1, step: float = 0.1):
14
+ super().__init__()
15
+ self.min_value, self.max_value = min_value, max_value
16
+ self.step = step
17
+ self.snap: bool = False
18
+ self.value = self.max_value
19
+ self.content = Shape((0, 0)).set_color(bf.color.BLUE)
20
+ self.content.set_debug_color("cyan")
21
+ self.content.top_at = lambda x, y: custom_top_at(self.content, x, y)
22
+ self.add(self.content)
23
+ self.set_padding(4)
24
+ self.set_color("gray20")
25
+ self.set_outline_width(1)
26
+ self.set_outline_color(bf.color.BLACK)
27
+ self.set_debug_color("pink")
28
+
29
+ def __str__(self) -> str:
30
+ return "Meter"
31
+
32
+ def set_step(self, step: float) -> Self:
33
+ self.step = step
34
+ self.set_value(self.value)
35
+ return self
36
+
37
+ def set_range(self, range_min: float, range_max: float) -> Self:
38
+ if range_min >= range_max:
39
+ print(
40
+ f"[Warning] : minimum value {range_min} is greater than or equal to maximum value {range_max}"
41
+ )
42
+ return self
43
+ self.min_value = range_min
44
+ self.max_value = range_max
45
+ self.dirty_shape = True
46
+
47
+ def get_debug_outlines(self):
48
+ yield from super().get_debug_outlines()
49
+ # yield from self.content.get_debug_outlines()
50
+
51
+ def set_value(self, value: float) -> Self:
52
+ value = max(self.min_value, min(self.max_value, value))
53
+ value = round(value / self.step) * self.step
54
+ self.value = round(value,10)
55
+ self.dirty_shape = True
56
+ return self
57
+
58
+ def get_value(self) -> float:
59
+ return self.value
60
+
61
+ def get_range(self) -> float:
62
+ return self.max_value - self.min_value
63
+
64
+ def get_ratio(self) -> float:
65
+ return (self.value-self.min_value) / (self.max_value - self.min_value)
66
+
67
+ def _build_content(self) -> None:
68
+ width = self.get_padded_width() * self.get_ratio()
69
+ self.content.set_size((width, self.get_padded_height()))
70
+ self.content.rect.topleft = self.get_padded_rect().topleft
71
+
72
+ def build(self) -> None:
73
+ super().build()
74
+ self._build_content()
@@ -0,0 +1,84 @@
1
+ import batFramework as bf
2
+ from typing import Self, Any, Callable
3
+ from .toggle import Toggle
4
+
5
+
6
+ class RadioVariable: ...
7
+
8
+
9
+ class RadioButton(Toggle):
10
+ def __init__(self, text: str = "", radio_value: Any = None) -> None:
11
+ super().__init__(text, None, False)
12
+ self.radio_value: Any = (
13
+ radio_value if radio_value is not None else text if text else None
14
+ )
15
+ self.radio_variable: RadioVariable = None
16
+
17
+ def __str__(self) -> str:
18
+ return (
19
+ f"RadioButton({self.radio_value}|{'Active' if self.value else 'Inactive'})"
20
+ )
21
+
22
+ def set_radio_value(self, value: Any) -> Self:
23
+ self.radio_value = value
24
+
25
+ if self.radio_variable:
26
+ self.radio_variable.update_buttons()
27
+ return self
28
+
29
+ def set_text(self, text: str) -> Self:
30
+ flag = False
31
+ if self.value == self.text or self.value is None:
32
+ flag = True
33
+ super().set_text(text)
34
+ if flag:
35
+ self.set_radio_value(self.text)
36
+ return self
37
+
38
+ def click(self) -> None:
39
+ if self.radio_variable is None:
40
+ return
41
+ self.radio_variable.set_value(self.radio_value)
42
+
43
+
44
+ class RadioVariable:
45
+ def __init__(self) -> None:
46
+ self.buttons: list[RadioButton] = []
47
+ self.value = None
48
+ self.modify_callback: Callable[[Any],] = None
49
+
50
+ def set_modify_callback(self, callback) -> Self:
51
+ self.modify_callback = callback
52
+ return self
53
+
54
+ def link(self, *buttons: RadioButton) -> Self:
55
+ if not buttons:
56
+ return self
57
+ for b in buttons:
58
+ b.radio_variable = self
59
+ self.buttons.extend(buttons)
60
+
61
+ if self.value is None:
62
+ self.set_value(buttons[0].radio_value)
63
+ else:
64
+ self.update_buttons()
65
+ return self
66
+
67
+ def unlink(self, *buttons) -> Self:
68
+ for b in self.buttons:
69
+ if b in buttons:
70
+ b.radio_variable = None
71
+ self.buttons = [b for b in self.buttons if b not in buttons]
72
+ return self
73
+
74
+ def set_value(self, value: Any) -> Self:
75
+ if value == self.value:
76
+ return
77
+ self.value = value
78
+ if self.modify_callback:
79
+ self.modify_callback(value)
80
+ self.update_buttons()
81
+ return self
82
+
83
+ def update_buttons(self):
84
+ _ = [b.set_value(b.radio_value == self.value, False) for b in self.buttons]
batFramework/gui/root.py CHANGED
@@ -2,59 +2,155 @@ import batFramework as bf
2
2
  from .interactiveWidget import InteractiveWidget
3
3
  from .widget import Widget
4
4
  import pygame
5
+ from typing import Self
6
+ import sys
7
+
5
8
 
6
9
  class Root(InteractiveWidget):
7
- def __init__(self):
10
+ def __init__(self, camera) -> None:
8
11
  super().__init__()
9
- self.surface = None
10
- self.set_root()
12
+ self.is_root = True
13
+ self.drawing_camera: bf.Camera = camera
14
+ self.visible = False
11
15
  self.rect.size = pygame.display.get_surface().get_size()
12
- self.focused : InteractiveWidget = self
13
- self.hovered : Widget|None = self
14
- self.set_debug_color("purple")
16
+ self.focused: InteractiveWidget | None = self
17
+ self.hovered: Widget | None = self
18
+ self.set_debug_color("yellow")
19
+ self.set_render_order(sys.maxsize)
20
+ self.clip_children = False
21
+
22
+ def __str__(self) -> str:
23
+ return "Root"
15
24
 
16
- def to_string(self)->str:
17
- return "ROOT"
25
+ def set_parent_scene(self, parent_scene: bf.Scene) -> Self:
26
+ bf.StyleManager().register_widget(self)
27
+ return super().set_parent_scene(parent_scene)
18
28
 
19
- def get_focused(self)->Widget|None:
29
+ def get_focused(self) -> Widget | None:
20
30
  return self.focused
21
31
 
22
- def get_hovered(self)->Widget|None:
32
+ def get_hovered(self) -> Widget | None:
23
33
  return self.hovered
24
-
25
- def to_string_id(self)->str:
26
- return "ROOT"
27
34
 
28
- def focus_on(self,widget:InteractiveWidget)->None:
29
- if self.focused is not None:
35
+ def clear_focused(self) -> None:
36
+ self.focus_on(None)
37
+
38
+ def clear_hovered(self) -> None:
39
+ if isinstance(self.hovered, InteractiveWidget):
40
+ self.hovered.on_exit()
41
+ self.hovered = None
42
+
43
+ def get_debug_outlines(self):
44
+ yield (self.rect, self.debug_color)
45
+ for child in self.children:
46
+ yield from child.get_debug_outlines()
47
+
48
+ def focus_on(self, widget: InteractiveWidget | None) -> None:
49
+ if widget == self.focused:
50
+ return
51
+ if widget and not widget.allow_focus_to_self():
52
+ return
53
+ if self.focused is not None:
30
54
  self.focused.on_lose_focus()
31
- if widget is None :
55
+ if widget is None:
32
56
  self.focused = self
33
57
  return
34
- self.focused= widget
58
+ self.focused = widget
35
59
  self.focused.on_get_focus()
36
60
 
37
- def set_size(self,width:float,height:float,force:bool=False)->"Root":
38
- if not force : return self
39
- self.rect.size = width,height
40
- self.build(apply_constraints=True)
61
+ def get_by_tags(self, *tags) -> list[Widget]:
62
+ res = []
63
+
64
+ def getter(w: Widget):
65
+ nonlocal res
66
+ if any(t in w.tags for t in tags):
67
+ res.append(w)
68
+
69
+ self.visit(getter)
70
+ return res
71
+
72
+ def focus_next_tab(self, widget):
73
+ return True
74
+
75
+ def focus_prev_tab(self, widget):
76
+ return True
77
+
78
+ def get_by_uid(self, uid: int) -> "Widget":
79
+ def helper(w: "Widget", uid: int) -> "Widget":
80
+ if w.uid == uid:
81
+ return w
82
+ for child in w.children:
83
+ res = helper(child, uid)
84
+ if res is not None:
85
+ return res
86
+ return None
87
+
88
+ return helper(self, uid)
89
+
90
+ def set_size(self, size: tuple[float, float], force: bool = False) -> "Root":
91
+ if not force:
92
+ return self
93
+ self.rect.size = size
94
+ # self.build(resolve_constraints=True)
41
95
  return self
42
-
43
- def build(self,apply_constraints:bool=False)->None:
44
- if apply_constraints : self.apply_all_constraints()
45
- for child in self.children :
46
- child.build()
47
-
48
- def do_handle_event(self,event):
49
- if event.type == pygame.VIDEORESIZE:
50
- self.set_size(event.w,event.h,force=True)
51
- return True
52
- return False
53
-
54
- def update(self,dt:float)->None:
55
- super().update(dt)
56
- self.hovered = self.top_at(*pygame.mouse.get_pos()) if self.top_at(*pygame.mouse.get_pos()) else None
57
-
58
96
 
97
+ def do_handle_event(self, event):
98
+ if not self.parent_scene.get_sharedVar("player_has_control"):
99
+ return
100
+ if self.focused:
101
+ if event.type == pygame.KEYDOWN:
102
+ if self.focused.on_key_down(event.key):
103
+ event.consumed = True
104
+ elif event.type == pygame.KEYUP:
105
+ if self.focused.on_key_up(event.key):
106
+ event.consumed = True
107
+
108
+ if not self.hovered or (not isinstance(self.hovered, InteractiveWidget)):
109
+ return
110
+
111
+ if event.type == pygame.MOUSEBUTTONDOWN:
112
+ if self.hovered.on_click_down(event.button):
113
+ event.consumed = True
114
+
115
+ elif event.type == pygame.MOUSEBUTTONUP:
116
+ if self.hovered.on_click_up(event.button):
117
+ event.consumed = True
118
+
119
+ def do_on_click_down(self, button: int) -> None:
120
+ if button == 1:
121
+ self.clear_focused()
59
122
 
60
-
123
+ def top_at(self, x: float | int, y: float | int) -> "None|Widget":
124
+ if self.children:
125
+ for child in reversed(self.children):
126
+ r = child.top_at(x, y)
127
+ if r is not None:
128
+ return r
129
+ return self if self.rect.collidepoint(x, y) else None
130
+
131
+ def update(self, dt: float) -> None:
132
+ super().update(dt)
133
+ if not self.parent_scene.get_sharedVar("player_has_control",True):
134
+ return
135
+ old = self.hovered
136
+ transposed = self.drawing_camera.screen_to_world(pygame.mouse.get_pos())
137
+ self.hovered = self.top_at(*transposed) if self.top_at(*transposed) else None
138
+ if old == self.hovered and isinstance(self.hovered, InteractiveWidget):
139
+ self.hovered.on_mouse_motion(*transposed)
140
+ return
141
+ if old and isinstance(old, InteractiveWidget):
142
+ old.on_exit()
143
+ if self.hovered and isinstance(self.hovered, InteractiveWidget):
144
+ self.hovered.on_enter()
145
+
146
+ def draw(self, camera: bf.Camera) -> None:
147
+ super().draw(camera)
148
+ if not self.parent_scene.get_sharedVar("player_has_control"):
149
+ return
150
+ if (
151
+ self.parent_scene
152
+ and self.parent_scene.active
153
+ and self.focused
154
+ and self.focused != self
155
+ ):
156
+ self.focused.draw_focused(camera)