batframework 1.0.10__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.
- batFramework/__init__.py +83 -52
- batFramework/action.py +280 -252
- batFramework/actionContainer.py +105 -38
- batFramework/animatedSprite.py +81 -117
- batFramework/animation.py +91 -0
- batFramework/audioManager.py +156 -85
- batFramework/baseScene.py +249 -0
- batFramework/camera.py +245 -123
- batFramework/constants.py +57 -75
- batFramework/cutscene.py +239 -119
- batFramework/cutsceneManager.py +34 -0
- batFramework/drawable.py +107 -0
- batFramework/dynamicEntity.py +30 -23
- batFramework/easingController.py +58 -0
- batFramework/entity.py +130 -123
- batFramework/enums.py +171 -0
- batFramework/fontManager.py +65 -0
- batFramework/gui/__init__.py +28 -14
- batFramework/gui/animatedLabel.py +90 -0
- batFramework/gui/button.py +18 -84
- batFramework/gui/clickableWidget.py +244 -0
- batFramework/gui/collapseContainer.py +98 -0
- batFramework/gui/constraints/__init__.py +1 -0
- batFramework/gui/constraints/constraints.py +1066 -0
- batFramework/gui/container.py +220 -49
- batFramework/gui/debugger.py +140 -47
- batFramework/gui/draggableWidget.py +63 -0
- batFramework/gui/image.py +61 -23
- batFramework/gui/indicator.py +116 -40
- batFramework/gui/interactiveWidget.py +243 -22
- batFramework/gui/label.py +147 -110
- batFramework/gui/layout.py +442 -81
- batFramework/gui/meter.py +155 -0
- batFramework/gui/radioButton.py +43 -0
- batFramework/gui/root.py +228 -60
- batFramework/gui/scrollingContainer.py +282 -0
- batFramework/gui/selector.py +232 -0
- batFramework/gui/shape.py +286 -86
- batFramework/gui/slider.py +353 -0
- batFramework/gui/style.py +10 -0
- batFramework/gui/styleManager.py +49 -0
- batFramework/gui/syncedVar.py +43 -0
- batFramework/gui/textInput.py +331 -0
- batFramework/gui/textWidget.py +308 -0
- batFramework/gui/toggle.py +140 -62
- batFramework/gui/tooltip.py +35 -0
- batFramework/gui/widget.py +546 -307
- batFramework/manager.py +131 -50
- batFramework/particle.py +118 -0
- batFramework/propertyEaser.py +79 -0
- batFramework/renderGroup.py +34 -0
- batFramework/resourceManager.py +130 -0
- batFramework/scene.py +31 -226
- batFramework/sceneLayer.py +134 -0
- batFramework/sceneManager.py +200 -165
- batFramework/scrollingSprite.py +115 -0
- batFramework/sprite.py +46 -0
- batFramework/stateMachine.py +49 -51
- batFramework/templates/__init__.py +2 -0
- batFramework/templates/character.py +15 -0
- batFramework/templates/controller.py +158 -0
- batFramework/templates/stateMachine.py +39 -0
- batFramework/tileset.py +46 -0
- batFramework/timeManager.py +213 -0
- batFramework/transition.py +162 -157
- batFramework/triggerZone.py +22 -22
- batFramework/utils.py +306 -184
- {batframework-1.0.10.dist-info → batframework-2.0.0a1.dist-info}/LICENSE +20 -20
- {batframework-1.0.10.dist-info → batframework-2.0.0a1.dist-info}/METADATA +2 -2
- batframework-2.0.0a1.dist-info/RECORD +72 -0
- batFramework/cutsceneBlocks.py +0 -176
- batFramework/debugger.py +0 -48
- batFramework/easing.py +0 -71
- batFramework/gui/constraints.py +0 -204
- batFramework/gui/frame.py +0 -19
- batFramework/particles.py +0 -77
- batFramework/time.py +0 -75
- batFramework/transitionManager.py +0 -0
- batframework-1.0.10.dist-info/RECORD +0 -43
- {batframework-1.0.10.dist-info → batframework-2.0.0a1.dist-info}/WHEEL +0 -0
- {batframework-1.0.10.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
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
self.
|
13
|
-
self.
|
14
|
-
self.
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
return
|
42
|
-
|
43
|
-
def
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
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)
|