batframework 1.0.6__py3-none-any.whl → 1.0.8a1__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 (59) hide show
  1. batFramework/__init__.py +23 -14
  2. batFramework/action.py +95 -106
  3. batFramework/actionContainer.py +11 -8
  4. batFramework/animatedSprite.py +60 -43
  5. batFramework/audioManager.py +52 -22
  6. batFramework/camera.py +87 -72
  7. batFramework/constants.py +19 -41
  8. batFramework/cutscene.py +12 -11
  9. batFramework/cutsceneBlocks.py +12 -14
  10. batFramework/dynamicEntity.py +5 -4
  11. batFramework/easingController.py +58 -0
  12. batFramework/entity.py +37 -130
  13. batFramework/enums.py +93 -3
  14. batFramework/fontManager.py +15 -7
  15. batFramework/gui/__init__.py +5 -1
  16. batFramework/gui/button.py +6 -144
  17. batFramework/gui/clickableWidget.py +206 -0
  18. batFramework/gui/constraints/__init__.py +1 -0
  19. batFramework/gui/constraints/constraints.py +378 -0
  20. batFramework/gui/container.py +147 -34
  21. batFramework/gui/debugger.py +39 -22
  22. batFramework/gui/dialogueBox.py +69 -43
  23. batFramework/gui/draggableWidget.py +38 -0
  24. batFramework/gui/image.py +33 -28
  25. batFramework/gui/indicator.py +30 -16
  26. batFramework/gui/interactiveWidget.py +72 -20
  27. batFramework/gui/label.py +240 -98
  28. batFramework/gui/layout.py +125 -53
  29. batFramework/gui/meter.py +76 -0
  30. batFramework/gui/radioButton.py +62 -0
  31. batFramework/gui/root.py +72 -48
  32. batFramework/gui/shape.py +257 -49
  33. batFramework/gui/slider.py +217 -2
  34. batFramework/gui/textInput.py +106 -60
  35. batFramework/gui/toggle.py +85 -42
  36. batFramework/gui/widget.py +259 -288
  37. batFramework/manager.py +30 -16
  38. batFramework/object.py +115 -0
  39. batFramework/{particles.py → particle.py} +37 -34
  40. batFramework/renderGroup.py +62 -0
  41. batFramework/resourceManager.py +36 -24
  42. batFramework/scene.py +94 -88
  43. batFramework/sceneManager.py +140 -57
  44. batFramework/scrollingSprite.py +113 -0
  45. batFramework/sprite.py +35 -23
  46. batFramework/tileset.py +34 -52
  47. batFramework/time.py +105 -56
  48. batFramework/transition.py +213 -1
  49. batFramework/utils.py +38 -18
  50. {batframework-1.0.6.dist-info → batframework-1.0.8a1.dist-info}/METADATA +1 -1
  51. batframework-1.0.8a1.dist-info/RECORD +56 -0
  52. {batframework-1.0.6.dist-info → batframework-1.0.8a1.dist-info}/WHEEL +1 -1
  53. batFramework/easing.py +0 -76
  54. batFramework/gui/constraints.py +0 -277
  55. batFramework/gui/frame.py +0 -25
  56. batFramework/transitionManager.py +0 -0
  57. batframework-1.0.6.dist-info/RECORD +0 -50
  58. {batframework-1.0.6.dist-info → batframework-1.0.8a1.dist-info}/LICENCE +0 -0
  59. {batframework-1.0.6.dist-info → batframework-1.0.8a1.dist-info}/top_level.txt +0 -0
@@ -1,96 +1,168 @@
1
1
  import batFramework as bf
2
2
  from .widget import Widget
3
- from .constraints import *
4
- from typing import Self
3
+ from .constraints.constraints import *
4
+ from typing import Self,TYPE_CHECKING
5
5
  import pygame
6
6
 
7
+ if TYPE_CHECKING:
8
+ from .container import Container
7
9
 
8
10
  class Layout:
9
- def __init__(self, parent: Widget = None):
11
+ def __init__(self, parent: "Container" = None):
10
12
  self.parent = parent
11
13
  self.child_constraints: list[Constraint] = []
12
14
  self.children_rect = pygame.FRect(0, 0, 0, 0)
13
15
 
14
16
  def set_child_constraints(self, *constraints) -> Self:
15
17
  self.child_constraints = list(constraints)
16
- self.arrange()
18
+ self.notify_parent()
17
19
  return self
18
20
 
19
21
  def set_parent(self, parent: Widget):
20
22
  self.parent = parent
21
- self.arrange()
23
+ self.notify_parent()
24
+
25
+ def notify_parent(self) -> None:
26
+ if self.parent:self.parent.dirty_children = True
22
27
 
23
28
  def arrange(self) -> None:
24
- raise NotImplementedError("Subclasses must implement arrange method")
29
+ return
30
+
31
+ def clamp_scroll(self)->None:
32
+ return
33
+
34
+ def get_raw_size(self)->tuple[float,float]:
35
+ return self.parent.rect.size if self.parent else 0,0
36
+
37
+ def get_auto_size(self)->tuple[float,float]:
38
+ return self.parent.rect.size if self.parent else 0,0
39
+
40
+ def focus_next_child(self)->None:
41
+ l = self.parent.get_interactive_children()
42
+ self.parent.focused_index = min(self.parent.focused_index + 1, len(l) - 1)
43
+ focused = l[self.parent.focused_index]
44
+ focused.get_focus()
45
+ self.scroll_to_widget(focused)
46
+
47
+ def focus_prev_child(self)->None:
48
+ l = self.parent.get_interactive_children()
49
+ self.parent.focused_index = max(self.parent.focused_index - 1, 0)
50
+ focused = l[self.parent.focused_index]
51
+ focused.get_focus()
52
+ self.scroll_to_widget(focused)
53
+
54
+ def scroll_to_widget(self,widget:Widget)->None:
55
+ padded = self.parent.get_padded_rect()
56
+ r = widget.rect
57
+ if padded.contains(r):return
58
+ clamped = r.clamp(padded)
59
+ dx,dy = clamped.move(-r.x,-r.y).topleft
60
+ self.parent.scroll_by((-dx,-dy))
61
+
25
62
 
26
63
 
27
64
  class Column(Layout):
28
- def __init__(self, gap: int = 0, fit_children: bool = True, center: bool = True):
65
+ def __init__(self, gap: int = 0,spacing:bf.spacing = bf.spacing.MANUAL):
29
66
  super().__init__()
30
67
  self.gap = gap
31
- self.fit_children: bool = fit_children
32
- self.center = center
68
+ self.spacing = spacing
33
69
 
34
- def arrange(self) -> None:
35
- if not self.parent or not self.parent.children:
36
- return
70
+ def set_gap(self, value: float) -> Self:
71
+ self.gap = value
72
+ self.notify_parent()
73
+ return self
74
+
75
+ def set_spacing(self,spacing: bf.spacing)->Self:
76
+ self.spacing = spacing
77
+ self.notify_parent()
78
+ return self
79
+
80
+ def get_raw_size(self)-> tuple[float,float]:
37
81
  len_children = len(self.parent.children)
82
+ if not len_children : return self.parent.rect.size
38
83
  parent_height = sum(c.get_min_required_size()[1] for c in self.parent.children)
39
84
  parent_width = max(c.get_min_required_size()[0] for c in self.parent.children)
40
85
  if self.gap:
41
86
  parent_height += (len_children - 1) * self.gap
42
- self.children_rect.update(
43
- self.parent.get_content_left(),
44
- self.parent.get_content_top(),
45
- parent_width,
46
- parent_height,
47
- )
48
- if self.fit_children:
49
- self.parent.set_size(parent_width,parent_height)
50
- if self.center:
51
- self.children_rect.center = self.parent.rect.center
52
-
53
- current_y = self.children_rect.top
54
-
55
- for child in self.parent.children:
56
- child.set_position(self.parent.rect.x, current_y)
57
- current_y += child.rect.h + self.gap
58
- for c in self.child_constraints:
59
- child.add_constraints(c)
87
+ target_rect = self.parent.inflate_rect_by_padding((0,0,parent_width,parent_height))
88
+ return target_rect.size
89
+
90
+ def get_auto_size(self)-> tuple[float,float]:
91
+ target_size = list(self.get_raw_size())
92
+ if not self.parent.autoresize_w : target_size[0] = self.parent.rect.w
93
+ if not self.parent.autoresize_h : target_size[1] = self.parent.rect.h
94
+ return target_size
60
95
 
96
+ def arrange(self) -> None:
97
+ if not self.parent or not self.parent.children:
98
+ return
99
+ if self.child_constraints :
100
+ for child in self.parent.children : child.add_constraints(*self.child_constraints)
101
+ self.child_rect = self.parent.get_padded_rect()
102
+
103
+ if self.parent.autoresize_w or self.parent.autoresize_h:
104
+ width,height = self.get_auto_size()
105
+ if self.parent.rect.size != (width,height):
106
+ self.parent.set_size((width,height))
107
+ self.parent.build()
108
+ self.arrange()
109
+ return
110
+ self.child_rect.move_ip(-self.parent.scroll.x,-self.parent.scroll.y)
111
+ y = self.child_rect.top
112
+ for child in self.parent.children :
113
+ child.set_position(self.child_rect.x,y)
114
+ y+= child.get_min_required_size()[1] + self.gap
61
115
 
62
116
  class Row(Layout):
63
- def __init__(self, gap: int = 0, fit_children: bool = True, center: bool = True):
117
+ def __init__(self, gap: int = 0,spacing: bf.spacing=bf.spacing.MANUAL):
64
118
  super().__init__()
65
119
  self.gap = gap
66
- self.fit_children: bool = fit_children
67
- self.center = center
120
+ self.spacing = spacing
68
121
 
69
- def arrange(self) -> None:
70
- if not self.parent or not self.parent.children:
71
- return
122
+ def set_gap(self, value: float) -> Self:
123
+ self.gap = value
124
+ self.notify_parent()
125
+ return self
126
+
127
+ def set_spacing(self,spacing: bf.spacing)->Self:
128
+ self.spacing = spacing
129
+ self.notify_parent()
130
+ return self
72
131
 
132
+
133
+ def get_raw_size(self) -> tuple[float, float]:
73
134
  len_children = len(self.parent.children)
135
+ if not len_children: return self.parent.rect.size
74
136
  parent_width = sum(c.get_min_required_size()[0] for c in self.parent.children)
75
137
  parent_height = max(c.get_min_required_size()[1] for c in self.parent.children)
76
-
77
138
  if self.gap:
78
139
  parent_width += (len_children - 1) * self.gap
79
- self.children_rect.update(
80
- self.parent.get_content_left(),
81
- self.parent.get_content_top(),
82
- parent_width,
83
- parent_height,
84
- )
85
- if self.center:
86
- self.children_rect.center = self.parent.get_content_center()
87
- if self.fit_children:
88
- self.parent.set_size(parent_width,parent_height)
89
-
90
- current_x = self.children_rect.left
140
+ target_rect = self.parent.inflate_rect_by_padding((0, 0, parent_width, parent_height))
141
+
142
+ return target_rect.size
143
+
144
+ def get_auto_size(self) -> tuple[float, float]:
145
+ target_size = list(self.get_raw_size())
146
+ if not self.parent.autoresize_w: target_size[0] = self.parent.rect.w
147
+ if not self.parent.autoresize_h: target_size[1] = self.parent.rect.h
148
+ return target_size
91
149
 
150
+ def arrange(self) -> None:
151
+ if not self.parent or not self.parent.children:
152
+ return
153
+ if self.child_constraints:
154
+ for child in self.parent.children: child.add_constraints(*self.child_constraints)
155
+ self.child_rect = self.parent.get_padded_rect()
156
+
157
+ if self.parent.autoresize_w or self.parent.autoresize_h:
158
+ width, height = self.get_auto_size()
159
+ if self.parent.rect.size != (width, height):
160
+ self.parent.set_size((width, height))
161
+ self.parent.build()
162
+ self.arrange()
163
+ return
164
+ self.child_rect.move_ip(-self.parent.scroll.x, -self.parent.scroll.y)
165
+ x = self.child_rect.left
92
166
  for child in self.parent.children:
93
- child.set_position(current_x, self.parent.rect.y)
94
- current_x += child.rect.w + self.gap
95
- for c in self.child_constraints:
96
- child.add_constraints(c)
167
+ child.set_position(x, self.child_rect.y)
168
+ x += child.get_min_required_size()[0] + self.gap
@@ -0,0 +1,76 @@
1
+ import batFramework as bf
2
+ from .shape import Shape
3
+ from typing import Self
4
+
5
+
6
+ def custom_top_at(self, x, y):
7
+ if Shape.top_at(self, x, y) == self:
8
+ return self.parent
9
+ return None
10
+
11
+
12
+ class Meter(Shape):
13
+ def __init__(
14
+ self,
15
+ min_value: float = 0,
16
+ max_value: float = 1,
17
+ step: float = 0.1
18
+ ):
19
+ super().__init__()
20
+ self.min_value, self.max_value = min_value,max_value
21
+ self.step = step
22
+ self.snap: bool = False
23
+ self.value = self.max_value
24
+ self.content = Shape((0, 0)).set_color(bf.color.BLUE)
25
+ self.content.top_at = lambda x, y: custom_top_at(self.content, x, y)
26
+ self.add(self.content)
27
+ self.set_padding(1)
28
+ self.set_outline_width(1)
29
+ self.set_outline_color(bf.color.BLACK)
30
+ self.set_debug_color("pink")
31
+
32
+ def __str__(self) -> str:
33
+ return "Meter"
34
+
35
+ def set_step(self, step: float) -> Self:
36
+ self.step = step
37
+ self.set_value(self.value)
38
+ return self
39
+
40
+ def set_range(self, range_min: float, range_max: float) -> Self:
41
+ if range_min >= range_max:
42
+ print(
43
+ f"[Warning] : minimum value {range_min} is greater than or equal to maximum value {range_max}"
44
+ )
45
+ return self
46
+ self.min_value, self.max_value = range_min, range_max
47
+ self.dirty_shape = True
48
+
49
+ def get_debug_outlines(self):
50
+ yield from super().get_debug_outlines()
51
+ yield from self.content.get_debug_outlines()
52
+
53
+ def set_value(self, value: float) -> Self:
54
+ value = max(self.min_value, min(self.max_value, value))
55
+ value = round(value / self.step) * self.step
56
+ self.value = value
57
+ self.dirty_surface = True
58
+ return self
59
+
60
+ def get_value(self) -> float:
61
+ return self.value
62
+
63
+ def get_range(self) -> float:
64
+ return self.max_value - self.min_value
65
+
66
+ def get_ratio(self) -> float:
67
+ return self.value / (self.max_value - self.min_value)
68
+
69
+ def _build_content(self) -> None:
70
+ width = (self.get_padded_width() * self.get_ratio())
71
+ self.content.set_size((width, self.get_padded_height()))
72
+ self.content.set_position(*self.get_padded_rect().topleft)
73
+
74
+ def build(self) -> None:
75
+ super().build()
76
+ self._build_content()
@@ -0,0 +1,62 @@
1
+ import batFramework as bf
2
+ from typing import Self,Any,Callable
3
+ from .toggle import Toggle
4
+
5
+ class RadioVariable:...
6
+
7
+ class RadioButton(Toggle):
8
+ def __init__(self, text: str, radio_value:Any = None) -> None:
9
+ super().__init__(text,None, False)
10
+ self.radio_value : Any = radio_value if radio_value is not None else text if text else None
11
+ self.radio_variable : RadioVariable = None
12
+
13
+ def set_radio_value(self,value:Any)->Self:
14
+ self.radio_value = value
15
+ self.update_radio()
16
+ return self
17
+
18
+ def set_text(self, text: str) -> Self:
19
+ flag = False
20
+ if self.value == self.text or self.value is None: flag = True
21
+ super().set_text(text)
22
+ if flag:
23
+ self.set_radio_value(self.text)
24
+ return self
25
+
26
+ def click(self) -> None:
27
+ if self.radio_variable is None: return
28
+ self.radio_variable.set_value(self.radio_value)
29
+
30
+ class RadioVariable:
31
+ def __init__(self)->None:
32
+ self.buttons : list[RadioButton] = []
33
+ self.value = None
34
+ self.modify_callback : Callable[[Any],] = None
35
+
36
+ def link(self,*buttons:RadioButton)->Self:
37
+ if not buttons : return self
38
+ for b in buttons :
39
+ b.radio_variable = self
40
+ self.buttons.extend(buttons)
41
+
42
+ if self.value is None:
43
+ self.set_value(buttons[0].radio_value)
44
+ else:
45
+ self.update_buttons()
46
+ return self
47
+
48
+ def unlink(self,*buttons)->Self:
49
+ for b in self.buttons:
50
+ if b in buttons:
51
+ b.radio_variable = None
52
+ self.buttons = [b for b in self.buttons if b not in buttons]
53
+ return self
54
+
55
+ def set_value(self,value:Any):
56
+ if value == self.value: return
57
+ self.value = value
58
+ if self.modify_callback :self.modify_callback(value)
59
+ self.update_buttons()
60
+
61
+ def update_buttons(self):
62
+ _ = [b.set_value(b.radio_value == self.value,False) for b in self.buttons]
batFramework/gui/root.py CHANGED
@@ -5,36 +5,39 @@ import pygame
5
5
 
6
6
 
7
7
  class Root(InteractiveWidget):
8
- def __init__(self,camera)->None:
8
+ def __init__(self, camera) -> None:
9
9
  super().__init__()
10
- self.drawing_camera = camera
11
- self.surface = None
12
- self.set_root()
10
+ self.is_root = True
11
+ self.drawing_camera: bf.Camera = camera
12
+ self.visible = False
13
13
  self.rect.size = pygame.display.get_surface().get_size()
14
- self.focused: InteractiveWidget |None= self
14
+ self.focused: InteractiveWidget | None = self
15
15
  self.hovered: Widget | None = self
16
- self.set_debug_color("purple")
16
+ self.set_debug_color("yellow")
17
+ self.set_render_order(999)
18
+ self.clip_children = False
17
19
 
18
- def to_string(self) -> str:
19
- return "ROOT"
20
+ def __str__(self) -> str:
21
+ return "Root"
20
22
 
21
- def to_string_id(self) -> str:
22
- return "ROOT"
23
-
24
23
  def get_focused(self) -> Widget | None:
25
24
  return self.focused
26
25
 
27
26
  def get_hovered(self) -> Widget | None:
28
27
  return self.hovered
29
28
 
30
- def clear_focused(self)->None:
29
+ def clear_focused(self) -> None:
31
30
  self.focus_on(None)
32
31
 
33
- def clear_hovered(self)->None:
34
- if isinstance(self.hovered,InteractiveWidget): self.hovered.on_exit()
32
+ def clear_hovered(self) -> None:
33
+ if isinstance(self.hovered, InteractiveWidget):
34
+ self.hovered.on_exit()
35
35
  self.hovered = None
36
36
 
37
- def focus_on(self, widget: InteractiveWidget|None) -> None:
37
+ def focus_on(self, widget: InteractiveWidget | None) -> None:
38
+ if widget == self.focused : return
39
+ if widget and not widget.allow_focus_to_self():
40
+ return
38
41
  if self.focused is not None:
39
42
  self.focused.on_lose_focus()
40
43
  if widget is None:
@@ -43,59 +46,80 @@ class Root(InteractiveWidget):
43
46
  self.focused = widget
44
47
  self.focused.on_get_focus()
45
48
 
46
- def set_size(self, width: float, height: float, force: bool = False) -> "Root":
49
+ def get_by_tags(self,*tags)->list[Widget]:
50
+ res = []
51
+ def getter(w:Widget):
52
+ nonlocal res
53
+ if any(t in w.tags for t in tags):
54
+ res.append(w)
55
+ self.visit(getter)
56
+ return res
57
+
58
+ def get_by_uid(self,uid:int)->Widget:
59
+ def helper(w: Widget,uid):
60
+ if w.uid == uid:
61
+ return w
62
+ for child in w.children:
63
+ res = helper(child,uid)
64
+ if res :
65
+ return child
66
+
67
+ return None
68
+ return helper(self,uid)
69
+
70
+ def set_size(self, size: tuple[float, float], force: bool = False) -> "Root":
47
71
  if not force:
48
72
  return self
49
- self.rect.size = width, height
50
- self.build(apply_constraints=True)
73
+ self.rect.size = size
74
+ # self.build(resolve_constraints=True)
51
75
  return self
52
76
 
53
- def build(self, apply_constraints: bool = False) -> None:
54
- if apply_constraints:
55
- self.apply_all_constraints()
56
- for child in self.children:
57
- child.build()
58
77
 
59
78
  def do_handle_event(self, event):
60
- if event.type == pygame.VIDEORESIZE:
61
- self.set_size(event.w, event.h, force=True)
62
- return True
79
+ # if event.type == pygame.VIDEORESIZE:
80
+ # self.set_size(event.w, event.h, force=True)
81
+ # return True
82
+ if self.focused:
83
+ if event.type == pygame.KEYDOWN:
84
+ self.focused.on_key_down(event.key)
85
+ if event.type == pygame.KEYUP:
86
+ self.focused.on_key_up(event.key)
87
+ elif not self.hovered or not isinstance(self.hovered, InteractiveWidget):
88
+ return
63
89
  if event.type == pygame.MOUSEBUTTONDOWN:
64
- if self.hovered and isinstance(self.hovered,InteractiveWidget) :
65
- self.hovered.on_click_down(event.button)
66
- if event.type == pygame.MOUSEBUTTONUP:
67
- if self.hovered and isinstance(self.hovered,InteractiveWidget) :
68
- self.hovered.on_click_up(event.button)
69
-
70
- return False
90
+ self.hovered.on_click_down(event.button)
91
+ elif event.type == pygame.MOUSEBUTTONUP:
92
+ self.hovered.on_click_up(event.button)
71
93
 
72
- def get_root(self) -> "Root" :
73
- return self
74
94
 
75
- def do_on_click_down(self,button:int)->None:
76
- if button == 1 : self.clear_focused()
95
+ def do_on_click_down(self, button: int) -> None:
96
+ if button == 1:
97
+ self.clear_focused()
77
98
 
78
- def top_at(self, x: float|int, y: float|int) -> "None|Widget":
99
+ def top_at(self, x: float | int, y: float | int) -> "None|Widget":
79
100
  if self.children:
80
101
  for child in reversed(self.children):
81
102
  r = child.top_at(x, y)
82
103
  if r is not None:
83
104
  return r
84
- return self if self.rect.collidepoint(x,y) else None
105
+ return self if self.rect.collidepoint(x, y) else None
85
106
 
86
107
  def update(self, dt: float) -> None:
87
108
  super().update(dt)
88
109
  old = self.hovered
89
- transposed = self.drawing_camera.convert_screen_to_world(*pygame.mouse.get_pos())
90
- self.hovered = (
91
- self.top_at(*transposed)
92
- if self.top_at(*transposed)
93
- else None
94
- )
95
- if old == self.hovered:
110
+ transposed = self.drawing_camera.screen_to_world(pygame.mouse.get_pos())
111
+ self.hovered = self.top_at(*transposed) if self.top_at(*transposed) else None
112
+ if old == self.hovered and isinstance(self.hovered, InteractiveWidget):
113
+ self.hovered.on_mouse_motion(*transposed)
96
114
  return
97
- if old and isinstance(old,InteractiveWidget):
115
+ if old and isinstance(old, InteractiveWidget):
98
116
  old.on_exit()
99
- if self.hovered and isinstance(self.hovered,InteractiveWidget) :
117
+ if self.hovered and isinstance(self.hovered, InteractiveWidget):
100
118
  self.hovered.on_enter()
101
119
 
120
+ def draw(self, camera: bf.Camera) -> int:
121
+ super().draw(camera)
122
+ if self.focused and self.focused!=self:
123
+ self.focused.draw_focused(camera)
124
+
125
+