batframework 1.0.9a7__py3-none-any.whl → 1.0.9a9__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 (62) hide show
  1. batFramework/__init__.py +20 -11
  2. batFramework/action.py +1 -1
  3. batFramework/animatedSprite.py +47 -116
  4. batFramework/animation.py +30 -5
  5. batFramework/audioManager.py +8 -5
  6. batFramework/baseScene.py +240 -0
  7. batFramework/camera.py +4 -0
  8. batFramework/constants.py +6 -2
  9. batFramework/cutscene.py +221 -21
  10. batFramework/cutsceneManager.py +5 -2
  11. batFramework/drawable.py +7 -5
  12. batFramework/easingController.py +10 -11
  13. batFramework/entity.py +21 -2
  14. batFramework/enums.py +48 -33
  15. batFramework/gui/__init__.py +6 -3
  16. batFramework/gui/animatedLabel.py +10 -2
  17. batFramework/gui/button.py +4 -31
  18. batFramework/gui/clickableWidget.py +63 -50
  19. batFramework/gui/constraints/constraints.py +212 -136
  20. batFramework/gui/container.py +77 -58
  21. batFramework/gui/debugger.py +12 -17
  22. batFramework/gui/draggableWidget.py +21 -17
  23. batFramework/gui/image.py +3 -10
  24. batFramework/gui/indicator.py +56 -1
  25. batFramework/gui/interactiveWidget.py +127 -108
  26. batFramework/gui/label.py +73 -64
  27. batFramework/gui/layout.py +286 -445
  28. batFramework/gui/meter.py +42 -20
  29. batFramework/gui/radioButton.py +20 -69
  30. batFramework/gui/root.py +99 -29
  31. batFramework/gui/selector.py +250 -0
  32. batFramework/gui/shape.py +13 -5
  33. batFramework/gui/slider.py +262 -107
  34. batFramework/gui/syncedVar.py +49 -0
  35. batFramework/gui/textInput.py +46 -22
  36. batFramework/gui/toggle.py +70 -52
  37. batFramework/gui/tooltip.py +30 -0
  38. batFramework/gui/widget.py +222 -135
  39. batFramework/manager.py +7 -8
  40. batFramework/particle.py +4 -1
  41. batFramework/propertyEaser.py +79 -0
  42. batFramework/renderGroup.py +17 -50
  43. batFramework/resourceManager.py +43 -13
  44. batFramework/scene.py +15 -335
  45. batFramework/sceneLayer.py +138 -0
  46. batFramework/sceneManager.py +31 -36
  47. batFramework/scrollingSprite.py +8 -3
  48. batFramework/sprite.py +1 -1
  49. batFramework/templates/__init__.py +1 -2
  50. batFramework/templates/controller.py +97 -0
  51. batFramework/timeManager.py +76 -22
  52. batFramework/transition.py +37 -103
  53. batFramework/utils.py +125 -66
  54. {batframework-1.0.9a7.dist-info → batframework-1.0.9a9.dist-info}/METADATA +24 -3
  55. batframework-1.0.9a9.dist-info/RECORD +67 -0
  56. {batframework-1.0.9a7.dist-info → batframework-1.0.9a9.dist-info}/WHEEL +1 -1
  57. batFramework/character.py +0 -27
  58. batFramework/templates/character.py +0 -43
  59. batFramework/templates/states.py +0 -166
  60. batframework-1.0.9a7.dist-info/RECORD +0 -63
  61. /batframework-1.0.9a7.dist-info/LICENCE → /batframework-1.0.9a9.dist-info/LICENSE +0 -0
  62. {batframework-1.0.9a7.dist-info → batframework-1.0.9a9.dist-info}/top_level.txt +0 -0
@@ -4,7 +4,6 @@ from .shape import Shape
4
4
  from .interactiveWidget import InteractiveWidget
5
5
  from .layout import Layout, Column
6
6
  from typing import Self
7
- import pygame
8
7
  from pygame.math import Vector2
9
8
 
10
9
 
@@ -42,39 +41,37 @@ class Container(Shape, InteractiveWidget):
42
41
  def scrollX_by(self, x: float | int) -> Self:
43
42
  if x == 0:
44
43
  return self
45
- self.scroll.x += x
46
- self.clamp_scroll()
47
- self.dirty_layout = True
44
+ self.set_scroll((self.scroll.x + x, self.scroll.y))
48
45
  return self
49
46
 
50
47
  def scrollY_by(self, y: float | int) -> Self:
51
48
  if y == 0:
52
49
  return self
53
- self.scroll.y += y
54
- self.clamp_scroll()
55
- self.dirty_layout = True
50
+ self.set_scroll((self.scroll.x, self.scroll.y + y))
56
51
  return self
57
52
 
58
53
  def scroll_by(self, value: tuple[float | int, float | int]) -> Self:
59
54
  if value[0] == 0 and value[1] == 0:
60
55
  return self
61
- self.scroll += value
62
- self.clamp_scroll()
63
- self.dirty_layout = True
56
+ self.set_scroll((self.scroll.x + value[0], self.scroll.y + value[1]))
64
57
  return self
65
58
 
66
59
  def clamp_scroll(self) -> Self:
67
60
  if not self.children:
68
61
  return self
69
- r = self.get_padded_rect()
70
- size = self.children[0].rect.unionall([child.rect for child in self.children[1:]]).size
71
-
72
- max_scroll_x = max(0, size[0] - r.width)
73
- max_scroll_y = max(0, size[1] - r.height)
74
-
75
- self.scroll.x = max(0, min(self.scroll.x, max_scroll_x))
76
- self.scroll.y = max(0, min(self.scroll.y, max_scroll_y))
77
- self.dirty_layout = True
62
+ r = self.get_inner_rect()
63
+ # Compute the bounding rect of all children in one go
64
+ children_rect = self.children[0].rect.copy()
65
+ for child in self.children[1:]:
66
+ children_rect.union_ip(child.rect)
67
+ max_scroll_x = max(0, children_rect.width - r.width)
68
+ max_scroll_y = max(0, children_rect.height - r.height)
69
+
70
+ # Clamp scroll values only if needed
71
+ new_x = min(max(self.scroll.x, 0), max_scroll_x)
72
+ new_y = min(max(self.scroll.y, 0), max_scroll_y)
73
+
74
+ self.set_scroll((new_x, new_y))
78
75
  return self
79
76
 
80
77
  def set_layout(self, layout: Layout) -> Self:
@@ -83,11 +80,15 @@ class Container(Shape, InteractiveWidget):
83
80
  if self.layout != tmp:
84
81
  tmp.set_parent(None)
85
82
  self.layout.set_parent(self)
83
+ self.reset_scroll()
86
84
  self.dirty_layout = True
87
85
  return self
88
86
 
89
87
  def get_interactive_children(self) -> list[InteractiveWidget]:
90
- return [child for child in self.children if isinstance(child, InteractiveWidget) and not isinstance(child,Container) and child.allow_focus_to_self()]
88
+ return [child for child in self.get_layout_children() if isinstance(child, InteractiveWidget) and not isinstance(child,Container) and child.allow_focus_to_self()]
89
+
90
+ def get_layout_children(self)->list[Widget]:
91
+ return self.children
91
92
 
92
93
  def clear_children(self) -> None:
93
94
  self.children.clear()
@@ -96,13 +97,13 @@ class Container(Shape, InteractiveWidget):
96
97
  def add(self, *child: Widget) -> Self:
97
98
  super().add(*child)
98
99
  self.dirty_shape = True
99
- self.clamp_scroll()
100
+ self.dirty_layout = True
100
101
  return self
101
102
 
102
103
  def remove(self, *child: Widget) -> Self:
103
104
  super().remove(*child)
104
105
  self.dirty_shape = True
105
- self.clamp_scroll()
106
+ self.dirty_layout = True
106
107
  return self
107
108
 
108
109
  def top_at(self, x: float | int, y: float | int) -> "None|Widget":
@@ -123,60 +124,78 @@ class Container(Shape, InteractiveWidget):
123
124
  self.focused_index = min(self.focused_index, len(interactive_children) - 1)
124
125
  return interactive_children[self.focused_index].get_focus()
125
126
 
127
+ def children_has_focus(self)->bool:
128
+ return any(child.is_focused for child in self.get_interactive_children())
129
+
126
130
  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)
131
+ if event.consumed:
132
+ return
133
+ self.layout.handle_event(event)
129
134
 
130
135
  def set_focused_child(self, child: InteractiveWidget) -> bool:
131
136
  interactive_children = self.get_interactive_children()
132
137
  try:
133
138
  index = interactive_children.index(child)
134
139
  self.focused_index = index
140
+ if self.layout :
141
+ self.layout.scroll_to_widget(child)
135
142
  return True
136
143
  except ValueError:
137
144
  return False
138
145
 
139
146
  def allow_focus_to_self(self) -> bool:
140
147
  return bool(self.get_interactive_children()) and self.visible
148
+
149
+ def build(self) -> None:
150
+ if self.layout is not None:
151
+ # print("I'm building !",self)
152
+ # size = self.expand_rect_with_padding((0,0,*self.layout.get_auto_size())).size
153
+ size = self.layout.get_auto_size()
154
+ self.set_size(self.resolve_size(size))
155
+ super().build()
156
+
157
+ def apply_pre_updates(self):
158
+ if self.dirty_size_constraints or self.dirty_shape:
159
+ self.resolve_constraints(size_only=True)
160
+ self.dirty_size_constraints = False
161
+ self.dirty_position_constraints = True
141
162
 
163
+ if self.dirty_layout:
164
+ self.layout.update_child_constraints()
165
+ self.layout.arrange()
166
+ self.dirty_layout = False
142
167
 
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
168
+ def apply_post_updates(self,skip_draw:bool=False):
169
+ """
170
+ BOTTOM TO TOP
171
+ for cases when widget attributes depend on children attributes
172
+ """
151
173
  if self.dirty_shape:
152
- self.build() # Finalize size of the container
174
+ self.layout.update_child_constraints()
175
+ self.build()
153
176
  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
157
- for child in self.children:
158
- child.dirty_constraints = True
177
+ self.dirty_surface = True
178
+ self.dirty_layout = True
179
+ self.dirty_size_constraints = True
180
+ self.dirty_position_constraints = True
181
+ from .container import Container
182
+ if self.parent and isinstance(self.parent, Container):
183
+ self.parent.dirty_layout = True
184
+ self.parent.dirty_shape = True
159
185
 
160
- for child in self.children:
161
- child.apply_updates()
186
+ # trigger layout or constraint updates in parent
187
+
162
188
 
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
172
- if self.dirty_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
177
-
178
- # Step 4: Paint the surface if marked as dirty
179
- if self.dirty_surface:
180
- # print("PAINT !!")
189
+ # force recheck of constraints
190
+
191
+
192
+ if self.dirty_position_constraints:
193
+ self.resolve_constraints(position_only=True)
194
+ self.dirty_position_constraints= False
195
+
196
+
197
+ if self.dirty_surface and not skip_draw:
181
198
  self.paint()
182
199
  self.dirty_surface = False
200
+
201
+
@@ -2,6 +2,7 @@ from .label import Label
2
2
  from typing import Self,Callable,Any
3
3
  import batFramework as bf
4
4
  import pygame
5
+ import sys
5
6
 
6
7
 
7
8
  def convert_to_int(*args):
@@ -46,7 +47,7 @@ class Debugger(Label):
46
47
 
47
48
  def set_parent_scene(self, scene) -> Self:
48
49
  super().set_parent_scene(scene)
49
- self.set_render_order(99)
50
+ self.set_render_order(sys.maxsize-100)
50
51
  self.update_text()
51
52
  return self
52
53
 
@@ -72,15 +73,19 @@ class Debugger(Label):
72
73
  def update(self, dt: float) -> None:
73
74
  if not self.parent_scene:
74
75
  return
76
+
75
77
  if bf.ResourceManager().get_sharedVar("debug_mode") != bf.debugMode.DEBUGGER:
76
78
  self.set_visible(False)
77
79
  return
80
+
78
81
  self.set_visible(True)
79
82
  self.refresh_counter = self.refresh_counter + (dt * 60)
83
+
80
84
  if self.refresh_counter > self.refresh_rate:
81
85
  self.refresh_counter = 0
82
86
  self.update_text()
83
87
 
88
+
84
89
  def __str__(self) -> str:
85
90
  return "Debugger"
86
91
 
@@ -109,23 +114,13 @@ class BasicDebugger(FPSDebugger):
109
114
  "Resolution", lambda: "x".join(str(i) for i in bf.const.RESOLUTION)
110
115
  )
111
116
  super().do_when_added()
112
- parent_scene = self.parent_scene
113
-
114
117
  self.add_dynamic("Mouse", pygame.mouse.get_pos)
115
- self.add_dynamic(
116
- "World",
117
- lambda: convert_to_int(
118
- *parent_scene.camera.screen_to_world(pygame.mouse.get_pos())
119
- ),
120
- )
121
- self.add_dynamic(
122
- "Hud",
123
- lambda: convert_to_int(
124
- *parent_scene.hud_camera.screen_to_world(pygame.mouse.get_pos())
125
- ),
126
- )
127
- self.add_dynamic("W. Ent.", lambda: parent_scene.get_world_entity_count())
128
- self.add_dynamic("H. Ent.", lambda: parent_scene.get_hud_entity_count())
118
+
119
+ if not hasattr(self.parent_scene,"root"):
120
+ print("Debugger couldn't find 'root' widget in parent scene")
121
+ return
122
+
123
+ parent_scene = self.parent_scene
129
124
 
130
125
  self.add_dynamic(
131
126
  "Hover",
@@ -5,36 +5,40 @@ import pygame
5
5
 
6
6
  class DraggableWidget(InteractiveWidget):
7
7
  def __init__(self, *args, **kwargs) -> None:
8
- self.drag_action = bf.Action("dragging").add_mouse_control(1).set_holding()
9
-
10
8
  self.drag_start = None
11
9
  self.offset = None
10
+ self.click_mask = [True,False,False,False,False]
11
+ self.is_dragged : bool = False # the widget is following the mouse AND the mouse is in the widget
12
+ self.is_dragged_outside : bool = False # the widget is following the mouse BUT the mouse is NOT in the widget
12
13
  super().__init__(*args, **kwargs)
14
+
15
+ def set_click_mask(self,b1=0,b2=0,b3=0,b4=0,b5=0):
16
+ self.click_mask = [b1,b2,b3,b4,b5]
13
17
 
14
- def do_process_actions(self, event: pygame.Event) -> None:
15
- self.drag_action.process_event(event)
16
-
17
- def do_reset_actions(self) -> None:
18
- self.drag_action.reset()
19
-
18
+ def on_click_down(self, button):
19
+ super().on_click_down(button)
20
+ return any(i==j and i== True for i,j in zip(self.is_clicked_down,self.click_mask))
21
+
20
22
  def do_on_drag(
21
- self, drag_start: tuple[float, float], drag_end: tuple[float, float]
23
+ self, drag_start_pos: tuple[float, float], drag_end_pos: tuple[float, float]
22
24
  ) -> None:
23
- self.set_position(drag_end[0] - self.offset[0], drag_end[1] - self.offset[1])
25
+ new_pos = drag_end_pos[0] - self.offset[0], drag_end_pos[1] - self.offset[1]
26
+ if self.rect.topleft != new_pos:
27
+ self.set_position(*new_pos)
24
28
 
25
29
  def update(self, dt: float):
26
- if self.drag_action.active and self.is_clicked_down:
27
- r = self.get_root()
28
- x, y = r.drawing_camera.screen_to_world(pygame.mouse.get_pos())
29
- if self.drag_start == None and self.drag_action.active:
30
+ self.is_dragged_outside = any(i==j and i== True for i,j in zip(pygame.mouse.get_pressed(5),self.click_mask))
31
+ self.is_dragged = any(i==j and i== True for i,j in zip(self.is_clicked_down,self.click_mask))
32
+
33
+ if self.is_dragged and self.is_dragged_outside:
34
+ x, y = self.parent_layer.camera.screen_to_world(pygame.mouse.get_pos())
35
+ if self.drag_start == None:
30
36
  self.offset = x - self.rect.x, y - self.rect.y
31
37
  self.drag_start = x, y
32
- return
33
38
  else:
34
39
  self.do_on_drag(self.drag_start, (x, y))
35
- return
36
40
  else:
37
41
  self.drag_start = None
38
42
  self.offset = None
39
- self.is_clicked_down = False
43
+ self.is_clicked_down = [False]*5
40
44
  super().update(dt)
batFramework/gui/image.py CHANGED
@@ -23,7 +23,7 @@ class Image(Shape):
23
23
  super().paint()
24
24
  if self.original_surface is None:
25
25
  return
26
- padded = self.get_padded_rect().move(-self.rect.x,-self.rect.y)
26
+ padded = self.get_inner_rect().move(-self.rect.x,-self.rect.y)
27
27
  target_size = padded.size
28
28
  if self.original_surface.get_size() != target_size:
29
29
  self.surface.blit(pygame.transform.scale(self.original_surface, target_size), padded.topleft)
@@ -32,17 +32,11 @@ class Image(Shape):
32
32
 
33
33
  def build(self) -> None:
34
34
  if self.original_surface is not None:
35
- self.set_size_if_autoresize(
36
- self.inflate_rect_by_padding((0,0,*self.original_surface.get_size())).size
35
+ self.set_size(
36
+ self.expand_rect_with_padding((0,0,*self.original_surface.get_size())).size
37
37
  )
38
38
  super().build()
39
39
 
40
- def get_min_required_size(self) -> tuple[float, float]:
41
- res = self.rect.size
42
- return self.inflate_rect_by_padding((0, 0, *res)).size
43
-
44
-
45
-
46
40
 
47
41
  def from_path(self, path: str) -> Self:
48
42
  tmp = bf.ResourceManager().get_image(path, self.convert_alpha)
@@ -60,6 +54,5 @@ class Image(Shape):
60
54
  self.original_surface = surface
61
55
  size = self.original_surface.get_size()
62
56
  self.set_size(size)
63
-
64
57
  self.dirty_surface = True
65
58
  return self
@@ -8,6 +8,12 @@ import batFramework as bf
8
8
 
9
9
 
10
10
  class Indicator(Shape):
11
+ """
12
+ Shape intended to be used as icons/indicators
13
+ due to its nature, it overrides the top_at function (it can not be 'seen' by the mouse)
14
+
15
+ """
16
+
11
17
  def __init__(self, size: tuple[int | float] = (10, 10)) -> None:
12
18
  super().__init__(size)
13
19
  self.debug_color = "magenta"
@@ -35,7 +41,7 @@ class ToggleIndicator(Indicator):
35
41
  self.set_value(default_value)
36
42
  self.callback(default_value)
37
43
  # TODO aspect ratio would be good right about here
38
- # self.add_constraint(ConstraintAspectRatio(1))
44
+ self.add_constraints(bf.gui.AspectRatio(1,reference_axis=bf.axis.VERTICAL))
39
45
 
40
46
  def set_callback(self, callback : Callable[[bool],Any]) -> Self:
41
47
  self.callback = callback
@@ -56,3 +62,52 @@ class ToggleIndicator(Indicator):
56
62
  return None
57
63
  return r
58
64
 
65
+ class ArrowIndicator(Indicator):
66
+ def __init__(self,direction:bf.direction):
67
+ super().__init__()
68
+ self.direction : bf.direction = direction
69
+ self.arrow_color = bf.color.WHITE
70
+ self.line_width : int = 1
71
+
72
+ def set_arrow_color(self,color)-> Self:
73
+ self.arrow_color = color
74
+ self.dirty_surface = True
75
+ return self
76
+
77
+ def set_arrow_direction(self,direction:bf.direction)->Self:
78
+ self.direction = direction
79
+ self.dirty_surface = True
80
+ return self
81
+
82
+ def set_arrow_line_width(self,value:int)->Self:
83
+ self.line_width = value
84
+ self.dirty_surface = True
85
+ return self
86
+
87
+ def paint(self):
88
+ super().paint()
89
+ r = self.get_local_inner_rect()
90
+ size = min(r.width, r.height)
91
+ if size %2 == 0:
92
+ size -= 1
93
+ r.width = size
94
+ r.height = size
95
+
96
+ #pixel alignment
97
+ if (self.padding[1]+self.padding[3] )%2 ==0:
98
+ r.height-=1
99
+ if (self.padding[0]+self.padding[2] )%2 ==0:
100
+ r.width-=1
101
+ r.center = self.get_local_inner_rect().center
102
+
103
+ bf.utils.draw_triangle(
104
+ surface = self.surface,
105
+ color = self.arrow_color,
106
+ rect =r,
107
+ direction = self.direction,
108
+ width = self.line_width
109
+
110
+ )
111
+
112
+
113
+