batframework 1.0.9a7__py3-none-any.whl → 1.0.9a8__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.
- batFramework/__init__.py +20 -11
- batFramework/action.py +1 -1
- batFramework/animatedSprite.py +47 -116
- batFramework/animation.py +30 -5
- batFramework/audioManager.py +8 -5
- batFramework/baseScene.py +240 -0
- batFramework/camera.py +4 -0
- batFramework/constants.py +6 -1
- batFramework/cutscene.py +221 -21
- batFramework/cutsceneManager.py +5 -2
- batFramework/drawable.py +7 -5
- batFramework/easingController.py +10 -11
- batFramework/entity.py +21 -2
- batFramework/enums.py +48 -33
- batFramework/gui/__init__.py +3 -1
- batFramework/gui/animatedLabel.py +10 -2
- batFramework/gui/button.py +4 -31
- batFramework/gui/clickableWidget.py +42 -30
- batFramework/gui/constraints/constraints.py +212 -136
- batFramework/gui/container.py +72 -48
- batFramework/gui/debugger.py +12 -17
- batFramework/gui/draggableWidget.py +8 -11
- batFramework/gui/image.py +3 -10
- batFramework/gui/indicator.py +73 -1
- batFramework/gui/interactiveWidget.py +117 -100
- batFramework/gui/label.py +73 -63
- batFramework/gui/layout.py +221 -452
- batFramework/gui/meter.py +21 -7
- batFramework/gui/radioButton.py +0 -1
- batFramework/gui/root.py +99 -29
- batFramework/gui/selector.py +257 -0
- batFramework/gui/shape.py +13 -5
- batFramework/gui/slider.py +260 -93
- batFramework/gui/textInput.py +45 -21
- batFramework/gui/toggle.py +70 -52
- batFramework/gui/tooltip.py +30 -0
- batFramework/gui/widget.py +203 -125
- batFramework/manager.py +7 -8
- batFramework/particle.py +4 -1
- batFramework/propertyEaser.py +79 -0
- batFramework/renderGroup.py +17 -50
- batFramework/resourceManager.py +43 -13
- batFramework/scene.py +15 -335
- batFramework/sceneLayer.py +138 -0
- batFramework/sceneManager.py +31 -36
- batFramework/scrollingSprite.py +8 -3
- batFramework/sprite.py +1 -1
- batFramework/templates/__init__.py +1 -2
- batFramework/templates/controller.py +97 -0
- batFramework/timeManager.py +76 -22
- batFramework/transition.py +37 -103
- batFramework/utils.py +121 -3
- {batframework-1.0.9a7.dist-info → batframework-1.0.9a8.dist-info}/METADATA +24 -3
- batframework-1.0.9a8.dist-info/RECORD +66 -0
- {batframework-1.0.9a7.dist-info → batframework-1.0.9a8.dist-info}/WHEEL +1 -1
- batFramework/character.py +0 -27
- batFramework/templates/character.py +0 -43
- batFramework/templates/states.py +0 -166
- batframework-1.0.9a7.dist-info/RECORD +0 -63
- /batframework-1.0.9a7.dist-info/LICENCE → /batframework-1.0.9a8.dist-info/LICENSE +0 -0
- {batframework-1.0.9a7.dist-info → batframework-1.0.9a8.dist-info}/top_level.txt +0 -0
batFramework/gui/container.py
CHANGED
@@ -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
|
|
@@ -66,15 +65,21 @@ class Container(Shape, InteractiveWidget):
|
|
66
65
|
def clamp_scroll(self) -> Self:
|
67
66
|
if not self.children:
|
68
67
|
return self
|
69
|
-
r = self.
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
68
|
+
r = self.get_inner_rect()
|
69
|
+
# Compute the bounding rect of all children in one go
|
70
|
+
children_rect = self.children[0].rect.copy()
|
71
|
+
for child in self.children[1:]:
|
72
|
+
children_rect.union_ip(child.rect)
|
73
|
+
max_scroll_x = max(0, children_rect.width - r.width)
|
74
|
+
max_scroll_y = max(0, children_rect.height - r.height)
|
75
|
+
|
76
|
+
# Clamp scroll values only if needed
|
77
|
+
new_x = min(max(self.scroll.x, 0), max_scroll_x)
|
78
|
+
new_y = min(max(self.scroll.y, 0), max_scroll_y)
|
79
|
+
|
80
|
+
if self.scroll.x != new_x or self.scroll.y != new_y:
|
81
|
+
self.scroll.x = new_x
|
82
|
+
self.scroll.y = new_y
|
78
83
|
return self
|
79
84
|
|
80
85
|
def set_layout(self, layout: Layout) -> Self:
|
@@ -83,6 +88,7 @@ class Container(Shape, InteractiveWidget):
|
|
83
88
|
if self.layout != tmp:
|
84
89
|
tmp.set_parent(None)
|
85
90
|
self.layout.set_parent(self)
|
91
|
+
self.reset_scroll()
|
86
92
|
self.dirty_layout = True
|
87
93
|
return self
|
88
94
|
|
@@ -96,13 +102,13 @@ class Container(Shape, InteractiveWidget):
|
|
96
102
|
def add(self, *child: Widget) -> Self:
|
97
103
|
super().add(*child)
|
98
104
|
self.dirty_shape = True
|
99
|
-
self.
|
105
|
+
self.dirty_layout = True
|
100
106
|
return self
|
101
107
|
|
102
108
|
def remove(self, *child: Widget) -> Self:
|
103
109
|
super().remove(*child)
|
104
110
|
self.dirty_shape = True
|
105
|
-
self.
|
111
|
+
self.dirty_layout = True
|
106
112
|
return self
|
107
113
|
|
108
114
|
def top_at(self, x: float | int, y: float | int) -> "None|Widget":
|
@@ -123,60 +129,78 @@ class Container(Shape, InteractiveWidget):
|
|
123
129
|
self.focused_index = min(self.focused_index, len(interactive_children) - 1)
|
124
130
|
return interactive_children[self.focused_index].get_focus()
|
125
131
|
|
132
|
+
def children_has_focus(self)->bool:
|
133
|
+
return any(child.is_focused for child in self.get_interactive_children())
|
134
|
+
|
126
135
|
def do_handle_event(self, event) -> None:
|
127
|
-
if
|
128
|
-
|
136
|
+
if event.consumed:
|
137
|
+
return
|
138
|
+
self.layout.handle_event(event)
|
129
139
|
|
130
140
|
def set_focused_child(self, child: InteractiveWidget) -> bool:
|
131
141
|
interactive_children = self.get_interactive_children()
|
132
142
|
try:
|
133
143
|
index = interactive_children.index(child)
|
134
144
|
self.focused_index = index
|
145
|
+
if self.layout :
|
146
|
+
self.layout.scroll_to_widget(child)
|
135
147
|
return True
|
136
148
|
except ValueError:
|
137
149
|
return False
|
138
150
|
|
139
151
|
def allow_focus_to_self(self) -> bool:
|
140
152
|
return bool(self.get_interactive_children()) and self.visible
|
153
|
+
|
154
|
+
def build(self) -> None:
|
155
|
+
if self.layout is not None:
|
156
|
+
# print("I'm building !",self)
|
157
|
+
# size = self.expand_rect_with_padding((0,0,*self.layout.get_auto_size())).size
|
158
|
+
size = self.layout.get_auto_size()
|
159
|
+
self.set_size(self.resolve_size(size))
|
160
|
+
super().build()
|
161
|
+
|
162
|
+
def apply_pre_updates(self):
|
163
|
+
if self.dirty_size_constraints or self.dirty_shape:
|
164
|
+
self.resolve_constraints(size_only=True)
|
165
|
+
self.dirty_size_constraints = False
|
166
|
+
self.dirty_position_constraints = True
|
141
167
|
|
168
|
+
if self.dirty_layout:
|
169
|
+
self.layout.update_child_constraints()
|
170
|
+
self.layout.arrange()
|
171
|
+
self.dirty_layout = False
|
142
172
|
|
143
|
-
def
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
self.resolve_constraints() # Finalize positioning based on size
|
149
|
-
|
150
|
-
# Step 1: Build shape if needed
|
173
|
+
def apply_post_updates(self,skip_draw:bool=False):
|
174
|
+
"""
|
175
|
+
BOTTOM TO TOP
|
176
|
+
for cases when widget attributes depend on children attributes
|
177
|
+
"""
|
151
178
|
if self.dirty_shape:
|
152
|
-
self.
|
179
|
+
self.layout.update_child_constraints()
|
180
|
+
self.build()
|
153
181
|
self.dirty_shape = False
|
154
|
-
self.dirty_surface = True
|
155
|
-
self.dirty_layout =
|
156
|
-
|
157
|
-
|
158
|
-
|
182
|
+
self.dirty_surface = True
|
183
|
+
self.dirty_layout = True
|
184
|
+
self.dirty_size_constraints = True
|
185
|
+
self.dirty_position_constraints = True
|
186
|
+
from .container import Container
|
187
|
+
if self.parent and isinstance(self.parent, Container):
|
188
|
+
self.parent.dirty_layout = True
|
189
|
+
self.parent.dirty_shape = True
|
159
190
|
|
160
|
-
|
161
|
-
|
191
|
+
# trigger layout or constraint updates in parent
|
192
|
+
|
162
193
|
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
self.
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
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 !!")
|
194
|
+
# force recheck of constraints
|
195
|
+
|
196
|
+
|
197
|
+
if self.dirty_position_constraints:
|
198
|
+
self.resolve_constraints(position_only=True)
|
199
|
+
self.dirty_position_constraints= False
|
200
|
+
|
201
|
+
|
202
|
+
if self.dirty_surface and not skip_draw:
|
181
203
|
self.paint()
|
182
204
|
self.dirty_surface = False
|
205
|
+
|
206
|
+
|
batFramework/gui/debugger.py
CHANGED
@@ -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(
|
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
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
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,34 +5,31 @@ 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
8
|
|
10
9
|
self.drag_start = None
|
11
10
|
self.offset = None
|
12
11
|
super().__init__(*args, **kwargs)
|
13
12
|
|
14
|
-
def
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
self.drag_action.reset()
|
19
|
-
|
13
|
+
def on_click_down(self, button):
|
14
|
+
if super().on_click_down(button)==False:
|
15
|
+
return button == 1 # capture event
|
16
|
+
|
20
17
|
def do_on_drag(
|
21
18
|
self, drag_start: tuple[float, float], drag_end: tuple[float, float]
|
22
19
|
) -> None:
|
23
20
|
self.set_position(drag_end[0] - self.offset[0], drag_end[1] - self.offset[1])
|
24
21
|
|
22
|
+
|
25
23
|
def update(self, dt: float):
|
26
|
-
if self.
|
24
|
+
if self.is_clicked_down and pygame.mouse.get_pressed(3)[0]:
|
27
25
|
r = self.get_root()
|
28
26
|
x, y = r.drawing_camera.screen_to_world(pygame.mouse.get_pos())
|
29
|
-
if self.drag_start == None and self.
|
27
|
+
if self.drag_start == None and self.is_clicked_down:
|
30
28
|
self.offset = x - self.rect.x, y - self.rect.y
|
31
29
|
self.drag_start = x, y
|
32
|
-
return
|
33
30
|
else:
|
34
31
|
self.do_on_drag(self.drag_start, (x, y))
|
35
|
-
|
32
|
+
|
36
33
|
else:
|
37
34
|
self.drag_start = None
|
38
35
|
self.offset = None
|
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.
|
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.
|
36
|
-
self.
|
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
|
batFramework/gui/indicator.py
CHANGED
@@ -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
|
-
|
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,69 @@ 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
|
+
self.angle : float = 45
|
72
|
+
self.spread : float = None
|
73
|
+
self.draw_stem : bool = True
|
74
|
+
|
75
|
+
def set_draw_stem(self,value:bool)->Self:
|
76
|
+
self.draw_stem = value
|
77
|
+
self.dirty_surface = False
|
78
|
+
return self
|
79
|
+
|
80
|
+
def set_spread(self,value:float)->Self:
|
81
|
+
self.spread = value
|
82
|
+
self.dirty_surface = True
|
83
|
+
return self
|
84
|
+
|
85
|
+
def set_angle(self,value:float)->Self:
|
86
|
+
self.angle = value
|
87
|
+
self.dirty_surface = True
|
88
|
+
return self
|
89
|
+
|
90
|
+
def set_arrow_color(self,color)-> Self:
|
91
|
+
self.arrow_color = color
|
92
|
+
self.dirty_surface = True
|
93
|
+
return self
|
94
|
+
|
95
|
+
def set_direction(self,direction:bf.direction)->Self:
|
96
|
+
self.direction = direction
|
97
|
+
self.dirty_surface = True
|
98
|
+
return self
|
99
|
+
|
100
|
+
def set_line_width(self,value:int)->Self:
|
101
|
+
self.line_width = value
|
102
|
+
self.dirty_surface = True
|
103
|
+
return self
|
104
|
+
|
105
|
+
def paint(self):
|
106
|
+
super().paint()
|
107
|
+
r = self.get_local_padded_rect()
|
108
|
+
size = min(r.width, r.height)
|
109
|
+
if size %2 == 0:
|
110
|
+
size -= 1
|
111
|
+
r.width = size
|
112
|
+
r.height = size
|
113
|
+
if (self.padding[1]+self.padding[3] )%2 ==0:
|
114
|
+
r.height-=1
|
115
|
+
if (self.padding[0]+self.padding[2] )%2 ==0:
|
116
|
+
r.width-=1
|
117
|
+
r.center = self.get_local_padded_rect().center
|
118
|
+
# r.inflate_ip(3,3)
|
119
|
+
# r.normalize()
|
120
|
+
# r.move_ip(-self.rect.left,-self.rect.right)
|
121
|
+
bf.utils.draw_triangle(
|
122
|
+
surface = self.surface,
|
123
|
+
color = self.arrow_color,
|
124
|
+
rect =r,
|
125
|
+
direction = self.direction,
|
126
|
+
|
127
|
+
)
|
128
|
+
|
129
|
+
|
130
|
+
|
@@ -56,7 +56,7 @@ 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):
|
59
|
+
if isinstance(self.parent,bf.gui.Container):
|
60
60
|
self.parent.layout.scroll_to_widget(self)
|
61
61
|
self.do_on_get_focus()
|
62
62
|
|
@@ -64,97 +64,131 @@ class InteractiveWidget(Widget):
|
|
64
64
|
self.is_focused = False
|
65
65
|
self.do_on_lose_focus()
|
66
66
|
|
67
|
-
|
68
|
-
|
69
|
-
if previous_widget != self and self.visible:
|
70
|
-
if (
|
71
|
-
isinstance(self, InteractiveWidget)
|
72
|
-
and not isinstance(self, bf.Container)
|
73
|
-
and self.allow_focus_to_self()
|
74
|
-
):
|
75
|
-
self.focus_next_sibling()
|
76
|
-
return
|
77
|
-
i_children = [
|
78
|
-
c
|
79
|
-
for c in self.children
|
80
|
-
if isinstance(c, InteractiveWidget) and c.visible
|
81
|
-
]
|
82
|
-
if i_children:
|
83
|
-
index = i_children.index(previous_widget)
|
84
|
-
if index < len(i_children) - 1:
|
85
|
-
|
86
|
-
i_children[index + 1].get_focus()
|
87
|
-
return
|
88
|
-
|
89
|
-
if self.parent and isinstance(self.parent,InteractiveWidget):
|
90
|
-
self.parent.focus_next_tab(self)
|
67
|
+
|
91
68
|
|
92
|
-
def
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
):
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
69
|
+
def get_interactive_widgets(self):
|
70
|
+
"""Retrieve all interactive widgets in the tree, in depth-first order."""
|
71
|
+
widgets = []
|
72
|
+
stack = [self]
|
73
|
+
while stack:
|
74
|
+
widget = stack.pop()
|
75
|
+
if isinstance(widget, InteractiveWidget) and widget.allow_focus_to_self():
|
76
|
+
widgets.append(widget)
|
77
|
+
stack.extend(reversed(widget.children)) # Add children in reverse for left-to-right traversal
|
78
|
+
return widgets
|
79
|
+
|
80
|
+
def find_next_widget(self, current):
|
81
|
+
"""Find the next interactive widget, considering parent and sibling relationships."""
|
82
|
+
if current.is_root:
|
83
|
+
return None # Root has no parent
|
84
|
+
|
85
|
+
siblings = current.parent.children
|
86
|
+
start_index = siblings.index(current)
|
87
|
+
good_index = -1
|
88
|
+
for i in range(start_index + 1, len(siblings)):
|
89
|
+
if isinstance(siblings[i], InteractiveWidget) and siblings[i].allow_focus_to_self():
|
90
|
+
good_index = i
|
91
|
+
break
|
92
|
+
if good_index >= 0:
|
93
|
+
# Not the last child, return the next sibling
|
94
|
+
return siblings[good_index]
|
95
|
+
else:
|
96
|
+
# Current is the last child, move to parent's next sibling
|
97
|
+
return self.find_next_widget(current.parent)
|
98
|
+
|
99
|
+
def find_prev_widget(self, current : "Widget"):
|
100
|
+
"""Find the previous interactive widget, considering parent and sibling relationships."""
|
101
|
+
if current.is_root:
|
102
|
+
return None # Root has no parent
|
103
|
+
|
104
|
+
# siblings = [c for c in current.parent.children if isinstance(c,InteractiveWidget) and c.allow_focus_to_self()]
|
105
|
+
siblings = current.parent.children
|
106
|
+
start_index = siblings.index(current)
|
107
|
+
good_index = -1
|
108
|
+
for i in range(start_index-1,-1,-1):
|
109
|
+
sibling = siblings[i]
|
110
|
+
if isinstance(sibling,InteractiveWidget):
|
111
|
+
if sibling.allow_focus_to_self():
|
112
|
+
good_index = i
|
113
|
+
break
|
114
|
+
if good_index >= 0:
|
115
|
+
# Not the first child, return the previous sibling
|
116
|
+
return siblings[good_index]
|
117
|
+
else:
|
118
|
+
# Current is the first child, move to parent's previous sibling
|
119
|
+
return self.find_prev_widget(current.parent)
|
115
120
|
|
116
|
-
def
|
117
|
-
|
118
|
-
|
119
|
-
|
121
|
+
def focus_next_tab(self, previous_widget):
|
122
|
+
"""Focus the next interactive widget."""
|
123
|
+
if previous_widget:
|
124
|
+
next_widget = self.find_next_widget(previous_widget)
|
125
|
+
if next_widget:
|
126
|
+
next_widget.get_focus()
|
120
127
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
128
|
+
def focus_prev_tab(self, previous_widget):
|
129
|
+
"""Focus the previous interactive widget."""
|
130
|
+
if previous_widget:
|
131
|
+
prev_widget = self.find_prev_widget(previous_widget)
|
132
|
+
if prev_widget:
|
133
|
+
prev_widget.get_focus()
|
126
134
|
|
127
|
-
return self.do_on_key_down(key)
|
128
135
|
|
129
|
-
|
136
|
+
|
137
|
+
def on_key_down(self, key) -> bool:
|
138
|
+
"""
|
139
|
+
return True to stop event progpagation
|
140
|
+
"""
|
141
|
+
return self.do_on_key_down(key)
|
130
142
|
|
131
143
|
def on_key_up(self, key) -> bool:
|
144
|
+
"""
|
145
|
+
return True to stop event progpagation
|
146
|
+
"""
|
132
147
|
return self.do_on_key_up(key)
|
133
148
|
|
149
|
+
def do_on_get_focus(self) -> None:
|
150
|
+
pass
|
151
|
+
|
152
|
+
def do_on_lose_focus(self) -> None:
|
153
|
+
pass
|
154
|
+
|
134
155
|
def do_on_key_down(self, key) -> bool:
|
156
|
+
"""
|
157
|
+
return True to stop event progpagation
|
158
|
+
"""
|
135
159
|
return False
|
136
160
|
|
137
161
|
def do_on_key_up(self, key) -> bool:
|
162
|
+
"""
|
163
|
+
return True to stop event progpagation
|
164
|
+
"""
|
138
165
|
return False
|
139
166
|
|
140
|
-
def do_on_get_focus(self) -> None:
|
141
|
-
pass
|
142
|
-
|
143
|
-
def do_on_lose_focus(self) -> None:
|
144
|
-
pass
|
145
167
|
|
146
168
|
def on_click_down(self, button: int) -> bool:
|
169
|
+
"""
|
170
|
+
return True to stop event progpagation
|
171
|
+
"""
|
147
172
|
self.is_clicked_down = True
|
148
173
|
return self.do_on_click_down(button)
|
149
174
|
|
150
175
|
def on_click_up(self, button: int) -> bool:
|
176
|
+
"""
|
177
|
+
return True to stop event progpagation
|
178
|
+
"""
|
151
179
|
self.is_clicked_down = False
|
152
180
|
return self.do_on_click_up(button)
|
153
181
|
|
154
182
|
def do_on_click_down(self, button: int) -> bool:
|
183
|
+
"""
|
184
|
+
return True to stop event progpagation
|
185
|
+
"""
|
155
186
|
return False
|
156
187
|
|
157
188
|
def do_on_click_up(self, button: int) -> bool:
|
189
|
+
"""
|
190
|
+
return True to stop event progpagation
|
191
|
+
"""
|
158
192
|
return False
|
159
193
|
|
160
194
|
def on_enter(self) -> None:
|
@@ -182,39 +216,22 @@ class InteractiveWidget(Widget):
|
|
182
216
|
pass
|
183
217
|
|
184
218
|
def draw_focused(self, camera: bf.Camera) -> None:
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
#
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
pygame.draw.rect(surface, "
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
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),
|
220
|
-
)
|
219
|
+
prop = 16
|
220
|
+
pulse = int(prop * 0.75 - (prop * cos(pygame.time.get_ticks() / 100) / 4))
|
221
|
+
delta = (pulse // 2) * 2 # ensure even
|
222
|
+
|
223
|
+
# Get rect in screen space, inflated for visual effect
|
224
|
+
screen_rect = camera.world_to_screen(self.rect.inflate(prop, prop))
|
225
|
+
|
226
|
+
# Shrink for inner pulsing border
|
227
|
+
inner = screen_rect.inflate(-delta, -delta)
|
228
|
+
inner.topleft = 0,0
|
229
|
+
surface = pygame.Surface(inner.size)
|
230
|
+
surface.set_colorkey((0,0,0))
|
231
|
+
pygame.draw.rect(surface, "white", inner, 2, *self.border_radius)
|
232
|
+
pygame.draw.rect(surface, "black", inner.inflate(-16,0), 2)
|
233
|
+
pygame.draw.rect(surface, "black", inner.inflate(0,-16), 2)
|
234
|
+
inner.center = screen_rect.center
|
235
|
+
camera.surface.blit(surface,inner)
|
236
|
+
|
237
|
+
# pygame.draw.rect(camera.surface, "white", inner, 2, *self.border_radius)
|