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,60 +1,173 @@
1
1
  import batFramework as bf
2
2
  from .widget import Widget
3
- from .layout import Layout,Column
4
- from .constraints import Constraint
3
+ from .shape import Shape
4
+ from .interactiveWidget import InteractiveWidget
5
+ from .layout import Layout, Column
5
6
  from typing import Self
7
+ import pygame
8
+ from pygame.math import Vector2
6
9
 
7
- class Container(Widget):
8
- def __init__(self, layout: Layout = Column(), *children: Widget)->None:
10
+
11
+ class Container(Shape, InteractiveWidget):
12
+ def __init__(self, layout: Layout = None, *children: Widget) -> None:
9
13
  super().__init__()
14
+ self.dirty_children = False
10
15
  self.set_debug_color("green")
11
- self.surface = None
12
16
  self.layout: Layout = layout
17
+ self.scroll = Vector2(0, 0)
18
+ if not self.layout:
19
+ self.layout = Column()
20
+ self.layout.set_parent(self)
21
+ self.add(*children)
22
+
23
+ def get_min_required_size(self):
13
24
  if self.layout:
14
- self.layout.set_parent(self)
15
- for child in children:
16
- self.add_child(child)
25
+ return self.layout.get_auto_size()
26
+ return self.rect.size
27
+
28
+ def reset_scroll(self) -> Self:
29
+ self.scroll.update(0, 0)
30
+ self.dirty_children = True
31
+ return self
32
+
33
+ def set_scroll(self, value: tuple) -> Self:
34
+ self.scroll.update(value)
35
+ self.dirty_children = True
36
+ return self
37
+
38
+ def scrollX_by(self, x: float | int) -> Self:
39
+ self.scroll.x += x
40
+ self.dirty_children = True
41
+ return self
17
42
 
18
- def set_padding(self,value)->Self:
43
+ def scrollY_by(self, y: float | int) -> Self:
44
+ self.scroll.y += y
45
+ self.dirty_children = True
46
+ return self
47
+
48
+ def scroll_by(self, value: tuple[float | int, float | int]) -> Self:
49
+ self.scroll += value
50
+ self.dirty_children = True
19
51
  return self
20
52
 
21
53
  def set_layout(self, layout: Layout) -> Self:
54
+ tmp = self.layout
22
55
  self.layout = layout
23
- self.apply_constraints()
56
+ if self.layout != tmp:
57
+ self.dirty_children = True
24
58
  return self
25
59
 
26
- def get_bounding_box(self):
60
+
61
+ def get_debug_outlines(self):
27
62
  yield (self.rect, self.debug_color)
63
+ yield (self.get_padded_rect(), self.debug_color)
64
+
28
65
  for child in self.children:
29
- yield from child.get_bounding_box()
66
+ yield from child.get_debug_outlines()
67
+ # for data in child.get_debug_outlines():
68
+
69
+ def get_interactive_children(self) -> list[InteractiveWidget]:
70
+ return [
71
+ child for child in self.children if isinstance(child, InteractiveWidget) and child.allow_focus_to_self()
72
+ ]
73
+
74
+ def focus_next_child(self) -> None:
75
+ self.layout.focus_next_child()
76
+
77
+ def focus_prev_child(self) -> None:
78
+ self.layout.focus_prev_child()
30
79
 
31
80
  def clear_children(self) -> None:
32
81
  self.children.clear()
33
- self.apply_constraints()
82
+ self.dirty_children = True
34
83
 
35
- def add_child(self, *child: Widget) -> None:
36
- super().add_child(*child)
37
- if self.layout:
38
- self.layout.arrange()
84
+ def add(self, *child: Widget) -> Self:
85
+ super().add(*child)
86
+ self.dirty_children = True
87
+ return self
39
88
 
40
- def remove_child(self, child: Widget) -> None:
41
- super().remove_child(child)
42
- if self.layout:
43
- self.layout.arrange()
89
+ def remove(self, *child: Widget) -> Self:
90
+ super().remove(*child)
91
+ self.dirty_children = True
92
+ return self
44
93
 
45
- def build(self) -> None:
46
- super().build()
47
- if self.layout:
48
- self.layout.arrange()
94
+ def resolve_constraints(self) -> None:
95
+ super().resolve_constraints()
49
96
 
50
- def apply_constraints(self) -> None:
51
- super().apply_constraints()
52
- if self.layout:
53
- self.layout.arrange()
97
+ def __str__(self) -> str:
98
+ return f"Container({self.uid},{len(self.children)})"
99
+
100
+ def top_at(self, x: float | int, y: float | int) -> "None|Widget":
101
+ if self.visible and self.rect.collidepoint(x, y):
102
+ if self.children:
103
+ for child in reversed(self.children):
104
+ r = child.top_at(x,y)
105
+ if r is not None:
106
+ return r
107
+ return self
108
+ return None
109
+
110
+ def get_focus(self) -> bool:
111
+ res = super().get_focus()
112
+ if not res:
113
+ return False
114
+ l = l = self.get_interactive_children()
115
+ if not l:
116
+ return True
117
+ self.focused_index = min(self.focused_index, len(l))
118
+ return l[self.focused_index].get_focus()
119
+
120
+ def set_focused_child(self, child: InteractiveWidget) -> bool:
121
+ l = self.get_interactive_children()
122
+ i = l.index(child)
123
+ if i >= 0:
124
+ self.focused_index = i
125
+ return True
126
+ return False
127
+
128
+ def allow_focus_to_self(self) -> bool:
129
+ return len(self.get_interactive_children()) != 0
130
+
131
+ def draw(self, camera: bf.Camera) -> None:
132
+ constraints_down = False
133
+ if self.dirty_shape:
134
+ self.dirty_constraints = True
135
+ self.dirty_children = True
136
+ self.dirty_surface = True
137
+ self.build()
138
+ for child in self.children:
139
+ child.dirty_constraints = True
140
+ self.dirty_shape = False
141
+
142
+ if self.dirty_constraints:
143
+ if self.parent and self.parent.dirty_constraints:
144
+ self.parent.visit_up(self.selective_up)
145
+ else:
146
+ constraints_down = True
147
+
148
+ if self.dirty_children:
149
+ if self.layout:
150
+ self.layout.arrange()
151
+ self.dirty_children = False
152
+
153
+ if constraints_down:
154
+ self.visit(lambda c: c.resolve_constraints())
155
+
156
+ if self.dirty_surface:
157
+ self.paint()
158
+ self.dirty_surface = False
159
+
160
+
161
+ bf.Entity.draw(self, camera)
162
+
163
+ if self.clip_children:
164
+ new_clip = camera.world_to_screen(self.get_padded_rect())
165
+ old_clip = camera.surface.get_clip()
166
+ new_clip = new_clip.clip(old_clip)
167
+ camera.surface.set_clip(new_clip)
168
+ # Draw children with adjusted positions
169
+ _ = [child.draw(camera) for child in sorted(self.children, key=lambda c: c.render_order)]
54
170
 
55
- def to_string_id(self) -> str:
56
- return f"Container({self.uid},{len(self.children)},{[c.to_string() for c in self.constraints]})"
171
+ if self.clip_children:
172
+ camera.surface.set_clip(old_clip)
57
173
 
58
- def children_modified(self)->None:
59
- self.apply_all_constraints()
60
- super().children_modified()
@@ -13,12 +13,13 @@ class Debugger(Label):
13
13
  self.static_data: dict = {}
14
14
  self.dynamic_data: dict = {}
15
15
  self.refresh_rate = 10
16
- self.refresh_counter : float = 0
17
- self.render_order = 99
18
- self.set_uid("debugger")
16
+ self.refresh_counter: float = 0
17
+ self.add_tags("debugger")
18
+ self.set_visible(False)
19
+
20
+ # def get_debug_outlines(self):
21
+ # yield None
19
22
 
20
- def get_bounding_box(self):
21
- yield None
22
23
  def to_string_id(self) -> str:
23
24
  return "Debugger"
24
25
 
@@ -34,14 +35,14 @@ class Debugger(Label):
34
35
  self.dynamic_data[key] = func
35
36
  self.update_text()
36
37
 
37
- def remove_static(self,key)->bool:
38
+ def remove_static(self, key) -> bool:
38
39
  try:
39
40
  self.static_data.pop(key)
40
41
  return True
41
42
  except KeyError:
42
43
  return False
43
44
 
44
- def remove_dynamic(self,key)->bool:
45
+ def remove_dynamic(self, key) -> bool:
45
46
  try:
46
47
  self.dynamic_data.pop(key)
47
48
  return True
@@ -50,9 +51,13 @@ class Debugger(Label):
50
51
 
51
52
  def set_parent_scene(self, scene) -> Self:
52
53
  super().set_parent_scene(scene)
54
+ self.set_render_order(99)
53
55
  self.update_text()
54
56
  return self
55
57
 
58
+ def set_text(self, text: str) -> Self:
59
+ return super().set_text(text)
60
+
56
61
  def update_text(self) -> None:
57
62
  if not self.parent_scene:
58
63
  return
@@ -69,7 +74,7 @@ class Debugger(Label):
69
74
  def update(self, dt: float) -> None:
70
75
  if not self.parent_scene:
71
76
  return
72
- if self.parent_scene.get_sharedVar("debugging_mode") != 1:
77
+ if self.parent_scene.get_sharedVar("debug_mode") != bf.debugMode.DEBUGGER:
73
78
  self.set_visible(False)
74
79
  return
75
80
  self.set_visible(True)
@@ -79,33 +84,45 @@ class Debugger(Label):
79
84
  self.update_text()
80
85
 
81
86
 
82
- class BasicDebugger(Debugger):
83
- def __init__(self):
84
- super().__init__()
87
+ def __str__(self)->str:
88
+ return "Debugger"
89
+ # def top_at(self,x,y):
90
+ # return None
85
91
 
92
+ class FPSDebugger(Debugger):
86
93
  def do_when_added(self):
87
- if not self.parent_scene or not self.parent_scene.manager:
94
+ if not self.parent_scene or not self.parent_scene.manager:
88
95
  print("Debugger could not link to the manager")
89
96
  return
90
97
  manager_link = self.parent_scene.manager
91
- parent_scene = self.parent_scene
92
- self.add_static("Resolution", 'x'.join(str(i) for i in bf.const.RESOLUTION))
98
+ self.add_dynamic("FPS", lambda: str(round(manager_link.get_fps())))
99
+
100
+
101
+ class BasicDebugger(FPSDebugger):
102
+ def do_when_added(self):
103
+ if not self.parent_scene or not self.parent_scene.manager:
104
+ print("Debugger could not link to the manager")
105
+ return
93
106
  self.add_dynamic(
94
- "FPS", lambda: str(round(manager_link.get_fps()))
107
+ "Resolution", lambda: "x".join(str(i) for i in bf.const.RESOLUTION)
95
108
  )
109
+ super().do_when_added()
110
+ parent_scene = self.parent_scene
111
+
96
112
  self.add_dynamic("Mouse", pygame.mouse.get_pos)
97
113
  self.add_dynamic(
98
114
  "World",
99
- lambda: convert_to_int(*parent_scene.camera.convert_screen_to_world(
100
- *pygame.mouse.get_pos())
115
+ lambda: convert_to_int(
116
+ *parent_scene.camera.screen_to_world(pygame.mouse.get_pos())
101
117
  ),
102
118
  )
103
119
  self.add_dynamic(
104
120
  "Hud",
105
- lambda: convert_to_int(*parent_scene.hud_camera.convert_screen_to_world(
106
- *pygame.mouse.get_pos())
121
+ lambda: convert_to_int(
122
+ *parent_scene.hud_camera.screen_to_world(pygame.mouse.get_pos())
107
123
  ),
108
124
  )
109
- self.add_dynamic("W. Ent.",lambda : parent_scene.get_world_entity_count())
110
- self.add_dynamic("H. Ent.",lambda : parent_scene.get_hud_entity_count())
111
-
125
+ self.add_dynamic("W. Ent.", lambda: parent_scene.get_world_entity_count())
126
+ self.add_dynamic("H. Ent.", lambda: parent_scene.get_hud_entity_count())
127
+
128
+ self.add_dynamic("Hover",lambda : str(parent_scene.root.hovered) if parent_scene.root.hovered else None)
@@ -1,70 +1,96 @@
1
- from .label import Label
1
+ from .label import Label
2
2
  import batFramework as bf
3
3
  from typing import Self
4
+
5
+
4
6
  class DialogueBox(Label):
5
- def __init__(self)->None:
6
- self.cursor_position :float = 0.0
7
- self.text_speed :float = 20.0
8
- self.message_queue : list[str]= []
9
- self.is_over : bool = True
7
+ def __init__(self) -> None:
8
+ self.cursor_position: float = 0.0
9
+ self.text_speed: float = 20.0
10
+ self.message_queue: list[str] = []
11
+ self.is_over: bool = True
12
+ self.is_paused: bool = False
13
+ self.set_autoresize(False)
14
+ self.set_alignment(bf.alignment.LEFT)
10
15
  super().__init__("")
11
16
 
12
- self.set_autoresize(False)
13
- self.set_alignment(bf.Alignment.LEFT)
17
+ def pause(self)->Self:
18
+ self.is_paused = True
19
+ return self
20
+
21
+ def resume(self)->Self:
22
+ self.is_paused = False
23
+ return self
14
24
 
15
- def set_text_speed(self,speed:float)->Self:
25
+ def set_text_speed(self, speed: float) -> Self:
16
26
  self.text_speed = speed
17
27
  return self
18
28
 
19
- def cut_text_to_width(self,text:str)->list[str]:
20
- if text == '' or self.get_content_width() < 1 or not self._font_object : return ['']
21
-
29
+ def cut_text_to_width(self, text: str) -> list[str]:
30
+ w = self.get_padded_width()
31
+ if text == "" or not self.font_object or w < self.font_object.point_size:
32
+ return [text]
33
+ left = 0
22
34
  for index in range(len(text)):
35
+ width = self.font_object.size(text[left:index])[0]
23
36
 
24
- width = self._font_object.size(text[:index])[0]
25
- if width > self.get_content_width():
26
- cut_point_start = index-1
27
- cut_point_end = index-1
28
- last_space = text.rfind(' ',0,cut_point_start)
29
- if last_space != -1 : # space was found !:
30
- cut_point_start = last_space
31
- cut_point_end = last_space+1
32
-
37
+ if width > w:
38
+ cut_point_start = index - 1
39
+ cut_point_end = index - 1
40
+ last_space = text.rfind(' ', 0, cut_point_start)
41
+ last_nline = text.rfind('\n', 0, cut_point_start)
42
+
43
+ if last_space != -1 or last_nline!= -1: # space was found !:
44
+ cut_point_start = max(last_space,last_nline)
45
+ cut_point_end = cut_point_start + 1
33
46
  res = [text[:cut_point_start].strip()]
34
- res.extend(self.cut_text_to_width(text[cut_point_end:].strip()))
35
- return res
36
- return [text]
37
-
38
- def say(self,message:str):
39
- message = '\n'.join(self.cut_text_to_width(message))
40
- self.message_queue.append(message)
47
+ res.extend(self.cut_text_to_width(text[cut_point_end:].strip()))
48
+ return res
49
+ elif text[index] == '\n':
50
+ left = index
51
+ return [text]
41
52
 
53
+ def paint(self)->None:
54
+ if self.font_object and self.message_queue :
55
+ message = self.message_queue.pop(0)
56
+ message = "\n".join(self.cut_text_to_width(message))
57
+ self.message_queue.insert(0,message)
58
+ super().paint()
59
+
60
+ def say(self, message: str) ->Self:
61
+ self.message_queue.append(message)
42
62
  self.is_over = False
43
-
44
- def is_queue_empty(self)->bool:
63
+ return self
64
+
65
+ def is_queue_empty(self) -> bool:
45
66
  return not self.message_queue
46
67
 
47
- def is_current_message_over(self)->bool:
68
+ def is_current_message_over(self) -> bool:
48
69
  return self.is_over
49
70
 
50
- def clear_queue(self)->None:
71
+ def clear_queue(self) -> Self:
51
72
  self.message_queue.clear()
52
73
  self.next_message()
74
+ return self
53
75
 
54
- def next_message(self)->None:
76
+ def next_message(self) -> Self:
55
77
  if self.message_queue:
56
78
  self.message_queue.pop(0)
57
79
  self.cursor_position = 0
58
80
  self.set_text("")
81
+ return self
82
+
83
+ def skip_current_message(self)->Self:
84
+ self.cursor_position = len(self.message_queue[0])
85
+ self.dirty_shape = True
59
86
 
60
- def do_update(self,dt):
61
- if not self.message_queue : return
87
+ def do_update(self, dt):
88
+ if not self.message_queue or self.is_paused:
89
+ return
62
90
  if not self.is_over and self.cursor_position == len(self.message_queue[0]):
63
- self.is_over = True
64
- else:
65
- self.cursor_position = min(
66
- self.cursor_position + self.text_speed * dt,
67
- len(self.message_queue[0])
68
- )
69
- self.set_text(self.message_queue[0][:int(self.cursor_position)])
70
-
91
+ self.is_over = True
92
+ return
93
+ self.cursor_position = min(
94
+ self.cursor_position + self.text_speed * dt, len(self.message_queue[0])
95
+ )
96
+ self.set_text(self.message_queue[0][: int(self.cursor_position)])
@@ -0,0 +1,38 @@
1
+ from .interactiveWidget import InteractiveWidget
2
+ import batFramework as bf
3
+ import pygame
4
+
5
+
6
+ class DraggableWidget(InteractiveWidget):
7
+ def __init__(self, *args, **kwargs) -> None:
8
+ self.drag_action = bf.Action("dragging").add_mouse_control(1).set_holding()
9
+
10
+ self.drag_start = None
11
+ self.offset = None
12
+ super().__init__(*args, **kwargs)
13
+
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
+
20
+ def do_on_drag(self,drag_start:tuple[float,float],drag_end: tuple[float,float])->None:
21
+ self.set_position(drag_end[0]-self.offset[0],drag_end[1]-self.offset[1])
22
+
23
+ def update(self, dt: float):
24
+ if self.drag_action.active and self.is_clicked_down:
25
+ r = self.get_root()
26
+ x, y = r.drawing_camera.screen_to_world(pygame.mouse.get_pos())
27
+ if self.drag_start == None and self.drag_action.active:
28
+ self.offset = x-self.rect.x,y-self.rect.y
29
+ self.drag_start = x,y
30
+ return
31
+ else:
32
+ self.do_on_drag(self.drag_start,(x,y))
33
+ return
34
+ else:
35
+ self.drag_start = None
36
+ self.offset = None
37
+ self.is_clicked_down = False
38
+ super().update(dt)
batFramework/gui/image.py CHANGED
@@ -1,42 +1,47 @@
1
1
  import batFramework as bf
2
2
  from .widget import Widget
3
3
  import pygame
4
+ from typing import Self
4
5
 
5
6
 
6
7
  class Image(Widget):
7
8
  def __init__(
8
9
  self,
9
- data: pygame.Surface | str | None = None,
10
- size: None | tuple[int, int] = None,
10
+ path: str = None,
11
11
  convert_alpha=True,
12
12
  ):
13
- self.dirty : bool = False
14
13
  self.original_surface = None
15
- self.surface = None
16
- super().__init__(convert_alpha)
17
- if data:
18
- self.set_image(data,size)
14
+ super().__init__(convert_alpha = convert_alpha)
15
+ if path is not None:
16
+ self.from_path(path)
19
17
 
20
- def build(self) -> None:
21
- if self.original_surface and (
22
- (not self.surface or self.surface.get_size() != self.get_size_int())
23
- or self.dirty
24
- ) :
25
- self.surface = pygame.transform.scale(
26
- self.original_surface, self.get_size_int()
27
- )
18
+ def paint(self) -> None:
19
+ super().paint()
20
+ self.surface.fill((0,0,0,0 if self.convert_alpha else 255))
21
+ if self.rect.size != self.original_surface.get_size():
22
+ self.surface.blit(pygame.transform.scale(self.original_surface,self.rect.size),(0,0))
23
+ else:
24
+ self.surface.blit(self.original_surface,(0,0))
28
25
 
29
- def set_image(
30
- self, data: pygame.Surface | str, size: None | tuple[int, int] = None
31
- ):
32
- if isinstance(data, str):
33
- tmp = bf.ResourceManager().get_image(data,self.convert_alpha)
34
- elif isinstance(data, pygame.Surface):
35
- tmp= data
36
- if self.convert_alpha:
37
- tmp = tmp.convert_alpha()
38
- if tmp != self.original_surface: self.dirty = True
26
+
27
+ def from_path(self,path:str)->Self:
28
+ tmp = bf.ResourceManager().get_image(path,self.convert_alpha)
29
+ if tmp is None:
30
+ return self
39
31
  self.original_surface = tmp
40
- if not size and self.original_surface:
41
- size = self.original_surface.get_size()
42
- self.set_size(*size)
32
+ size = self.original_surface.get_size()
33
+ if not self.autoresize_h : size[0] = None
34
+ if not self.autoresize_h : size[1] = None
35
+ self.set_size(size)
36
+ self.dirty_surface = True
37
+ return self
38
+
39
+ def from_surface(self,surface: pygame.Surface)->Self:
40
+ if surface is None : return self
41
+ self.original_surface = surface
42
+ size = self.original_surface.get_size()
43
+ if not self.autoresize_h : size[0] = None
44
+ if not self.autoresize_h : size[1] = None
45
+ self.set_size(size)
46
+ self.dirty_surface = True
47
+ return self
@@ -1,13 +1,18 @@
1
1
  from .shape import Shape
2
- from typing import Any
2
+ from typing import Any, Self
3
3
  import pygame
4
-
5
- # from .constraints import ConstraintAspectRatio
4
+ from .widget import Widget
5
+ from .interactiveWidget import InteractiveWidget
6
+ from .draggableWidget import DraggableWidget
7
+ import batFramework as bf
6
8
 
7
9
 
8
10
  class Indicator(Shape):
9
- def __init__(self, width: int | float = 10, height: int | float = 10) -> None:
10
- super().__init__(width, height)
11
+ def __init__(self, size: tuple[int | float] = (10, 10)) -> None:
12
+ super().__init__(size)
13
+ self.debug_color = "magenta"
14
+ self.set_outline_width(1)
15
+ self.set_outline_color("black")
11
16
 
12
17
  def to_string_id(self) -> str:
13
18
  return "Indicator"
@@ -18,29 +23,38 @@ class Indicator(Shape):
18
23
  def get_value(self) -> Any:
19
24
  pass
20
25
 
21
- def _build_indicator(self) -> None:
22
- pass
23
26
 
24
- def build(self) -> None:
25
- super().build()
26
- self._build_indicator()
27
+ def top_at(self, x, y):
28
+ return None
27
29
 
28
30
 
29
31
  class ToggleIndicator(Indicator):
30
32
  def __init__(self, default_value: bool) -> None:
31
33
  self.value: bool = default_value
32
- super().__init__(20, 20)
34
+ self.callback = lambda val : self.set_color("green" if val else "red")
35
+ super().__init__((20, 20))
36
+ self.set_value(default_value)
33
37
 
34
- #TODO aspect ratio would be good right about here
38
+ # TODO aspect ratio would be good right about here
35
39
  # self.add_constraint(ConstraintAspectRatio(1))
36
40
 
37
- def set_value(self, value) -> None:
41
+ def set_callback(self, callback) -> Self:
42
+ self.callback = callback
43
+ return self
44
+
45
+ def set_value(self, value: bool) -> None:
38
46
  self.value = value
39
- self.set_color("green" if value else "red")
40
- self.build()
47
+ if self.callback:
48
+ self.callback(value)
49
+ self.dirty_surface = True
41
50
 
42
51
  def get_value(self) -> bool:
43
52
  return self.value
44
53
 
45
54
  def top_at(self, x: float, y: float) -> "None|Widget":
46
- return None
55
+ r = super().top_at(x, y)
56
+ if r is self:
57
+ return None
58
+ return r
59
+
60
+