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.
- batFramework/__init__.py +84 -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.1.0.dist-info → batframework-2.0.0a1.dist-info}/LICENSE +20 -20
- {batframework-1.1.0.dist-info → batframework-2.0.0a1.dist-info}/METADATA +7 -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.1.0.dist-info/RECORD +0 -43
- {batframework-1.1.0.dist-info → batframework-2.0.0a1.dist-info}/WHEEL +0 -0
- {batframework-1.1.0.dist-info → batframework-2.0.0a1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,353 @@
|
|
1
|
+
import batFramework as bf
|
2
|
+
import pygame
|
3
|
+
from .button import Button
|
4
|
+
from .meter import BarMeter
|
5
|
+
from .indicator import Indicator, DraggableWidget
|
6
|
+
from .shape import Shape
|
7
|
+
from .interactiveWidget import InteractiveWidget
|
8
|
+
from .syncedVar import SyncedVar
|
9
|
+
from decimal import Decimal
|
10
|
+
from typing import Callable, Any, Self
|
11
|
+
|
12
|
+
def round_to_step_precision(value, step):
|
13
|
+
decimals = -Decimal(str(step)).as_tuple().exponent
|
14
|
+
rounded = round(value, decimals)
|
15
|
+
# Preserve int type if step is an int
|
16
|
+
if isinstance(step, int) or (isinstance(step, float) and step.is_integer()):
|
17
|
+
return int(rounded)
|
18
|
+
return rounded
|
19
|
+
|
20
|
+
|
21
|
+
class SliderHandle(Indicator, DraggableWidget):
|
22
|
+
def __init__(self,synced_var:SyncedVar):
|
23
|
+
super().__init__()
|
24
|
+
self.set_color(bf.color.CLOUD_SHADE)
|
25
|
+
self.set_click_mask(1)
|
26
|
+
self.synced_var = synced_var
|
27
|
+
synced_var.bind(self,self._on_synced_var_update)
|
28
|
+
|
29
|
+
def __str__(self) -> str:
|
30
|
+
return "SliderHandle"
|
31
|
+
|
32
|
+
def top_at(self, x, y):
|
33
|
+
return Shape.top_at(self,x,y)
|
34
|
+
|
35
|
+
def on_click_down(self, button, event=None):
|
36
|
+
if button == 1:
|
37
|
+
self.parent.get_focus()
|
38
|
+
event.consumed = True
|
39
|
+
super().on_click_down(button, event)
|
40
|
+
|
41
|
+
def do_on_drag(self, drag_start, drag_end):
|
42
|
+
if not self.parent:
|
43
|
+
return
|
44
|
+
super().do_on_drag(drag_start, drag_end)
|
45
|
+
meter: SliderMeter = self.parent
|
46
|
+
new_value = meter.position_to_value(self.rect.center)
|
47
|
+
self._on_synced_var_update(new_value) # need to call this manually otherwise the order of update is fucked up and handle goes outside meter
|
48
|
+
self.synced_var.value = new_value
|
49
|
+
|
50
|
+
def _on_synced_var_update(self,value:float):
|
51
|
+
meter: SliderMeter = self.parent
|
52
|
+
self.set_center(*meter.value_to_position(value))
|
53
|
+
self.rect.clamp_ip(meter.get_inner_rect())
|
54
|
+
|
55
|
+
def set_size(self, size):
|
56
|
+
super().set_size(size)
|
57
|
+
if self.parent:
|
58
|
+
self.parent.dirty_shape = True
|
59
|
+
|
60
|
+
def on_exit(self):
|
61
|
+
before = self.is_clicked_down
|
62
|
+
super().on_exit()
|
63
|
+
self.is_clicked_down = before
|
64
|
+
|
65
|
+
def draw_focused(self, camera):
|
66
|
+
return
|
67
|
+
|
68
|
+
class SliderMeter(BarMeter, InteractiveWidget):
|
69
|
+
def __init__(self, min_value=0, max_value=1, step=0.1, synced_var: SyncedVar = None):
|
70
|
+
super().__init__(min_value, max_value, step, synced_var)
|
71
|
+
self.axis = bf.axis.HORIZONTAL
|
72
|
+
self.handle = SliderHandle(synced_var=synced_var)
|
73
|
+
self.add(self.handle)
|
74
|
+
self.set_padding(0)
|
75
|
+
self.set_debug_color(bf.color.RED)
|
76
|
+
|
77
|
+
def get_focus(self) -> bool:
|
78
|
+
res = super().get_focus()
|
79
|
+
if res:
|
80
|
+
return self.parent.get_focus()
|
81
|
+
return False
|
82
|
+
def __str__(self) -> str:
|
83
|
+
return "SliderMeter"
|
84
|
+
|
85
|
+
def set_tooltip_text(self, text):
|
86
|
+
self.handle.set_tooltip_text(text)
|
87
|
+
return super().set_tooltip_text(text)
|
88
|
+
|
89
|
+
def get_min_required_size(self):
|
90
|
+
size = [bf.FontManager().DEFAULT_FONT_SIZE] * 2
|
91
|
+
if self.axis == bf.axis.HORIZONTAL:
|
92
|
+
size[0] = size[1] * 3
|
93
|
+
else:
|
94
|
+
size[1] = size[0] * 3
|
95
|
+
return self.expand_rect_with_padding((0, 0, *size)).size
|
96
|
+
|
97
|
+
def value_to_position(self, value: float) -> tuple[float, float]:
|
98
|
+
rect = self.get_inner_rect()
|
99
|
+
value_range = self.get_range()
|
100
|
+
if self.snap :
|
101
|
+
value = round_to_step_precision(value, self.step)
|
102
|
+
ratio = (value - self.min_value) / value_range if value_range else 0
|
103
|
+
|
104
|
+
if self.direction in [bf.direction.LEFT, bf.direction.DOWN]:
|
105
|
+
ratio = 1 - ratio
|
106
|
+
|
107
|
+
if self.axis == bf.axis.HORIZONTAL:
|
108
|
+
x = rect.left + (self.handle.rect.w / 2) + ratio * (rect.width - self.handle.rect.w)
|
109
|
+
y = rect.centery
|
110
|
+
else:
|
111
|
+
x = rect.centerx
|
112
|
+
y = rect.bottom - (self.handle.rect.h / 2) - ratio * (rect.height - self.handle.rect.h)
|
113
|
+
return (x, y)
|
114
|
+
|
115
|
+
def position_to_value(self, position: tuple[float, float]) -> float:
|
116
|
+
rect = self.get_inner_rect()
|
117
|
+
if self.axis == bf.axis.HORIZONTAL:
|
118
|
+
pos = position[0]
|
119
|
+
handle_half = self.handle.rect.w / 2
|
120
|
+
pos = max(rect.left + handle_half, min(pos, rect.right - handle_half))
|
121
|
+
ratio = (pos - rect.left - handle_half) / (rect.width - self.handle.rect.w) if rect.width != self.handle.rect.w else 0
|
122
|
+
else:
|
123
|
+
pos = position[1]
|
124
|
+
handle_half = self.handle.rect.h / 2
|
125
|
+
pos = max(rect.top + handle_half, min(pos, rect.bottom - handle_half))
|
126
|
+
ratio = (rect.bottom - pos - handle_half) / (rect.height - self.handle.rect.h) if rect.height != self.handle.rect.h else 0
|
127
|
+
|
128
|
+
if self.direction in [bf.direction.LEFT, bf.direction.DOWN]:
|
129
|
+
ratio = 1 - ratio
|
130
|
+
|
131
|
+
value = self.min_value + ratio * self.get_range()
|
132
|
+
return round_to_step_precision(value, self.step) if self.snap else value
|
133
|
+
|
134
|
+
|
135
|
+
def handle_event(self, event: pygame.Event):
|
136
|
+
|
137
|
+
|
138
|
+
if (self.is_hovered or getattr(self.parent, 'is_hovered', False) or self.handle.is_hovered) :
|
139
|
+
if event.type in [pygame.MOUSEBUTTONUP,pygame.MOUSEBUTTONDOWN] and event.button in [4,5]:
|
140
|
+
event.consumed = True
|
141
|
+
elif event.type == pygame.MOUSEWHEEL:
|
142
|
+
keys = pygame.key.get_pressed()
|
143
|
+
shift_held = keys[pygame.K_LSHIFT] or keys[pygame.K_RSHIFT]
|
144
|
+
is_vertical = self.axis == bf.axis.VERTICAL
|
145
|
+
is_horizontal = not is_vertical
|
146
|
+
if (not shift_held and is_horizontal) or (shift_held and is_vertical):
|
147
|
+
return
|
148
|
+
if event.y:
|
149
|
+
delta = -self.step if event.y < 0 else self.step
|
150
|
+
if self.direction in [bf.direction.DOWN,bf.direction.LEFT]:
|
151
|
+
delta*=-1
|
152
|
+
self.parent.set_value(self.parent.get_value() + delta)
|
153
|
+
event.consumed = True
|
154
|
+
super().handle_event(event)
|
155
|
+
|
156
|
+
def on_click_down(self, button: int,event=None):
|
157
|
+
# old_consume = event.consumed
|
158
|
+
if not self.parent.is_enabled:
|
159
|
+
return
|
160
|
+
super().on_click_down(button,event)
|
161
|
+
# event.consumed = old_consume
|
162
|
+
if button == 1:
|
163
|
+
self.parent.get_focus()
|
164
|
+
world_pos = self.parent_layer.camera.get_mouse_pos()
|
165
|
+
if self.get_inner_rect().collidepoint(*world_pos):
|
166
|
+
new_value = self.position_to_value(world_pos)
|
167
|
+
self.set_value(new_value)
|
168
|
+
self.handle.on_click_down(button,event)
|
169
|
+
|
170
|
+
def on_click_up(self, button, event=None):
|
171
|
+
super().do_on_click_up(button,event)
|
172
|
+
|
173
|
+
def _build_content(self):
|
174
|
+
super()._build_content()
|
175
|
+
handle_size = self.get_inner_height() if self.axis == bf.axis.HORIZONTAL else self.get_inner_width()
|
176
|
+
self.handle.set_size(self.handle.resolve_size((handle_size, handle_size)))
|
177
|
+
self.handle._on_synced_var_update(self.synced_var.value)
|
178
|
+
|
179
|
+
|
180
|
+
class Slider(Button):
|
181
|
+
def __init__(self, text: str, default_value: float = 1.0, synced_var: SyncedVar = None) -> None:
|
182
|
+
super().__init__(text, None)
|
183
|
+
self.old_key_repeat = (0, 0)
|
184
|
+
self.synced_var = synced_var or SyncedVar(default_value)
|
185
|
+
self.axis: bf.axis = bf.axis.HORIZONTAL
|
186
|
+
self.gap: float | int = 0
|
187
|
+
self.spacing: bf.spacing = bf.spacing.MANUAL
|
188
|
+
self.modify_callback: Callable[[float], Any] = None
|
189
|
+
self.meter: SliderMeter = SliderMeter(synced_var=self.synced_var)
|
190
|
+
self.add(self.meter)
|
191
|
+
self.synced_var.bind(self, self._on_synced_var_update)
|
192
|
+
self.set_range(0, self.synced_var.value)
|
193
|
+
self.synced_var.update_bound_entities()
|
194
|
+
|
195
|
+
def set_snap(self,snap:bool)->Self:
|
196
|
+
self.meter.set_snap(snap)
|
197
|
+
return self
|
198
|
+
|
199
|
+
def do_on_get_focus(self) -> None:
|
200
|
+
super().do_on_get_focus()
|
201
|
+
self.old_key_repeat = pygame.key.get_repeat()
|
202
|
+
pygame.key.set_repeat(200, 50)
|
203
|
+
|
204
|
+
def do_on_lose_focus(self) -> None:
|
205
|
+
super().do_on_lose_focus()
|
206
|
+
pygame.key.set_repeat(*self.old_key_repeat)
|
207
|
+
|
208
|
+
def _on_synced_var_update(self, value):
|
209
|
+
if self.modify_callback:
|
210
|
+
self.modify_callback(value)
|
211
|
+
rounded = round_to_step_precision(self.get_value(), self.meter.step)
|
212
|
+
self.meter.set_tooltip_text(str(rounded))
|
213
|
+
|
214
|
+
def set_fill_color(self, color) -> Self:
|
215
|
+
self.meter.content.set_color(color)
|
216
|
+
return self
|
217
|
+
|
218
|
+
def set_axis(self, axis: bf.axis) -> Self:
|
219
|
+
self.axis = axis
|
220
|
+
self.meter.axis = axis
|
221
|
+
self.dirty_shape = True
|
222
|
+
return self
|
223
|
+
|
224
|
+
def set_direction(self, direction: bf.direction) -> Self:
|
225
|
+
self.meter.set_direction(direction)
|
226
|
+
self.set_axis(self.meter.axis)
|
227
|
+
return self
|
228
|
+
|
229
|
+
def set_visible(self, value: bool) -> Self:
|
230
|
+
self.meter.set_visible(value)
|
231
|
+
return super().set_visible(value)
|
232
|
+
|
233
|
+
def __str__(self) -> str:
|
234
|
+
return "Slider"
|
235
|
+
|
236
|
+
def set_gap(self, value: int | float) -> Self:
|
237
|
+
self.gap = max(0, value)
|
238
|
+
return self
|
239
|
+
|
240
|
+
def set_spacing(self, spacing: bf.spacing) -> Self:
|
241
|
+
if spacing != self.spacing:
|
242
|
+
self.spacing = spacing
|
243
|
+
self.dirty_shape = True
|
244
|
+
return self
|
245
|
+
|
246
|
+
def set_modify_callback(self, callback: Callable[[float], Any]) -> Self:
|
247
|
+
self.modify_callback = callback
|
248
|
+
return self
|
249
|
+
|
250
|
+
def set_range(self, range_min: float, range_max: float) -> Self:
|
251
|
+
self.meter.set_range(range_min, range_max)
|
252
|
+
# self.meter.set_value(self.synced_var.value)
|
253
|
+
self.dirty_shape = True
|
254
|
+
return self
|
255
|
+
|
256
|
+
def set_step(self, step: float) -> Self:
|
257
|
+
self.meter.set_step(step)
|
258
|
+
self.dirty_shape = True
|
259
|
+
return self
|
260
|
+
|
261
|
+
def set_value(self, value) -> Self:
|
262
|
+
value = max(self.meter.min_value, min(value, self.meter.max_value))
|
263
|
+
self.synced_var.value = value
|
264
|
+
return self
|
265
|
+
|
266
|
+
def get_value(self) -> float:
|
267
|
+
return self.synced_var.value
|
268
|
+
|
269
|
+
def on_key_down(self, key, event):
|
270
|
+
super().on_key_down(key, event)
|
271
|
+
if event.consumed or not self.is_enabled:
|
272
|
+
return
|
273
|
+
|
274
|
+
step = self.meter.step
|
275
|
+
value = self.get_value()
|
276
|
+
axis = self.axis
|
277
|
+
direction = self.meter.direction
|
278
|
+
|
279
|
+
if axis == bf.axis.HORIZONTAL:
|
280
|
+
if key == pygame.K_RIGHT:
|
281
|
+
delta = step if direction == bf.direction.RIGHT else -step
|
282
|
+
elif key == pygame.K_LEFT:
|
283
|
+
delta = -step if direction == bf.direction.RIGHT else step
|
284
|
+
else:
|
285
|
+
return
|
286
|
+
elif axis == bf.axis.VERTICAL:
|
287
|
+
if key == pygame.K_UP:
|
288
|
+
delta = step if direction == bf.direction.UP else -step
|
289
|
+
elif key == pygame.K_DOWN:
|
290
|
+
delta = -step if direction == bf.direction.UP else step
|
291
|
+
else:
|
292
|
+
return
|
293
|
+
else:
|
294
|
+
return
|
295
|
+
|
296
|
+
self.set_value(value + delta)
|
297
|
+
event.consumed = True
|
298
|
+
|
299
|
+
|
300
|
+
def get_min_required_size(self) -> tuple[float, float]:
|
301
|
+
left = self.text_widget.get_min_required_size()
|
302
|
+
right = self.meter.get_min_required_size()
|
303
|
+
gap = self.gap if self.text_widget.text else 0
|
304
|
+
full_rect = pygame.FRect(0, 0, left[0] + right[0] + gap, left[1])
|
305
|
+
full_rect.h += self.unpressed_relief
|
306
|
+
return self.expand_rect_with_padding((0, 0, *full_rect.size)).size
|
307
|
+
|
308
|
+
def _align_composed(self, left: Shape, right: Shape):
|
309
|
+
full_rect = self.get_inner_rect()
|
310
|
+
left_rect = left.rect
|
311
|
+
right_rect = right.rect
|
312
|
+
gap = {
|
313
|
+
bf.spacing.MIN: 0,
|
314
|
+
bf.spacing.HALF: (full_rect.width - left_rect.width - right_rect.width) // 2,
|
315
|
+
bf.spacing.MAX: full_rect.width - left_rect.width - right_rect.width,
|
316
|
+
bf.spacing.MANUAL: self.gap
|
317
|
+
}.get(self.spacing, 0)
|
318
|
+
gap = max(0, gap)
|
319
|
+
combined_width = left_rect.width + right_rect.width + gap
|
320
|
+
group_x = {
|
321
|
+
bf.alignment.LEFT: full_rect.left,
|
322
|
+
bf.alignment.MIDLEFT: full_rect.left,
|
323
|
+
bf.alignment.RIGHT: full_rect.right - combined_width,
|
324
|
+
bf.alignment.MIDRIGHT: full_rect.right - combined_width,
|
325
|
+
bf.alignment.CENTER: full_rect.centerx - combined_width // 2
|
326
|
+
}.get(self.alignment, full_rect.left)
|
327
|
+
left.set_position(x=group_x)
|
328
|
+
right.set_position(x=group_x + left_rect.width + gap)
|
329
|
+
# Set vertical positions
|
330
|
+
if self.alignment in {bf.alignment.TOP, bf.alignment.TOPLEFT, bf.alignment.TOPRIGHT}:
|
331
|
+
left.set_position(y=full_rect.top)
|
332
|
+
right.set_position(y=full_rect.top)
|
333
|
+
elif self.alignment in {bf.alignment.BOTTOM, bf.alignment.BOTTOMLEFT, bf.alignment.BOTTOMRIGHT}:
|
334
|
+
left.set_position(y=full_rect.bottom - left_rect.height)
|
335
|
+
right.set_position(y=full_rect.bottom - right_rect.height)
|
336
|
+
else:
|
337
|
+
left.set_center(y=full_rect.centery)
|
338
|
+
right.set_center(y=full_rect.centery)
|
339
|
+
|
340
|
+
def build(self) -> None:
|
341
|
+
res = super().build()
|
342
|
+
gap = self.gap if self.spacing == bf.spacing.MANUAL else 0
|
343
|
+
if self.meter.axis==bf.axis.HORIZONTAL:
|
344
|
+
meter_width = self.get_inner_width() - self.text_widget.rect.w - gap
|
345
|
+
meter_height = min(self.meter.get_min_required_size()[1], self.text_widget.rect.h)
|
346
|
+
else:
|
347
|
+
meter_height = self.get_inner_height()
|
348
|
+
meter_width = min(self.text_widget.rect.h,max(self.meter.get_min_required_size()[0],self.get_inner_width() - self.text_widget.rect.w - gap))
|
349
|
+
|
350
|
+
meter_size = self.meter.resolve_size((meter_width, meter_height))
|
351
|
+
self.meter.set_size(meter_size)
|
352
|
+
self._align_composed(self.text_widget, self.meter)
|
353
|
+
return res
|
@@ -0,0 +1,49 @@
|
|
1
|
+
from ..utils import Singleton
|
2
|
+
from .widget import Widget
|
3
|
+
from .style import Style
|
4
|
+
import batFramework as bf
|
5
|
+
|
6
|
+
class StyleManager(metaclass=Singleton):
|
7
|
+
def __init__(self):
|
8
|
+
self.styles: list[Style] = []
|
9
|
+
self.widgets: set[Widget] = set()
|
10
|
+
self.lookup: dict[Widget, bool] = {}
|
11
|
+
|
12
|
+
def register_widget(self, widget: Widget):
|
13
|
+
if widget in self.widgets:
|
14
|
+
return
|
15
|
+
self.widgets.add(widget)
|
16
|
+
self.lookup[widget] = False
|
17
|
+
self.update()
|
18
|
+
|
19
|
+
def refresh_widget(self, widget: Widget):
|
20
|
+
if widget in self.widgets:
|
21
|
+
self.lookup[widget] = False
|
22
|
+
self.update()
|
23
|
+
|
24
|
+
def remove_widget(self, widget: Widget):
|
25
|
+
if widget not in self.widgets:
|
26
|
+
return
|
27
|
+
self.widgets.remove(widget)
|
28
|
+
self.lookup.pop(widget)
|
29
|
+
|
30
|
+
def add(self, style: Style):
|
31
|
+
self.styles.append(style)
|
32
|
+
self.lookup = {key: False for key in self.lookup}
|
33
|
+
self.update()
|
34
|
+
|
35
|
+
def update_forced(self):
|
36
|
+
for style in self.styles:
|
37
|
+
for widget in self.widgets:
|
38
|
+
style.apply(widget)
|
39
|
+
for key in self.lookup.keys():
|
40
|
+
self.lookup[key] = True
|
41
|
+
|
42
|
+
def update(self):
|
43
|
+
for style in self.styles:
|
44
|
+
for widget in self.widgets:
|
45
|
+
if self.lookup[widget]:
|
46
|
+
continue
|
47
|
+
style.apply(widget)
|
48
|
+
for key in self.lookup.keys():
|
49
|
+
self.lookup[key] = True
|
@@ -0,0 +1,43 @@
|
|
1
|
+
from typing import TypeVar, Generic, Callable, Any, Self
|
2
|
+
from .widget import Widget
|
3
|
+
from ..entity import Entity
|
4
|
+
|
5
|
+
T = TypeVar('T')
|
6
|
+
|
7
|
+
class SyncedVar(Generic[T]):
|
8
|
+
def __init__(self, value: T = None):
|
9
|
+
self._value: T = value
|
10
|
+
self.modify_callback: Callable[[T], Any] | None = None
|
11
|
+
self._bound_entities: set[tuple[Entity, Callable[[T], Any]]] = set()
|
12
|
+
|
13
|
+
def set_modify_callback(self, callback: Callable[[T], Any]) -> "SyncedVar[T]":
|
14
|
+
self.modify_callback = callback
|
15
|
+
return self
|
16
|
+
|
17
|
+
def bind(self, entity, update_callback: Callable[[T], Any]) -> "SyncedVar[T]":
|
18
|
+
self._bound_entities.add((entity, update_callback))
|
19
|
+
return self
|
20
|
+
|
21
|
+
def unbind(self, entity: Entity) -> "SyncedVar[T]":
|
22
|
+
self._bound_entities = {(e, cb) for e, cb in self._bound_entities if e != entity}
|
23
|
+
return self
|
24
|
+
|
25
|
+
@property
|
26
|
+
def value(self) -> T:
|
27
|
+
return self._value
|
28
|
+
|
29
|
+
def set_value(self,value:T)->Self:
|
30
|
+
self.value = value
|
31
|
+
return self
|
32
|
+
|
33
|
+
@value.setter
|
34
|
+
def value(self, new_value: T):
|
35
|
+
if self._value != new_value:
|
36
|
+
self._value = new_value
|
37
|
+
self.update_bound_entities()
|
38
|
+
if self.modify_callback is not None:
|
39
|
+
self.modify_callback(new_value)
|
40
|
+
|
41
|
+
def update_bound_entities(self):
|
42
|
+
for _, update_callback in self._bound_entities:
|
43
|
+
update_callback(self._value)
|