batframework 1.0.8a13__py3-none-any.whl → 1.0.9a1__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 (42) hide show
  1. batFramework/__init__.py +11 -15
  2. batFramework/animatedSprite.py +1 -1
  3. batFramework/camera.py +1 -1
  4. batFramework/character.py +1 -1
  5. batFramework/constants.py +10 -0
  6. batFramework/cutscene.py +10 -10
  7. batFramework/drawable.py +75 -0
  8. batFramework/dynamicEntity.py +2 -4
  9. batFramework/entity.py +93 -56
  10. batFramework/enums.py +1 -0
  11. batFramework/fontManager.py +3 -3
  12. batFramework/gui/__init__.py +2 -2
  13. batFramework/gui/{dialogueBox.py → animatedLabel.py} +18 -36
  14. batFramework/gui/button.py +30 -0
  15. batFramework/gui/clickableWidget.py +6 -1
  16. batFramework/gui/constraints/constraints.py +90 -1
  17. batFramework/gui/container.py +84 -93
  18. batFramework/gui/debugger.py +1 -1
  19. batFramework/gui/indicator.py +3 -2
  20. batFramework/gui/interactiveWidget.py +43 -24
  21. batFramework/gui/label.py +43 -49
  22. batFramework/gui/layout.py +378 -42
  23. batFramework/gui/root.py +2 -9
  24. batFramework/gui/shape.py +2 -0
  25. batFramework/gui/textInput.py +115 -78
  26. batFramework/gui/toggle.py +1 -4
  27. batFramework/gui/widget.py +50 -38
  28. batFramework/manager.py +65 -53
  29. batFramework/particle.py +1 -1
  30. batFramework/scene.py +1 -34
  31. batFramework/sceneManager.py +6 -34
  32. batFramework/scrollingSprite.py +1 -1
  33. batFramework/sprite.py +2 -2
  34. batFramework/{time.py → timeManager.py} +0 -2
  35. batFramework/utils.py +118 -19
  36. {batframework-1.0.8a13.dist-info → batframework-1.0.9a1.dist-info}/METADATA +1 -1
  37. batframework-1.0.9a1.dist-info/RECORD +63 -0
  38. {batframework-1.0.8a13.dist-info → batframework-1.0.9a1.dist-info}/WHEEL +1 -1
  39. batFramework/object.py +0 -123
  40. batframework-1.0.8a13.dist-info/RECORD +0 -63
  41. {batframework-1.0.8a13.dist-info → batframework-1.0.9a1.dist-info}/LICENCE +0 -0
  42. {batframework-1.0.8a13.dist-info → batframework-1.0.9a1.dist-info}/top_level.txt +0 -0
@@ -598,6 +598,95 @@ class MarginRight(Constraint):
598
598
  other.margin == self.margin
599
599
  )
600
600
 
601
+ class RectMarginBottom(Constraint):
602
+ def __init__(self, margin: float):
603
+ super().__init__()
604
+ self.margin = margin
605
+
606
+ def evaluate(self, parent_widget, child_widget):
607
+ return (
608
+ child_widget.rect.bottom == parent_widget.rect.bottom - self.margin
609
+ )
610
+
611
+ def apply_constraint(self, parent_widget, child_widget):
612
+ child_widget.set_position(
613
+ child_widget.rect.x,
614
+ parent_widget.rect.bottom- child_widget.rect.h - self.margin,
615
+ )
616
+
617
+ def __eq__(self,other:"Constraint")->bool:
618
+ if not isinstance(other,self.__class__):
619
+ return False
620
+ return (
621
+ other.name == self.name and
622
+ other.margin == self.margin
623
+ )
624
+
625
+ class RectMarginTop(Constraint):
626
+ def __init__(self, margin: float):
627
+ super().__init__()
628
+ self.margin = margin
629
+
630
+ def evaluate(self, parent_widget, child_widget):
631
+ return child_widget.rect.top == parent_widget.rect.top + self.margin
632
+
633
+ def apply_constraint(self, parent_widget, child_widget):
634
+ child_widget.set_position(
635
+ child_widget.rect.x, parent_widget.rect.top + self.margin
636
+ )
637
+
638
+ def __eq__(self,other:"Constraint")->bool:
639
+ if not isinstance(other,self.__class__):
640
+ return False
641
+ return (
642
+ other.name == self.name and
643
+ other.margin == self.margin
644
+ )
645
+
646
+ class RectMarginLeft(Constraint):
647
+ def __init__(self, margin: float):
648
+ super().__init__()
649
+ self.margin = margin
650
+
651
+ def evaluate(self, parent_widget, child_widget):
652
+ return child_widget.rect.left == parent_widget.rect.left + self.margin
653
+
654
+ def apply_constraint(self, parent_widget, child_widget):
655
+ if not self.evaluate(parent_widget, child_widget):
656
+ child_widget.set_position(
657
+ parent_widget.rect.left + self.margin, child_widget.rect.y
658
+ )
659
+
660
+ def __eq__(self,other:"Constraint")->bool:
661
+ if not isinstance(other,self.__class__):
662
+ return False
663
+ return (
664
+ other.name == self.name and
665
+ other.margin == self.margin
666
+ )
667
+
668
+ class RectMarginRight(Constraint):
669
+ def __init__(self, margin: float):
670
+ super().__init__()
671
+ self.margin = margin
672
+
673
+ def evaluate(self, parent_widget, child_widget):
674
+ return child_widget.rect.right == parent_widget.rect.right - self.margin
675
+
676
+ def apply_constraint(self, parent_widget, child_widget):
677
+ child_widget.set_position(
678
+ parent_widget.rect.right - child_widget.rect.w - self.margin,
679
+ child_widget.rect.y,
680
+ )
681
+
682
+ def __eq__(self,other:"Constraint")->bool:
683
+ if not isinstance(other,self.__class__):
684
+ return False
685
+ return (
686
+ other.name == self.name and
687
+ other.margin == self.margin
688
+ )
689
+
601
690
  class PercentageMarginBottom(Constraint):
602
691
  def __init__(self, margin: float):
603
692
  super().__init__()
@@ -812,4 +901,4 @@ class PercentageRectMarginRight(Constraint):
812
901
  return (
813
902
  other.name == self.name and
814
903
  other.margin == self.margin
815
- )
904
+ )
@@ -11,181 +11,172 @@ from pygame.math import Vector2
11
11
  class Container(Shape, InteractiveWidget):
12
12
  def __init__(self, layout: Layout = None, *children: Widget) -> None:
13
13
  super().__init__()
14
- self.dirty_children = False
14
+ self.dirty_layout: bool = False
15
15
  self.set_debug_color("green")
16
- self.layout: Layout = layout
17
- self.scroll = Vector2(0, 0)
18
- if not self.layout:
19
- self.layout = Column()
16
+ self.layout = layout if layout else Column()
20
17
  self.layout.set_parent(self)
18
+ self.scroll = Vector2(0, 0)
21
19
  self.add(*children)
22
20
 
23
21
  def __str__(self) -> str:
24
22
  return f"Container({self.uid},{len(self.children)})"
25
23
 
26
24
  def get_min_required_size(self):
27
- if self.layout:
28
- return self.layout.get_auto_size()
29
- return self.rect.size
25
+ return self.layout.get_auto_size() if self.layout else self.rect.size
30
26
 
31
27
  def reset_scroll(self) -> Self:
28
+ if self.scroll == (0,0):
29
+ return self
32
30
  self.scroll.update(0, 0)
33
- self.dirty_children = True
31
+ self.dirty_layout = True
34
32
  return self
35
33
 
36
34
  def set_scroll(self, value: tuple) -> Self:
35
+ if (self.scroll.x,self.scroll.y) == value:
36
+ return self
37
37
  self.scroll.update(value)
38
- self.dirty_children = True
38
+ self.clamp_scroll()
39
+ self.dirty_layout = True
39
40
  return self
40
41
 
41
42
  def scrollX_by(self, x: float | int) -> Self:
43
+ if x == 0:
44
+ return self
42
45
  self.scroll.x += x
43
- self.dirty_children = True
46
+ self.clamp_scroll()
47
+ self.dirty_layout = True
44
48
  return self
45
49
 
46
50
  def scrollY_by(self, y: float | int) -> Self:
51
+ if y == 0:
52
+ return self
47
53
  self.scroll.y += y
48
- self.dirty_children = True
54
+ self.clamp_scroll()
55
+ self.dirty_layout = True
49
56
  return self
50
57
 
51
58
  def scroll_by(self, value: tuple[float | int, float | int]) -> Self:
59
+ if value[0] == 0 and value[1] == 0:
60
+ return self
52
61
  self.scroll += value
53
- self.dirty_children = True
62
+ self.clamp_scroll()
63
+ self.dirty_layout = True
54
64
  return self
55
65
 
56
66
  def clamp_scroll(self) -> Self:
57
67
  if not self.children:
58
- return Self
68
+ return self
59
69
  r = self.get_padded_rect()
60
- size = self.children[0].rect.unionall(self.children[1:]).size
70
+ size = self.children[0].rect.unionall([child.rect for child in self.children[1:]]).size
61
71
 
62
- # Calculate the maximum scroll values
63
72
  max_scroll_x = max(0, size[0] - r.width)
64
73
  max_scroll_y = max(0, size[1] - r.height)
65
74
 
66
- # Clamp the scroll values
67
75
  self.scroll.x = max(0, min(self.scroll.x, max_scroll_x))
68
76
  self.scroll.y = max(0, min(self.scroll.y, max_scroll_y))
69
-
70
- self.dirty_children = True
77
+ self.dirty_layout = True
71
78
  return self
72
79
 
73
80
  def set_layout(self, layout: Layout) -> Self:
74
81
  tmp = self.layout
75
82
  self.layout = layout
76
83
  if self.layout != tmp:
77
- self.dirty_children = True
84
+ tmp.set_parent(None)
85
+ self.layout.set_parent(self)
86
+ self.dirty_layout = True
78
87
  return self
79
88
 
80
89
  def get_interactive_children(self) -> list[InteractiveWidget]:
81
- return [
82
- child
83
- for child in self.children
84
- if isinstance(child, InteractiveWidget) and child.allow_focus_to_self()
85
- ]
86
-
87
- def focus_next_child(self) -> None:
88
- self.layout.focus_next_child()
89
-
90
- def focus_prev_child(self) -> None:
91
- self.layout.focus_prev_child()
90
+ return [child for child in self.children if isinstance(child, InteractiveWidget) and not isinstance(child,Container) and child.allow_focus_to_self()]
92
91
 
93
92
  def clear_children(self) -> None:
94
93
  self.children.clear()
95
- self.dirty_children = True
94
+ self.dirty_layout = True
96
95
 
97
96
  def add(self, *child: Widget) -> Self:
98
97
  super().add(*child)
99
- self.dirty_children = True
98
+ self.dirty_shape = True
99
+ self.clamp_scroll()
100
100
  return self
101
101
 
102
102
  def remove(self, *child: Widget) -> Self:
103
103
  super().remove(*child)
104
- self.dirty_children = True
104
+ self.dirty_shape = True
105
+ self.clamp_scroll()
105
106
  return self
106
107
 
107
- def resolve_constraints(self) -> None:
108
- super().resolve_constraints()
109
-
110
108
  def top_at(self, x: float | int, y: float | int) -> "None|Widget":
111
109
  if self.visible and self.rect.collidepoint(x, y):
112
- if self.children:
113
- for child in reversed(self.children):
114
- r = child.top_at(x, y)
115
- if r is not None:
116
- return r
110
+ for child in reversed(self.children):
111
+ result = child.top_at(x, y)
112
+ if result is not None:
113
+ return result
117
114
  return self
118
115
  return None
119
116
 
120
117
  def get_focus(self) -> bool:
121
- res = super().get_focus()
122
- if not res:
118
+ if not super().get_focus():
123
119
  return False
124
- l = l = self.get_interactive_children()
125
- if not l:
120
+ interactive_children = self.get_interactive_children()
121
+ if not interactive_children:
126
122
  return True
127
- self.focused_index = min(self.focused_index, len(l))
128
- return l[self.focused_index].get_focus()
123
+ self.focused_index = min(self.focused_index, len(interactive_children) - 1)
124
+ return interactive_children[self.focused_index].get_focus()
129
125
 
130
- def do_handle_event(self, event):
131
- self.layout.handle_event(event)
126
+ def do_handle_event(self, event) -> None:
127
+ if any(child.is_focused for child in self.get_interactive_children()):
128
+ self.layout.handle_event(event)
132
129
 
133
130
  def set_focused_child(self, child: InteractiveWidget) -> bool:
134
- l = self.get_interactive_children()
131
+ interactive_children = self.get_interactive_children()
135
132
  try:
136
- i = l.index(child)
133
+ index = interactive_children.index(child)
134
+ self.focused_index = index
135
+ return True
137
136
  except ValueError:
138
137
  return False
139
- if i >= 0:
140
- self.focused_index = i
141
- return True
142
- return False
143
138
 
144
139
  def allow_focus_to_self(self) -> bool:
145
- return len(self.get_interactive_children()) != 0 and self.visible
140
+ return bool(self.get_interactive_children()) and self.visible
146
141
 
147
- def draw(self, camera: bf.Camera) -> None:
148
- constraints_down = False
142
+
143
+ def apply_updates(self):
144
+ if any(child.dirty_shape for child in self.children):
145
+ self.dirty_layout = True # Mark layout as dirty if any child changed size
146
+
147
+ if self.dirty_constraints:
148
+ self.resolve_constraints() # Finalize positioning based on size
149
+
150
+ # Step 1: Build shape if needed
149
151
  if self.dirty_shape:
150
- self.dirty_constraints = True
151
- self.dirty_children = True
152
- self.dirty_surface = True
153
- self.build()
152
+ self.build() # Finalize size of the container
153
+ self.dirty_shape = False
154
+ self.dirty_surface = True # Mark surface for repaint
155
+ self.dirty_layout = True # Mark layout for arrangement
156
+ # Flag all children to update constraints after size is finalized
154
157
  for child in self.children:
155
158
  child.dirty_constraints = True
156
- self.dirty_shape = False
157
159
 
160
+ for child in self.children:
161
+ child.apply_updates()
162
+
163
+ # Step 2: Arrange layout if marked as dirty
164
+ if self.dirty_layout:
165
+ self.layout.arrange()
166
+ self.dirty_surface = True
167
+ self.dirty_layout = False
168
+ # Constraints may need to adjust based on the layout change
169
+ self.dirty_constraints = True
170
+
171
+ # Step 3: Resolve constraints now that size and layout are finalized
158
172
  if self.dirty_constraints:
159
- if self.parent and self.parent.dirty_constraints:
160
- self.parent.visit_up(self.selective_up)
161
- else:
162
- constraints_down = True
163
- if not self.dirty_children:
164
- self.dirty_children = any(c.dirty_shape for c in self.children)
165
- if self.dirty_children:
166
- if self.layout:
167
- self.layout.arrange()
168
- self.dirty_children = False
169
-
170
- if constraints_down:
171
- self.visit(lambda c: c.resolve_constraints())
173
+ self.resolve_constraints() # Finalize positioning based on size
174
+ for child in self.children:
175
+ child.dirty_constraints = True # Children inherit updated positioning
176
+ self.dirty_constraints = False
172
177
 
178
+ # Step 4: Paint the surface if marked as dirty
173
179
  if self.dirty_surface:
180
+ # print("PAINT !!")
174
181
  self.paint()
175
182
  self.dirty_surface = False
176
-
177
- bf.Entity.draw(self, camera)
178
-
179
- if self.clip_children:
180
- new_clip = camera.world_to_screen(self.get_padded_rect())
181
- old_clip = camera.surface.get_clip()
182
- new_clip = new_clip.clip(old_clip)
183
- camera.surface.set_clip(new_clip)
184
- # Draw children with adjusted positions
185
- _ = [
186
- child.draw(camera)
187
- for child in sorted(self.children, key=lambda c: c.render_order)
188
- ]
189
-
190
- if self.clip_children:
191
- camera.surface.set_clip(old_clip)
@@ -72,7 +72,7 @@ class Debugger(Label):
72
72
  def update(self, dt: float) -> None:
73
73
  if not self.parent_scene:
74
74
  return
75
- if self.parent_scene.get_sharedVar("debug_mode") != bf.debugMode.DEBUGGER:
75
+ if bf.ResourceManager().get_sharedVar("debug_mode") != bf.debugMode.DEBUGGER:
76
76
  self.set_visible(False)
77
77
  return
78
78
  self.set_visible(True)
@@ -29,11 +29,11 @@ class Indicator(Shape):
29
29
 
30
30
  class ToggleIndicator(Indicator):
31
31
  def __init__(self, default_value: bool) -> None:
32
+ super().__init__((20, 20))
32
33
  self.value: bool = default_value
33
34
  self.callback = lambda val: self.set_color("green" if val else "red")
34
- super().__init__((20, 20))
35
35
  self.set_value(default_value)
36
-
36
+ self.callback(default_value)
37
37
  # TODO aspect ratio would be good right about here
38
38
  # self.add_constraint(ConstraintAspectRatio(1))
39
39
 
@@ -55,3 +55,4 @@ class ToggleIndicator(Indicator):
55
55
  if r is self:
56
56
  return None
57
57
  return r
58
+
@@ -2,13 +2,13 @@ from .widget import Widget
2
2
  from typing import Self
3
3
  from typing import TYPE_CHECKING
4
4
  import pygame
5
- from math import cos
5
+ from math import cos,floor,ceil
6
6
 
7
7
  if TYPE_CHECKING:
8
8
  from .container import Container
9
9
  import batFramework as bf
10
10
 
11
- def children_has_focus(widget):
11
+ def children_has_focus(widget)->bool:
12
12
  if isinstance(widget,InteractiveWidget) and widget.is_focused:
13
13
  return True
14
14
  for child in widget.children:
@@ -56,6 +56,8 @@ class InteractiveWidget(Widget):
56
56
 
57
57
  def on_get_focus(self) -> None:
58
58
  self.is_focused = True
59
+ if isinstance(self.parent,bf.Container):
60
+ self.parent.layout.scroll_to_widget(self)
59
61
  self.do_on_get_focus()
60
62
 
61
63
  def on_lose_focus(self) -> None:
@@ -84,7 +86,7 @@ class InteractiveWidget(Widget):
84
86
  i_children[index + 1].get_focus()
85
87
  return
86
88
 
87
- if self.parent:
89
+ if self.parent and isinstance(self.parent,InteractiveWidget):
88
90
  self.parent.focus_next_tab(self)
89
91
 
90
92
  def focus_prev_tab(self, previous_widget):
@@ -108,30 +110,18 @@ class InteractiveWidget(Widget):
108
110
  i_children[index - 1].get_focus()
109
111
  return
110
112
 
111
- if self.parent:
113
+ if self.parent and isinstance(self.parent,InteractiveWidget):
112
114
  self.parent.focus_prev_tab(self)
113
115
 
114
- def focus_next_sibling(self) -> None:
115
- if isinstance(self.parent, bf.Container):
116
- self.parent.focus_next_child()
117
-
118
- def focus_prev_sibling(self) -> None:
119
- if isinstance(self.parent, bf.Container):
120
- self.parent.focus_prev_child()
121
-
122
116
  def on_key_down(self, key) -> bool:
123
- if key == pygame.K_DOWN:
124
- self.focus_next_sibling()
125
- elif key == pygame.K_UP:
126
- self.focus_prev_sibling()
127
- elif key == pygame.K_TAB and self.parent:
117
+ if key == pygame.K_TAB and self.parent:
128
118
  keys = pygame.key.get_pressed()
129
119
  if keys[pygame.K_LSHIFT] or keys[pygame.K_RSHIFT]:
130
120
 
131
121
  self.focus_prev_tab(self)
132
122
  else:
133
123
  self.focus_next_tab(self)
134
-
124
+ return True
135
125
  else:
136
126
 
137
127
  return self.do_on_key_down(key)
@@ -192,10 +182,39 @@ class InteractiveWidget(Widget):
192
182
  pass
193
183
 
194
184
  def draw_focused(self, camera: bf.Camera) -> None:
195
- delta = 4 + ((2 * cos(pygame.time.get_ticks() / 100)) // 2) * 2
196
- pygame.draw.rect(
197
- camera.surface,
198
- "white",
199
- self.rect.move(-camera.rect.x, -camera.rect.y),#.inflate(delta, delta),
200
- 1,
185
+
186
+ proportion = 16
187
+ surface = pygame.Surface(self.rect.inflate(proportion,proportion).size)
188
+ surface.fill("black")
189
+
190
+ delta = proportion*0.75 - int(proportion * cos(pygame.time.get_ticks() / 100) /4)
191
+ delta = delta//2 * 2
192
+ # Base rect centered in tmp surface
193
+ base_rect = surface.get_frect()
194
+
195
+ # Expanded white rectangle for border effect
196
+ white_rect = base_rect.inflate(-delta,-delta)
197
+ white_rect.center = base_rect.center
198
+ pygame.draw.rect(surface, "white", white_rect, 2, *self.border_radius)
199
+
200
+ # Black cutout rectangles to create the effect around the edges
201
+ black_rect_1 = white_rect.copy()
202
+ black_rect_1.w -= proportion
203
+ black_rect_1.centerx = white_rect.centerx
204
+
205
+ black_rect_2 = white_rect.copy()
206
+ black_rect_2.h -= proportion
207
+ black_rect_2.centery = white_rect.centery
208
+
209
+ surface.fill("black", black_rect_1)
210
+ surface.fill("black", black_rect_2)
211
+
212
+ base_rect.center = self.rect.center
213
+
214
+ surface.set_colorkey("black")
215
+
216
+ # Blit the tmp surface onto the camera surface with adjusted position
217
+ camera.surface.blit(
218
+ surface,
219
+ base_rect.move(-camera.rect.x, -camera.rect.y),
201
220
  )