batframework 1.1.0__py3-none-any.whl → 2.0.0a1__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 (81) hide show
  1. batFramework/__init__.py +84 -52
  2. batFramework/action.py +280 -252
  3. batFramework/actionContainer.py +105 -38
  4. batFramework/animatedSprite.py +81 -117
  5. batFramework/animation.py +91 -0
  6. batFramework/audioManager.py +156 -85
  7. batFramework/baseScene.py +249 -0
  8. batFramework/camera.py +245 -123
  9. batFramework/constants.py +57 -75
  10. batFramework/cutscene.py +239 -119
  11. batFramework/cutsceneManager.py +34 -0
  12. batFramework/drawable.py +107 -0
  13. batFramework/dynamicEntity.py +30 -23
  14. batFramework/easingController.py +58 -0
  15. batFramework/entity.py +130 -123
  16. batFramework/enums.py +171 -0
  17. batFramework/fontManager.py +65 -0
  18. batFramework/gui/__init__.py +28 -14
  19. batFramework/gui/animatedLabel.py +90 -0
  20. batFramework/gui/button.py +18 -84
  21. batFramework/gui/clickableWidget.py +244 -0
  22. batFramework/gui/collapseContainer.py +98 -0
  23. batFramework/gui/constraints/__init__.py +1 -0
  24. batFramework/gui/constraints/constraints.py +1066 -0
  25. batFramework/gui/container.py +220 -49
  26. batFramework/gui/debugger.py +140 -47
  27. batFramework/gui/draggableWidget.py +63 -0
  28. batFramework/gui/image.py +61 -23
  29. batFramework/gui/indicator.py +116 -40
  30. batFramework/gui/interactiveWidget.py +243 -22
  31. batFramework/gui/label.py +147 -110
  32. batFramework/gui/layout.py +442 -81
  33. batFramework/gui/meter.py +155 -0
  34. batFramework/gui/radioButton.py +43 -0
  35. batFramework/gui/root.py +228 -60
  36. batFramework/gui/scrollingContainer.py +282 -0
  37. batFramework/gui/selector.py +232 -0
  38. batFramework/gui/shape.py +286 -86
  39. batFramework/gui/slider.py +353 -0
  40. batFramework/gui/style.py +10 -0
  41. batFramework/gui/styleManager.py +49 -0
  42. batFramework/gui/syncedVar.py +43 -0
  43. batFramework/gui/textInput.py +331 -0
  44. batFramework/gui/textWidget.py +308 -0
  45. batFramework/gui/toggle.py +140 -62
  46. batFramework/gui/tooltip.py +35 -0
  47. batFramework/gui/widget.py +546 -307
  48. batFramework/manager.py +131 -50
  49. batFramework/particle.py +118 -0
  50. batFramework/propertyEaser.py +79 -0
  51. batFramework/renderGroup.py +34 -0
  52. batFramework/resourceManager.py +130 -0
  53. batFramework/scene.py +31 -226
  54. batFramework/sceneLayer.py +134 -0
  55. batFramework/sceneManager.py +200 -165
  56. batFramework/scrollingSprite.py +115 -0
  57. batFramework/sprite.py +46 -0
  58. batFramework/stateMachine.py +49 -51
  59. batFramework/templates/__init__.py +2 -0
  60. batFramework/templates/character.py +15 -0
  61. batFramework/templates/controller.py +158 -0
  62. batFramework/templates/stateMachine.py +39 -0
  63. batFramework/tileset.py +46 -0
  64. batFramework/timeManager.py +213 -0
  65. batFramework/transition.py +162 -157
  66. batFramework/triggerZone.py +22 -22
  67. batFramework/utils.py +306 -184
  68. {batframework-1.1.0.dist-info → batframework-2.0.0a1.dist-info}/LICENSE +20 -20
  69. {batframework-1.1.0.dist-info → batframework-2.0.0a1.dist-info}/METADATA +7 -2
  70. batframework-2.0.0a1.dist-info/RECORD +72 -0
  71. batFramework/cutsceneBlocks.py +0 -176
  72. batFramework/debugger.py +0 -48
  73. batFramework/easing.py +0 -71
  74. batFramework/gui/constraints.py +0 -204
  75. batFramework/gui/frame.py +0 -19
  76. batFramework/particles.py +0 -77
  77. batFramework/time.py +0 -75
  78. batFramework/transitionManager.py +0 -0
  79. batframework-1.1.0.dist-info/RECORD +0 -43
  80. {batframework-1.1.0.dist-info → batframework-2.0.0a1.dist-info}/WHEEL +0 -0
  81. {batframework-1.1.0.dist-info → batframework-2.0.0a1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,43 @@
1
+ import batFramework as bf
2
+ from typing import Self, Any, Callable
3
+ from .toggle import Toggle
4
+ from .syncedVar import SyncedVar
5
+
6
+
7
+ # TODO : RadioButton with no synced var ? click crashes
8
+
9
+ class RadioButton(Toggle):
10
+ def __init__(self, text: str, synced_var: SyncedVar, radio_value: Any = None) -> None:
11
+ super().__init__(text, None, False)
12
+ self.radio_value: Any = radio_value if radio_value is not None else text if text else None
13
+ self.synced_var : SyncedVar = synced_var
14
+ self.synced_var.bind(self,self._update_state)
15
+
16
+ def __str__(self) -> str:
17
+ return f"RadioButton({self.radio_value}|{'Active' if self.value else 'Inactive'})"
18
+
19
+ def set_radio_value(self, value: Any) -> Self:
20
+ self.radio_value = value
21
+ return self
22
+
23
+ def set_value(self, value : bool, do_callback=False):
24
+ if self.value == value:
25
+ return self # No change
26
+ self.value = value
27
+ self.indicator.set_value(value)
28
+ self.dirty_surface = True
29
+
30
+ # Update SyncedVar only if different (avoid recursion)
31
+ if value and self.synced_var.value != self.radio_value:
32
+ self.synced_var.value = self.radio_value
33
+
34
+ if do_callback and self.callback:
35
+ self.callback(self.value)
36
+ return self
37
+ # if value : self.synced_var.value = self.radio_value
38
+
39
+ def _update_state(self, synced_value: Any) -> None:
40
+ """
41
+ Updates the state of the RadioButton based on the synced variable's value.
42
+ """
43
+ self.set_value(self.radio_value == synced_value, False)
batFramework/gui/root.py CHANGED
@@ -1,60 +1,228 @@
1
- import batFramework as bf
2
- from .interactiveWidget import InteractiveWidget
3
- from .widget import Widget
4
- import pygame
5
-
6
- class Root(InteractiveWidget):
7
- def __init__(self):
8
- super().__init__()
9
- self.surface = None
10
- self.set_root()
11
- 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")
15
-
16
- def to_string(self)->str:
17
- return "ROOT"
18
-
19
- def get_focused(self)->Widget|None:
20
- return self.focused
21
-
22
- def get_hovered(self)->Widget|None:
23
- return self.hovered
24
-
25
- def to_string_id(self)->str:
26
- return "ROOT"
27
-
28
- def focus_on(self,widget:InteractiveWidget)->None:
29
- if self.focused is not None:
30
- self.focused.on_lose_focus()
31
- if widget is None :
32
- self.focused = self
33
- return
34
- self.focused= widget
35
- self.focused.on_get_focus()
36
-
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)
41
- 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
-
59
-
60
-
1
+ import batFramework as bf
2
+ from .interactiveWidget import InteractiveWidget
3
+ from .widget import Widget
4
+ import pygame
5
+ from typing import Self
6
+ import sys
7
+
8
+
9
+ class Root(InteractiveWidget):
10
+ def __init__(self, camera) -> None:
11
+ super().__init__()
12
+ self.is_root = True
13
+ self.drawing_camera: bf.Camera = camera
14
+ self.visible = False
15
+ self.rect.size = pygame.display.get_surface().get_size()
16
+ self.focused: InteractiveWidget | None = self
17
+ self.hovered: Widget | None = self
18
+ self.clip_children = False
19
+ self.set_debug_color("yellow")
20
+
21
+ self.show_tooltip : bool = True
22
+ self.tooltip = bf.gui.ToolTip("").set_visible(False)
23
+ self.add(self.tooltip)
24
+ self.set_click_pass_through(True)
25
+
26
+ def set_show_tooltip(self,value:bool)->Self:
27
+ self.show_tooltip = value
28
+ return self
29
+
30
+ def __str__(self) -> str:
31
+ return "Root"
32
+
33
+ def to_ascii_tree(self) -> str:
34
+ def f(w:Widget, depth):
35
+ prefix = " " * (depth * 4) + ("L__ " if depth > 0 else "")
36
+ children = "\n".join(f(c, depth + 1) for c in w.children) if w.children else ""
37
+ return f"{prefix}{str(w)}\n{children}"
38
+ return f(self, 0)
39
+
40
+ def set_parent_scene(self, parent_scene: bf.Scene) -> Self:
41
+ return super().set_parent_scene(parent_scene)
42
+
43
+ def get_focused(self) -> Widget | None:
44
+ return self.focused
45
+
46
+ def get_hovered(self) -> Widget | None:
47
+ return self.hovered
48
+
49
+ def clear_focused(self) -> None:
50
+ self.focus_on(None)
51
+
52
+ def clear_hovered(self) -> None:
53
+ if isinstance(self.hovered, InteractiveWidget):
54
+ self.hovered.on_exit()
55
+ self.hovered = None
56
+
57
+ def get_debug_outlines(self):
58
+ yield (self.rect, self.debug_color)
59
+ for child in self.children:
60
+ yield from child.get_debug_outlines()
61
+
62
+ def focus_on(self, widget: InteractiveWidget | None) -> None:
63
+ if widget == self.focused:
64
+ return
65
+ if widget and not widget.allow_focus_to_self():
66
+ return
67
+ if self.focused is not None:
68
+ self.focused.on_lose_focus()
69
+ if widget is None:
70
+ self.focused = self
71
+ return
72
+ self.focused = widget
73
+ self.focused.on_get_focus()
74
+
75
+ def get_by_tags(self, *tags) -> list[Widget]:
76
+ res = []
77
+
78
+ def getter(w: Widget):
79
+ nonlocal res
80
+ if any(t in w.tags for t in tags):
81
+ res.append(w)
82
+
83
+ self.visit(getter)
84
+ return res
85
+
86
+ def focus_next_tab(self, widget):
87
+ return
88
+
89
+ def focus_prev_tab(self, widget):
90
+ return
91
+
92
+ def get_by_uid(self, uid: int) -> "Widget":
93
+ def helper(w: "Widget", uid: int) -> "Widget":
94
+ if w.uid == uid:
95
+ return w
96
+ for child in w.children:
97
+ res = helper(child, uid)
98
+ if res is not None:
99
+ return res
100
+ return None
101
+
102
+ return helper(self, uid)
103
+
104
+ def set_size(self, size: tuple[float, float], force: bool = False) -> "Root":
105
+ if not force:
106
+ return self
107
+ self.rect.size = size
108
+ self.dirty_shape = True
109
+ self.dirty_size_constraints = True
110
+ return self
111
+
112
+ def process_event(self,event):
113
+ if not event.consumed : self.handle_event_early(event)
114
+ super().process_event(event)
115
+
116
+ def handle_event_early(self, event):
117
+ if event.type == pygame.VIDEORESIZE and not pygame.SCALED & bf.const.FLAGS:
118
+ self.set_size((event.w,event.h),force=True)
119
+ return
120
+
121
+ def handle_event(self, event):
122
+ super().handle_event(event)
123
+ if not event.consumed :
124
+ if event.type == pygame.KEYDOWN and event.key==pygame.K_TAB:
125
+ self.tab_focus(event)
126
+
127
+ def tab_focus(self,event=None):
128
+ if self.focused is None:
129
+ return
130
+ keys = pygame.key.get_pressed()
131
+ if keys[pygame.K_LSHIFT] or keys[pygame.K_RSHIFT]:
132
+ self.focused.focus_prev_tab(self.focused)
133
+ else:
134
+ self.focused.focus_next_tab(self.focused)
135
+ if event :event.consumed = True
136
+
137
+
138
+ def on_click_down(self, button: int,event) -> None:
139
+ if button == 1:
140
+ self.clear_focused()
141
+ super().on_click_down(button,event)
142
+
143
+ def top_at(self, x: float | int, y: float | int) -> "None|Widget":
144
+ for child in reversed(self.children):
145
+ r = child.top_at(x, y)
146
+ if r is not None:
147
+ return r
148
+ return self if self.rect.collidepoint(x, y) else None
149
+
150
+ def update(self, dt: float) -> None:
151
+ super().update(dt)
152
+ self.update_tree()
153
+
154
+ mouse_world = self.drawing_camera.get_mouse_pos()
155
+ prev_hovered = self.hovered
156
+ self.hovered = self.top_at(*mouse_world)
157
+
158
+ if (self.hovered and self.hovered.tooltip_text and self.show_tooltip):
159
+ self.tooltip.set_text(self.hovered.tooltip_text)
160
+ self.tooltip.fade_in()
161
+ else:
162
+ self.tooltip.fade_out()
163
+
164
+ # Tooltip logic
165
+ if self.tooltip.visible:
166
+
167
+ tooltip_size = self.tooltip.get_min_required_size()
168
+ # screen_w, screen_h = self.drawing_camera.rect.size
169
+ screen_w, screen_h = bf.const.RESOLUTION
170
+
171
+ tooltip_x, tooltip_y = mouse_world
172
+ tooltip_x+=4
173
+ tooltip_y+=4
174
+ tooltip_x = min(tooltip_x, screen_w - tooltip_size[0])
175
+ tooltip_y = min(tooltip_y, screen_h - tooltip_size[1])
176
+ tooltip_x = max(0, tooltip_x)
177
+ tooltip_y = max(0, tooltip_y)
178
+
179
+ self.tooltip.set_position(tooltip_x, tooltip_y)
180
+
181
+ if self.hovered == prev_hovered:
182
+ if isinstance(self.hovered, InteractiveWidget):
183
+ self.hovered.on_mouse_motion(*mouse_world)
184
+ return
185
+
186
+ if isinstance(prev_hovered, InteractiveWidget):
187
+ prev_hovered.on_exit()
188
+ if isinstance(self.hovered, InteractiveWidget):
189
+ self.hovered.on_enter()
190
+
191
+
192
+
193
+
194
+ def update_tree(self):
195
+ # 1st pass
196
+ self.apply_updates("pre")
197
+ self.apply_updates("post")
198
+ # 2nd pass
199
+ # self.apply_updates("pre")
200
+ # self.apply_updates("post")
201
+
202
+
203
+ def apply_pre_updates(self):
204
+ return
205
+
206
+ def apply_post_updates(self, skip_draw = False):
207
+ return
208
+
209
+ def draw(self, camera: bf.Camera) -> None:
210
+ if self.clip_children:
211
+ new_clip = camera.world_to_screen(self.get_inner_rect())
212
+ old_clip = camera.surface.get_clip()
213
+ camera.surface.set_clip(new_clip)
214
+
215
+ # Draw each child widget, sorted by render order
216
+ for child in [c for c in self.children if c != self.tooltip]:
217
+ if (not self.clip_children) or (child.rect.colliderect(self.rect) or not child.rect):
218
+ child.draw(camera)
219
+ if self.clip_children:
220
+ camera.surface.set_clip(old_clip)
221
+
222
+ if self.focused != self and (not self.focused is None) :
223
+ old_clip = camera.surface.get_clip()
224
+ camera.surface.set_clip(self.focused.parent.get_inner_rect())
225
+ self.focused.draw_focused(camera)
226
+ camera.surface.set_clip(old_clip)
227
+
228
+ self.tooltip.draw(camera)
@@ -0,0 +1,282 @@
1
+ from typing import Self
2
+ import pygame
3
+ from .slider import Slider
4
+ import batFramework as bf
5
+ from .syncedVar import SyncedVar
6
+ from .container import Container
7
+ from .constraints.constraints import FillX, FillY
8
+ from .widget import Widget
9
+ from .shape import Shape
10
+
11
+ class ScrollBar(Slider):
12
+ def do_when_added(self):
13
+ super().do_when_added()
14
+ self.meter.add_constraints(FillX(), FillY())
15
+ self.set_clip_children(False)
16
+ self.set_pressed_relief(0).set_unpressed_relief(0)
17
+ self.meter.content.set_color(None)
18
+
19
+ def __str__(self):
20
+ return "Scrollbar"
21
+
22
+ def _on_synced_var_update(self, value):
23
+ self.dirty_shape = True # build uses synced var directly so we just trigger build via dirty shape
24
+ if self.modify_callback:
25
+ self.modify_callback(value)
26
+
27
+ def on_get_focus(self) -> None:
28
+ self.is_focused = True
29
+ self.do_on_get_focus()
30
+
31
+ def draw_focused(self, camera):
32
+ return
33
+
34
+ def paint(self) -> None:
35
+ Shape.paint(self)
36
+
37
+
38
+ class ScrollingContainer(Container):
39
+ """
40
+ Should work for single and double axis standard layouts
41
+ """
42
+ def __init__(self, layout=None, *children):
43
+ super().__init__(layout, *children)
44
+ self.sync_scroll_x= SyncedVar(0) # 0 to 1
45
+ self.sync_scroll_y= SyncedVar(0) # 0 to 1
46
+
47
+ self.scrollbar_size = 8
48
+ self.scrollbars_needed = [False, False] # [horizontal, vertical]
49
+
50
+ # Create scrollbars
51
+ self.y_scrollbar = (
52
+ ScrollBar("", 0, synced_var=self.sync_scroll_y)
53
+ .set_axis(bf.axis.VERTICAL)
54
+ .set_autoresize(False)
55
+ .set_padding(0)
56
+ .set_direction(bf.direction.DOWN)
57
+ .set_step(0.01)
58
+ )
59
+ self.y_scrollbar.meter.handle.set_autoresize_h(False)
60
+
61
+ self.x_scrollbar = (
62
+ ScrollBar("", 0, synced_var=self.sync_scroll_x)
63
+ .set_autoresize(False)
64
+ .set_padding(0)
65
+ .set_step(0.01)
66
+ )
67
+
68
+ self.x_scrollbar.meter.handle.set_autoresize_w(False)
69
+
70
+ self.sync_scroll_x.bind(self,lambda v : self.on_sync_update((v,self.sync_scroll_y.value)))
71
+ self.sync_scroll_y.bind(self,lambda v : self.on_sync_update((self.sync_scroll_x.value,v)))
72
+
73
+
74
+ # Add scrollbars but initially hide them
75
+ self.add(
76
+ self.y_scrollbar,
77
+ self.x_scrollbar
78
+ )
79
+ self.x_scrollbar.hide()
80
+ self.y_scrollbar.hide()
81
+
82
+ def __str__(self):
83
+ return "Scrolling"+ super().__str__()
84
+
85
+ def set_scroll(self,value):
86
+ # print("Scrolling set scroll called", value)
87
+ self.sync_scroll_x.value = self.x_scroll_scaled_to_normalized(value[0])
88
+ self.sync_scroll_y.value = self.y_scroll_scaled_to_normalized(value[1])
89
+
90
+
91
+ def on_sync_update(self,value):
92
+ # print("Scrolling on sync update called", value)
93
+ value = [self.x_scroll_normalized_to_scaled(value[0]),self.y_scroll_normalized_to_scaled(value[1])]
94
+ super().set_scroll(value)
95
+
96
+
97
+ def set_scrollbar_size(self, size:int)->Self:
98
+ self.scrollbar_size = size
99
+ self.dirty_layout= True
100
+ self.dirty_shape = True
101
+ return self
102
+
103
+ def y_scroll_normalized_to_scaled(self, value):
104
+ inner_height = self.get_inner_height()
105
+ content_height = self.layout.children_rect.h # total content height
106
+
107
+ max_scroll = max(0, content_height - inner_height)
108
+ converted_y = value * max_scroll
109
+
110
+ return converted_y
111
+
112
+ def x_scroll_normalized_to_scaled(self, value):
113
+ inner_width = self.get_inner_width()
114
+ content_width = self.layout.children_rect.w # total content width
115
+
116
+ max_scroll = max(0, content_width - inner_width)
117
+ converted_x = value * max_scroll
118
+
119
+ return converted_x
120
+
121
+ def y_scroll_scaled_to_normalized(self, value):
122
+ inner_height = self.get_inner_height()
123
+ content_height = self.layout.children_rect.h
124
+
125
+ max_scroll = max(0, content_height - inner_height)
126
+ if max_scroll == 0:
127
+ return 0
128
+ return value / max_scroll
129
+
130
+ def x_scroll_scaled_to_normalized(self, value):
131
+ inner_width = self.get_inner_width()
132
+ content_width = self.layout.children_rect.w
133
+
134
+ max_scroll = max(0, content_width - inner_width)
135
+ if max_scroll == 0:
136
+ return 0
137
+ return value / max_scroll
138
+
139
+
140
+
141
+ def get_layout_children(self):
142
+ return [c for c in super().get_layout_children() if c not in [self.y_scrollbar,self.x_scrollbar]]
143
+
144
+ def get_interactive_children(self):
145
+ return [c for c in super().get_interactive_children() if c not in [self.y_scrollbar,self.x_scrollbar]]
146
+
147
+ def top_at(self, x, y):
148
+ res = self.y_scrollbar.top_at(x,y)
149
+ if res: return res
150
+ res = self.x_scrollbar.top_at(x,y)
151
+ if res: return res
152
+ return super().top_at(x, y)
153
+
154
+
155
+ def get_inner_width(self):
156
+ r = super().get_inner_width()
157
+ if not self.scrollbars_needed[1]:
158
+ return r
159
+ return r - self.scrollbar_size
160
+
161
+ def get_inner_height(self):
162
+ r = super().get_inner_height()
163
+ if not self.scrollbars_needed[0]:
164
+ return r
165
+ return r - self.scrollbar_size
166
+
167
+ def get_inner_rect(self):
168
+ r = super().get_inner_rect()
169
+ if self.scrollbars_needed[1] :
170
+ r.w -= self.scrollbar_size
171
+ if self.scrollbars_needed[0] :
172
+ r.h -= self.scrollbar_size
173
+ return r
174
+
175
+
176
+ def expand_rect_with_padding(self, rect):
177
+ r = super().expand_rect_with_padding(rect)
178
+
179
+ if self.scrollbars_needed[1] :
180
+ r.w += self.scrollbar_size
181
+ if self.scrollbars_needed[0] :
182
+ r.h += self.scrollbar_size
183
+ return r
184
+
185
+ def _update_scrollbars(self):
186
+ # print("Update scrollbar")
187
+ if self.layout.children_rect.h == 0:
188
+ y_ratio = 1 # set to 1 so no need to scroll
189
+ else:
190
+ y_ratio = self.get_inner_height() / self.layout.children_rect.h
191
+ if self.layout.children_rect.w == 0:
192
+ x_ratio = 1
193
+ else:
194
+ x_ratio = self.get_inner_width() / self.layout.children_rect.w
195
+
196
+
197
+ tmp_scrollbars_needed = [
198
+ x_ratio < 1,
199
+ y_ratio < 1
200
+ ]
201
+ visible_list = [self.x_scrollbar.visible,self.y_scrollbar.visible]
202
+ # print(f"{tmp_scrollbars_needed=}")
203
+
204
+ # print(f"{self.scrollbars_needed=}")
205
+
206
+ # print(f"{x_ratio=}")
207
+ # print(f"{y_ratio=}")
208
+
209
+
210
+ # check if scrollbars changed (remove or add)
211
+ if self.scrollbars_needed != tmp_scrollbars_needed :
212
+ # self.dirty_shape = True # because scrollbars change self shape
213
+ self.x_scrollbar.show() if tmp_scrollbars_needed[0] else self.x_scrollbar.hide()
214
+ self.y_scrollbar.show() if tmp_scrollbars_needed[1] else self.y_scrollbar.hide()
215
+
216
+ self.scrollbars_needed = tmp_scrollbars_needed
217
+
218
+
219
+
220
+
221
+ if self.x_scrollbar.visible:
222
+ self.x_scrollbar.set_size((self.rect.w-(self.scrollbar_size if tmp_scrollbars_needed[1] else 0),self.scrollbar_size))
223
+ self.x_scrollbar.set_position(self.rect.left,self.rect.bottom - self.scrollbar_size)
224
+ self.x_scrollbar.meter.handle.set_size((max(2,self.get_inner_width()*x_ratio),None))
225
+ if self.y_scrollbar.visible:
226
+ self.y_scrollbar.set_size((self.scrollbar_size,self.rect.h))
227
+ self.y_scrollbar.set_position(self.rect.right - self.scrollbar_size,self.rect.top)
228
+ self.y_scrollbar.meter.handle.set_size((None,max(self.get_inner_height()*y_ratio,2)))
229
+
230
+
231
+
232
+
233
+
234
+
235
+
236
+ def build(self) -> None:
237
+ if self.layout is not None:
238
+ size = list(self.layout.get_auto_size())
239
+ size[0]+=self.scrollbar_size
240
+ size[1]+=self.scrollbar_size
241
+ self.set_size(self.resolve_size(size))
242
+ return super().build()
243
+
244
+ def apply_pre_updates(self):
245
+ if self.dirty_size_constraints or self.dirty_shape:
246
+ self.resolve_constraints(size_only=True)
247
+ self.dirty_size_constraints = False
248
+ self.dirty_position_constraints = True
249
+
250
+ if self.dirty_layout:
251
+ self.layout.update_child_constraints()
252
+ self.layout.arrange()
253
+ self.dirty_layout = False
254
+ self.dirty_scroll = True
255
+
256
+ if self.dirty_scroll:
257
+ self.layout.scroll_children()
258
+ self._update_scrollbars()
259
+ self.dirty_scroll = False
260
+
261
+
262
+
263
+
264
+
265
+ def draw(self, camera):
266
+ bf.Drawable.draw(self,camera)
267
+
268
+ if self.clip_children:
269
+ new_clip = camera.world_to_screen(self.get_inner_rect())
270
+ old_clip = camera.surface.get_clip()
271
+ new_clip = new_clip.clip(old_clip)
272
+ camera.surface.set_clip(new_clip)
273
+
274
+ # Draw each child widget, sorted by render order
275
+ for child in self.children:
276
+ if (not self.clip_children) or (child.rect.colliderect(self.rect) or not child.rect):
277
+ child.draw(camera)
278
+ if self.clip_children:
279
+ camera.surface.set_clip(old_clip)
280
+
281
+ self.y_scrollbar.draw(camera)
282
+ self.x_scrollbar.draw(camera)