batframework 1.0.9a10__py3-none-any.whl → 1.1.0__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 (76) hide show
  1. batFramework/__init__.py +52 -76
  2. batFramework/action.py +99 -126
  3. batFramework/actionContainer.py +9 -53
  4. batFramework/animatedSprite.py +114 -56
  5. batFramework/audioManager.py +36 -82
  6. batFramework/camera.py +69 -263
  7. batFramework/constants.py +53 -29
  8. batFramework/cutscene.py +109 -243
  9. batFramework/cutsceneBlocks.py +176 -0
  10. batFramework/debugger.py +48 -0
  11. batFramework/dynamicEntity.py +9 -16
  12. batFramework/easing.py +71 -0
  13. batFramework/entity.py +85 -92
  14. batFramework/gui/__init__.py +3 -14
  15. batFramework/gui/button.py +78 -12
  16. batFramework/gui/constraints.py +204 -0
  17. batFramework/gui/container.py +31 -183
  18. batFramework/gui/debugger.py +43 -126
  19. batFramework/gui/frame.py +19 -0
  20. batFramework/gui/image.py +20 -55
  21. batFramework/gui/indicator.py +22 -95
  22. batFramework/gui/interactiveWidget.py +12 -229
  23. batFramework/gui/label.py +77 -311
  24. batFramework/gui/layout.py +66 -411
  25. batFramework/gui/root.py +35 -203
  26. batFramework/gui/shape.py +57 -247
  27. batFramework/gui/toggle.py +48 -114
  28. batFramework/gui/widget.py +243 -457
  29. batFramework/manager.py +29 -113
  30. batFramework/particles.py +77 -0
  31. batFramework/scene.py +217 -22
  32. batFramework/sceneManager.py +129 -161
  33. batFramework/stateMachine.py +8 -11
  34. batFramework/time.py +75 -0
  35. batFramework/transition.py +124 -129
  36. batFramework/transitionManager.py +0 -0
  37. batFramework/triggerZone.py +4 -4
  38. batFramework/utils.py +144 -266
  39. {batframework-1.0.9a10.dist-info → batframework-1.1.0.dist-info}/METADATA +24 -22
  40. batframework-1.1.0.dist-info/RECORD +43 -0
  41. batFramework/animation.py +0 -77
  42. batFramework/baseScene.py +0 -240
  43. batFramework/cutsceneManager.py +0 -34
  44. batFramework/drawable.py +0 -77
  45. batFramework/easingController.py +0 -58
  46. batFramework/enums.py +0 -135
  47. batFramework/fontManager.py +0 -65
  48. batFramework/gui/animatedLabel.py +0 -89
  49. batFramework/gui/clickableWidget.py +0 -245
  50. batFramework/gui/constraints/__init__.py +0 -1
  51. batFramework/gui/constraints/constraints.py +0 -980
  52. batFramework/gui/draggableWidget.py +0 -44
  53. batFramework/gui/meter.py +0 -96
  54. batFramework/gui/radioButton.py +0 -35
  55. batFramework/gui/selector.py +0 -250
  56. batFramework/gui/slider.py +0 -397
  57. batFramework/gui/style.py +0 -10
  58. batFramework/gui/styleManager.py +0 -54
  59. batFramework/gui/syncedVar.py +0 -49
  60. batFramework/gui/textInput.py +0 -306
  61. batFramework/gui/tooltip.py +0 -30
  62. batFramework/particle.py +0 -118
  63. batFramework/propertyEaser.py +0 -79
  64. batFramework/renderGroup.py +0 -34
  65. batFramework/resourceManager.py +0 -130
  66. batFramework/sceneLayer.py +0 -138
  67. batFramework/scrollingSprite.py +0 -115
  68. batFramework/sprite.py +0 -51
  69. batFramework/templates/__init__.py +0 -1
  70. batFramework/templates/controller.py +0 -97
  71. batFramework/tileset.py +0 -46
  72. batFramework/timeManager.py +0 -213
  73. batframework-1.0.9a10.dist-info/RECORD +0 -67
  74. {batframework-1.0.9a10.dist-info → batframework-1.1.0.dist-info}/LICENSE +0 -0
  75. {batframework-1.0.9a10.dist-info → batframework-1.1.0.dist-info}/WHEEL +0 -0
  76. {batframework-1.0.9a10.dist-info → batframework-1.1.0.dist-info}/top_level.txt +0 -0
@@ -1,201 +1,49 @@
1
1
  import batFramework as bf
2
2
  from .widget import Widget
3
- from .shape import Shape
4
- from .interactiveWidget import InteractiveWidget
5
- from .layout import Layout, Column
3
+ from .layout import Layout
4
+ from .constraints import Constraint
6
5
  from typing import Self
7
- from pygame.math import Vector2
8
6
 
9
-
10
- class Container(Shape, InteractiveWidget):
11
- def __init__(self, layout: Layout = None, *children: Widget) -> None:
7
+ class Container(Widget):
8
+ def __init__(self, layout:Layout=None, *children:Widget):
12
9
  super().__init__()
13
- self.dirty_layout: bool = False
14
10
  self.set_debug_color("green")
15
- self.layout = layout if layout else Column()
16
- self.layout.set_parent(self)
17
- self.scroll = Vector2(0, 0)
18
- self.add(*children)
19
-
20
- def __str__(self) -> str:
21
- return f"Container({self.uid},{len(self.children)})"
22
-
23
- def get_min_required_size(self):
24
- return self.layout.get_auto_size() if self.layout else self.rect.size
25
-
26
- def reset_scroll(self) -> Self:
27
- if self.scroll == (0,0):
28
- return self
29
- self.scroll.update(0, 0)
30
- self.dirty_layout = True
31
- return self
32
-
33
- def set_scroll(self, value: tuple) -> Self:
34
- if (self.scroll.x,self.scroll.y) == value:
35
- return self
36
- self.scroll.update(value)
37
- self.clamp_scroll()
38
- self.dirty_layout = True
39
- return self
40
-
41
- def scrollX_by(self, x: float | int) -> Self:
42
- if x == 0:
43
- return self
44
- self.set_scroll((self.scroll.x + x, self.scroll.y))
45
- return self
11
+ self.surface = None
12
+ self.layout :Layout = layout
13
+ if self.layout : self.layout.set_parent(self)
14
+ for child in children:
15
+ self.add_child(child)
46
16
 
47
- def scrollY_by(self, y: float | int) -> Self:
48
- if y == 0:
49
- return self
50
- self.set_scroll((self.scroll.x, self.scroll.y + y))
51
- return self
52
-
53
- def scroll_by(self, value: tuple[float | int, float | int]) -> Self:
54
- if value[0] == 0 and value[1] == 0:
55
- return self
56
- self.set_scroll((self.scroll.x + value[0], self.scroll.y + value[1]))
57
- return self
58
-
59
- def clamp_scroll(self) -> Self:
60
- if not self.children:
61
- return self
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))
75
- return self
76
-
77
- def set_layout(self, layout: Layout) -> Self:
78
- tmp = self.layout
17
+ def set_layout(self,layout:Layout)->Self:
79
18
  self.layout = layout
80
- if self.layout != tmp:
81
- tmp.set_parent(None)
82
- self.layout.set_parent(self)
83
- self.reset_scroll()
84
- self.dirty_layout = True
19
+ self.apply_constraints()
85
20
  return self
86
21
 
87
- def get_interactive_children(self) -> list[InteractiveWidget]:
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
22
+ def get_bounding_box(self):
23
+ yield (self.rect,self._debug_color)
24
+ for child in self.children:
25
+ yield from child.get_bounding_box()
92
26
 
93
- def clear_children(self) -> None:
27
+ def clear_children(self)->None:
94
28
  self.children.clear()
95
- self.dirty_layout = True
29
+ self.apply_constraints()
96
30
 
97
- def add(self, *child: Widget) -> Self:
98
- super().add(*child)
99
- self.dirty_shape = True
100
- self.dirty_layout = True
101
- return self
102
-
103
- def remove(self, *child: Widget) -> Self:
104
- super().remove(*child)
105
- self.dirty_shape = True
106
- self.dirty_layout = True
107
- return self
108
-
109
- def top_at(self, x: float | int, y: float | int) -> "None|Widget":
110
- if self.rect.collidepoint(x, y):
111
- for child in reversed(self.children):
112
- result = child.top_at(x, y)
113
- if result is not None:
114
- return result
115
- return self
116
- return None
117
-
118
- def get_focus(self) -> bool:
119
- if not super().get_focus():
120
- return False
121
- interactive_children = self.get_interactive_children()
122
- if not interactive_children:
123
- return True
124
- self.focused_index = min(self.focused_index, len(interactive_children) - 1)
125
- return interactive_children[self.focused_index].get_focus()
31
+ def add_child(self,*child:Widget)->None:
32
+ super().add_child(*child)
33
+ if self.layout : self.layout.arrange()
126
34
 
127
- def children_has_focus(self)->bool:
128
- return any(child.is_focused for child in self.get_interactive_children())
35
+ def remove_child(self,child:Widget)->None:
36
+ super().remove_child(child)
37
+ if self.layout : self.layout.arrange()
129
38
 
130
- def do_handle_event(self, event) -> None:
131
- if event.consumed:
132
- return
133
- self.layout.handle_event(event)
134
-
135
- def set_focused_child(self, child: InteractiveWidget) -> bool:
136
- interactive_children = self.get_interactive_children()
137
- try:
138
- index = interactive_children.index(child)
139
- self.focused_index = index
140
- if self.layout :
141
- self.layout.scroll_to_widget(child)
142
- return True
143
- except ValueError:
144
- return False
145
-
146
- def allow_focus_to_self(self) -> bool:
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))
39
+ def build(self)->None:
155
40
  super().build()
41
+ if self.layout : self.layout.arrange()
156
42
 
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
162
-
163
- if self.dirty_layout:
164
- self.layout.update_child_constraints()
165
- self.layout.arrange()
166
- self.dirty_layout = False
167
-
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
- """
173
- if self.dirty_shape:
174
- self.layout.update_child_constraints()
175
- self.build()
176
- self.dirty_shape = False
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
185
-
186
- # trigger layout or constraint updates in parent
187
-
188
-
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:
198
- self.paint()
199
- self.dirty_surface = False
200
-
43
+ def apply_constraints(self)->None:
44
+ super().apply_constraints()
45
+ if self.layout : self.layout.arrange()
201
46
 
47
+ def to_string_id(self)->str:
48
+ return f"Container({len(self.children)},{[c.to_string() for c in self.constraints]})"
49
+
@@ -1,130 +1,47 @@
1
1
  from .label import Label
2
- from typing import Self,Callable,Any
3
- import batFramework as bf
4
- import pygame
5
- import sys
6
-
7
-
8
- def convert_to_int(*args):
9
- return [int(arg) for arg in args]
10
-
2
+ from typing import Self
11
3
 
12
4
  class Debugger(Label):
13
- def __init__(self) -> None:
14
- super().__init__("")
15
- self.static_data: dict[str,Any] = {}
16
- self.dynamic_data: dict[str,Callable[[],str]] = {}
17
- self.refresh_rate = 10
18
- self.refresh_counter: float = 0
19
- self.add_tags("debugger")
20
- self.set_visible(False)
21
-
22
- def set_refresh_rate(self, value: int) -> Self:
23
- self.refresh_rate = value
24
- return self
25
-
26
- def add_static(self, key: str, data):
27
- self.static_data[key] = str(data)
28
- self.update_text()
29
-
30
- def add_dynamic(self, key: str, func:Callable[[],str]) -> None:
31
- self.dynamic_data[key] = func
32
- self.update_text()
33
-
34
- def remove_static(self, key:str) -> bool:
35
- try:
36
- self.static_data.pop(key)
37
- return True
38
- except KeyError:
39
- return False
40
-
41
- def remove_dynamic(self, key:str) -> bool:
42
- try:
43
- self.dynamic_data.pop(key)
44
- return True
45
- except KeyError:
46
- return False
47
-
48
- def set_parent_scene(self, scene) -> Self:
49
- super().set_parent_scene(scene)
50
- self.set_render_order(sys.maxsize-100)
51
- self.update_text()
52
- return self
53
-
54
- def set_text(self, text: str) -> Self:
55
- return super().set_text(text)
56
-
57
- def update_text(self) -> None:
58
- if not self.parent_scene:
59
- return
60
-
61
- d = "\n".join(
62
- key + ":" + data if key != "" else data
63
- for key, data in self.static_data.items()
64
- )
65
-
66
- d2 = "\n".join(
67
- key + ":" + str(data()) if key != "" else str(data())
68
- for key, data in self.dynamic_data.items()
69
- )
70
-
71
- self.set_text("\n".join((d, d2)).strip())
72
-
73
- def update(self, dt: float) -> None:
74
- if not self.parent_scene:
75
- return
76
-
77
- if bf.ResourceManager().get_sharedVar("debug_mode") != bf.debugMode.DEBUGGER:
78
- self.set_visible(False)
79
- return
80
-
81
- self.set_visible(True)
82
- self.refresh_counter = self.refresh_counter + (dt * 60)
83
-
84
- if self.refresh_counter > self.refresh_rate:
85
- self.refresh_counter = 0
86
- self.update_text()
87
-
88
-
89
- def __str__(self) -> str:
90
- return "Debugger"
91
-
92
- def top_at(self, x, y):
93
- return None
94
-
95
-
96
- class FPSDebugger(Debugger):
97
- def __init__(self):
98
- super().__init__()
99
-
100
- def do_when_added(self):
101
- if not self.parent_scene or not self.parent_scene.manager:
102
- print("Debugger could not link to the manager")
103
- return
104
- manager_link = self.parent_scene.manager
105
- self.add_dynamic("FPS", lambda: str(round(manager_link.get_fps())))
106
-
107
-
108
- class BasicDebugger(FPSDebugger):
109
- def do_when_added(self):
110
- if not self.parent_scene or not self.parent_scene.manager:
111
- print("Debugger could not link to the manager")
112
- return
113
- self.add_dynamic(
114
- "Resolution", lambda: "x".join(str(i) for i in bf.const.RESOLUTION)
115
- )
116
- super().do_when_added()
117
- self.add_dynamic("Mouse", pygame.mouse.get_pos)
118
-
119
- if not hasattr(self.parent_scene,"root"):
120
- print("Debugger couldn't find 'root' widget in parent scene")
121
- return
5
+ def __init__(self)->None:
6
+ super().__init__("")
7
+ self.static_data : dict = {}
8
+ self.dynamic_data : dict = {}
9
+ self.refresh_rate = 10
10
+ self.refresh_counter = 0
11
+ self.render_order = 99
12
+
13
+ def to_string_id(self)->str:
14
+ return "Debugger"
15
+
16
+ def set_refresh_rate(self, value:int)-> Self:
17
+ self.refresh_rate = value
18
+ return self
19
+
20
+ def add_data(self,key:str,data):
21
+ self.static_data[key] = str(data)
22
+ self.update_text()
23
+
24
+ def add_dynamic_data(self,key:str,func)->None:
25
+ self.dynamic_data[key] = func
26
+ self.update_text()
27
+
28
+ def set_parent_scene(self,scene)->None:
29
+ super().set_parent_scene(scene)
30
+ self.update_text()
122
31
 
123
- parent_scene = self.parent_scene
124
-
125
- self.add_dynamic(
126
- "Hover",
127
- lambda: (
128
- str(parent_scene.root.hovered) if parent_scene.root.hovered else None
129
- ),
130
- )
32
+ def update_text(self)->None:
33
+ if not self.parent_scene:return
34
+ d = '\n'.join(key+':'+data if key!='' else data for key,data in self.static_data.items())
35
+ d2 = '\n'.join(key+':'+str(data()) if key!='' else str(data()) for key,data in self.dynamic_data.items())
36
+ self.set_text('\n'.join((d,d2)).strip())
37
+
38
+ def update(self,dt:float)->None:
39
+ if not self.parent_scene:return
40
+ if self.parent_scene.get_sharedVar("is_debugging_func")() != 1:
41
+ self.set_visible(False)
42
+ return
43
+ self.set_visible(True)
44
+ self.refresh_counter = self.refresh_counter + (dt * 60)
45
+ if self.refresh_counter > self.refresh_rate:
46
+ self.refresh_counter = 0
47
+ self.update_text()
@@ -0,0 +1,19 @@
1
+ import batFramework as bf
2
+ from .shape import Shape
3
+ import pygame
4
+
5
+
6
+ class Frame(Shape):
7
+ def __init__(self,width:float,height:float):
8
+ super().__init__(width,height)
9
+ self.set_debug_color("magenta")
10
+
11
+ def to_string_id(self)->str:
12
+ return "Frame"
13
+
14
+ def children_modified(self)->None:
15
+ print(self.children)
16
+ self.set_size(
17
+ *self.inflate_rect_by_padding(self.rect.unionall(list(c.rect for c in self.children))).size
18
+ )
19
+ super().children_modified()
batFramework/gui/image.py CHANGED
@@ -1,58 +1,23 @@
1
1
  import batFramework as bf
2
2
  from .widget import Widget
3
- from .shape import Shape
4
3
  import pygame
5
- from typing import Self
6
-
7
-
8
- class Image(Shape):
9
- def __init__(
10
- self,
11
- path: str = None,
12
- convert_alpha=True,
13
- ):
14
- self.original_surface = None
15
- super().__init__(convert_alpha=convert_alpha)
16
- if path is not None:
17
- self.from_path(path)
18
-
19
- def __str__(self) -> str:
20
- return "Image"
21
-
22
- def paint(self) -> None:
23
- super().paint()
24
- if self.original_surface is None:
25
- return
26
- padded = self.get_inner_rect().move(-self.rect.x,-self.rect.y)
27
- target_size = padded.size
28
- if self.original_surface.get_size() != target_size:
29
- self.surface.blit(pygame.transform.scale(self.original_surface, target_size), padded.topleft)
30
- else:
31
- self.surface.blit(self.original_surface, padded.topleft)
32
-
33
- def build(self) -> None:
34
- if self.original_surface is not None:
35
- self.set_size(
36
- self.expand_rect_with_padding((0,0,*self.original_surface.get_size())).size
37
- )
38
- super().build()
39
-
40
-
41
- def from_path(self, path: str) -> Self:
42
- tmp = bf.ResourceManager().get_image(path, self.convert_alpha)
43
- if tmp is None:
44
- return self
45
- self.original_surface = tmp
46
- size = self.original_surface.get_size()
47
- self.set_size(size)
48
- self.dirty_surface = True
49
- return self
50
-
51
- def from_surface(self, surface: pygame.Surface) -> Self:
52
- if surface is None:
53
- return self
54
- self.original_surface = surface
55
- size = self.original_surface.get_size()
56
- self.set_size(size)
57
- self.dirty_surface = True
58
- return self
4
+ class Image(Widget):
5
+ def __init__(self,data:pygame.Surface|str,size:None|tuple[int,int]=None,convert_alpha=True):
6
+ super().__init__(False)
7
+ self.surface = None
8
+ if isinstance(data,str):
9
+ path = bf.utils.get_path(data)
10
+ self.original_surface=pygame.image.load(path)
11
+ elif isinstance(data,pygame.Surface):
12
+ self.original_surface = data
13
+
14
+ if convert_alpha: self.original_surface = self.original_surface.convert_alpha()
15
+ if not size : size = self.original_surface.get_size()
16
+ self.set_size(*size)
17
+
18
+
19
+ def build(self)->None:
20
+ if not self.surface or self.surface.get_size() != self.get_size_int():
21
+ self.surface = pygame.transform.scale(self.original_surface,self.get_size_int())
22
+
23
+
@@ -1,113 +1,40 @@
1
1
  from .shape import Shape
2
- from typing import Any, Self, Callable
2
+ from typing import Any
3
3
  import pygame
4
- from .widget import Widget
5
- from .interactiveWidget import InteractiveWidget
6
- from .draggableWidget import DraggableWidget
7
- import batFramework as bf
8
-
4
+ # from .constraints import ConstraintAspectRatio
9
5
 
10
6
  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
-
17
- def __init__(self, size: tuple[int | float] = (10, 10)) -> None:
18
- super().__init__(size)
19
- self.debug_color = "magenta"
20
- self.set_outline_width(1)
21
- self.set_outline_color("black")
7
+ def __init__(self,width:int|float= 10,height:int|float=10)->None:
8
+ super().__init__(width,height)
22
9
 
23
- def to_string_id(self) -> str:
10
+ def to_string_id(self)->str:
24
11
  return "Indicator"
25
12
 
26
- def set_value(self, value: Any) -> None:
13
+ def set_value(self,value:Any)->None:
27
14
  pass
28
15
 
29
- def get_value(self) -> Any:
16
+ def get_value(self)->Any:
30
17
  pass
31
18
 
32
- def top_at(self, x, y):
33
- return None
19
+ def _build_indicator(self)->None:
20
+ pass
21
+
22
+ def build(self)->None:
23
+ super().build()
24
+ self._build_indicator()
34
25
 
35
26
 
36
27
  class ToggleIndicator(Indicator):
37
- def __init__(self, default_value: bool) -> None:
38
- super().__init__((20, 20))
39
- self.value: bool = default_value
40
- self.callback = lambda val: self.set_color("green" if val else "red")
41
- self.set_value(default_value)
42
- self.callback(default_value)
43
- # TODO aspect ratio would be good right about here
44
- self.add_constraints(bf.gui.AspectRatio(1,reference_axis=bf.axis.VERTICAL))
45
-
46
- def set_callback(self, callback : Callable[[bool],Any]) -> Self:
47
- self.callback = callback
48
- return self
28
+ def __init__(self,default_value:bool)->None:
29
+ self.value:bool = default_value
30
+ super().__init__(20,20)
31
+ # self.add_constraint(ConstraintAspectRatio(1))
49
32
 
50
- def set_value(self, value: bool) -> None:
33
+ def set_value(self,value)->None:
51
34
  self.value = value
52
- if self.callback:
53
- self.callback(value)
54
- self.dirty_surface = True
35
+ self.set_color("green" if value else "red")
36
+ self.build()
55
37
 
56
- def get_value(self) -> bool:
38
+ def get_value(self)->bool:
57
39
  return self.value
58
-
59
- def top_at(self, x: float, y: float) -> "None|Widget":
60
- r = super().top_at(x, y)
61
- if r is self:
62
- return None
63
- return r
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
-
40
+ #