crimsonland 0.1.0.dev11__py3-none-any.whl → 0.1.0.dev12__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.
- crimson/creatures/runtime.py +15 -0
- crimson/demo.py +47 -38
- crimson/effects.py +46 -1
- crimson/frontend/boot.py +2 -1
- crimson/frontend/menu.py +6 -27
- crimson/frontend/panels/base.py +13 -3
- crimson/frontend/panels/controls.py +1 -3
- crimson/frontend/panels/mods.py +1 -3
- crimson/frontend/panels/options.py +35 -42
- crimson/frontend/panels/play_game.py +78 -70
- crimson/frontend/panels/stats.py +1 -3
- crimson/frontend/pause_menu.py +425 -0
- crimson/game.py +315 -446
- crimson/gameplay.py +35 -6
- crimson/modes/base_gameplay_mode.py +3 -0
- crimson/modes/quest_mode.py +45 -36
- crimson/modes/rush_mode.py +4 -1
- crimson/modes/survival_mode.py +6 -2
- crimson/modes/tutorial_mode.py +6 -2
- crimson/modes/typo_mode.py +4 -1
- crimson/persistence/highscores.py +6 -2
- crimson/render/world_renderer.py +1 -1
- crimson/sim/world_state.py +8 -1
- crimson/typo/spawns.py +3 -4
- crimson/ui/demo_trial_overlay.py +3 -3
- crimson/ui/game_over.py +18 -2
- crimson/ui/perk_menu.py +106 -14
- crimson/ui/quest_results.py +663 -0
- crimson/ui/shadow.py +39 -0
- crimson/views/particles.py +1 -1
- crimson/weapons.py +110 -110
- {crimsonland-0.1.0.dev11.dist-info → crimsonland-0.1.0.dev12.dist-info}/METADATA +1 -1
- {crimsonland-0.1.0.dev11.dist-info → crimsonland-0.1.0.dev12.dist-info}/RECORD +36 -33
- grim/app.py +3 -0
- {crimsonland-0.1.0.dev11.dist-info → crimsonland-0.1.0.dev12.dist-info}/WHEEL +0 -0
- {crimsonland-0.1.0.dev11.dist-info → crimsonland-0.1.0.dev12.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
import math
|
|
5
|
+
|
|
6
|
+
import pyray as rl
|
|
7
|
+
|
|
8
|
+
from grim.audio import play_sfx, update_audio
|
|
9
|
+
|
|
10
|
+
from .assets import MenuAssets, load_menu_assets
|
|
11
|
+
from .menu import (
|
|
12
|
+
MENU_ITEM_OFFSET_X,
|
|
13
|
+
MENU_ITEM_OFFSET_Y,
|
|
14
|
+
MENU_LABEL_BASE_Y,
|
|
15
|
+
MENU_LABEL_HEIGHT,
|
|
16
|
+
MENU_LABEL_OFFSET_X,
|
|
17
|
+
MENU_LABEL_OFFSET_Y,
|
|
18
|
+
MENU_LABEL_ROW_BACK,
|
|
19
|
+
MENU_LABEL_ROW_HEIGHT,
|
|
20
|
+
MENU_LABEL_ROW_OPTIONS,
|
|
21
|
+
MENU_LABEL_ROW_QUIT,
|
|
22
|
+
MENU_LABEL_STEP,
|
|
23
|
+
MENU_LABEL_WIDTH,
|
|
24
|
+
MENU_SCALE_SMALL_THRESHOLD,
|
|
25
|
+
MENU_SIGN_HEIGHT,
|
|
26
|
+
MENU_SIGN_OFFSET_X,
|
|
27
|
+
MENU_SIGN_OFFSET_Y,
|
|
28
|
+
MENU_SIGN_POS_X_PAD,
|
|
29
|
+
MENU_SIGN_POS_Y,
|
|
30
|
+
MENU_SIGN_POS_Y_SMALL,
|
|
31
|
+
MENU_SIGN_WIDTH,
|
|
32
|
+
UI_SHADOW_OFFSET,
|
|
33
|
+
MenuEntry,
|
|
34
|
+
MenuView,
|
|
35
|
+
_draw_menu_cursor,
|
|
36
|
+
)
|
|
37
|
+
from .transitions import _draw_screen_fade
|
|
38
|
+
|
|
39
|
+
if TYPE_CHECKING:
|
|
40
|
+
from ..game import GameState, PauseBackground
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class PauseMenuView:
|
|
44
|
+
def __init__(self, state: GameState) -> None:
|
|
45
|
+
self._state = state
|
|
46
|
+
self._assets: MenuAssets | None = None
|
|
47
|
+
self._menu_entries: list[MenuEntry] = []
|
|
48
|
+
self._selected_index = 0
|
|
49
|
+
self._focus_timer_ms = 0
|
|
50
|
+
self._hovered_index: int | None = None
|
|
51
|
+
self._timeline_ms = 0
|
|
52
|
+
self._timeline_max_ms = 0
|
|
53
|
+
self._cursor_pulse_time = 0.0
|
|
54
|
+
self._widescreen_y_shift = 0.0
|
|
55
|
+
self._menu_screen_width = 0
|
|
56
|
+
self._closing = False
|
|
57
|
+
self._close_action: str | None = None
|
|
58
|
+
self._pending_action: str | None = None
|
|
59
|
+
self._panel_open_sfx_played = False
|
|
60
|
+
|
|
61
|
+
def open(self) -> None:
|
|
62
|
+
layout_w = float(self._state.config.screen_width)
|
|
63
|
+
self._menu_screen_width = int(layout_w)
|
|
64
|
+
self._widescreen_y_shift = MenuView._menu_widescreen_y_shift(layout_w)
|
|
65
|
+
self._assets = load_menu_assets(self._state)
|
|
66
|
+
|
|
67
|
+
ys = [
|
|
68
|
+
MENU_LABEL_BASE_Y + self._widescreen_y_shift,
|
|
69
|
+
MENU_LABEL_BASE_Y + MENU_LABEL_STEP + self._widescreen_y_shift,
|
|
70
|
+
MENU_LABEL_BASE_Y + MENU_LABEL_STEP * 2.0 + self._widescreen_y_shift,
|
|
71
|
+
]
|
|
72
|
+
self._menu_entries = [
|
|
73
|
+
MenuEntry(slot=0, row=MENU_LABEL_ROW_OPTIONS, y=ys[0]),
|
|
74
|
+
MenuEntry(slot=1, row=MENU_LABEL_ROW_QUIT, y=ys[1]),
|
|
75
|
+
MenuEntry(slot=2, row=MENU_LABEL_ROW_BACK, y=ys[2]),
|
|
76
|
+
]
|
|
77
|
+
self._selected_index = 0 if self._menu_entries else -1
|
|
78
|
+
self._focus_timer_ms = 0
|
|
79
|
+
self._hovered_index = None
|
|
80
|
+
self._timeline_ms = 0
|
|
81
|
+
self._timeline_max_ms = max(300, *(MenuView._menu_slot_start_ms(entry.slot) for entry in self._menu_entries))
|
|
82
|
+
self._cursor_pulse_time = 0.0
|
|
83
|
+
self._closing = False
|
|
84
|
+
self._close_action = None
|
|
85
|
+
self._pending_action = None
|
|
86
|
+
self._panel_open_sfx_played = False
|
|
87
|
+
|
|
88
|
+
def close(self) -> None:
|
|
89
|
+
self._assets = None
|
|
90
|
+
self._menu_entries = []
|
|
91
|
+
|
|
92
|
+
def update(self, dt: float) -> None:
|
|
93
|
+
if self._state.audio is not None:
|
|
94
|
+
update_audio(self._state.audio, dt)
|
|
95
|
+
self._cursor_pulse_time += min(dt, 0.1) * 1.1
|
|
96
|
+
|
|
97
|
+
dt_ms = int(min(dt, 0.1) * 1000.0)
|
|
98
|
+
if self._closing:
|
|
99
|
+
if dt_ms > 0 and self._pending_action is None:
|
|
100
|
+
self._timeline_ms -= dt_ms
|
|
101
|
+
self._focus_timer_ms = max(0, self._focus_timer_ms - dt_ms)
|
|
102
|
+
if self._timeline_ms < 0 and self._close_action is not None:
|
|
103
|
+
self._pending_action = self._close_action
|
|
104
|
+
self._close_action = None
|
|
105
|
+
return
|
|
106
|
+
|
|
107
|
+
if dt_ms > 0:
|
|
108
|
+
self._timeline_ms = min(self._timeline_max_ms, self._timeline_ms + dt_ms)
|
|
109
|
+
self._focus_timer_ms = max(0, self._focus_timer_ms - dt_ms)
|
|
110
|
+
if self._timeline_ms >= self._timeline_max_ms:
|
|
111
|
+
self._state.menu_sign_locked = True
|
|
112
|
+
if (not self._panel_open_sfx_played) and (self._state.audio is not None):
|
|
113
|
+
play_sfx(self._state.audio, "sfx_ui_panelclick", rng=self._state.rng)
|
|
114
|
+
self._panel_open_sfx_played = True
|
|
115
|
+
|
|
116
|
+
if not self._menu_entries:
|
|
117
|
+
return
|
|
118
|
+
|
|
119
|
+
self._hovered_index = self._hovered_entry_index()
|
|
120
|
+
|
|
121
|
+
if rl.is_key_pressed(rl.KeyboardKey.KEY_TAB):
|
|
122
|
+
reverse = rl.is_key_down(rl.KeyboardKey.KEY_LEFT_SHIFT) or rl.is_key_down(rl.KeyboardKey.KEY_RIGHT_SHIFT)
|
|
123
|
+
delta = -1 if reverse else 1
|
|
124
|
+
self._selected_index = (self._selected_index + delta) % len(self._menu_entries)
|
|
125
|
+
self._focus_timer_ms = 1000
|
|
126
|
+
|
|
127
|
+
activated_index: int | None = None
|
|
128
|
+
if rl.is_key_pressed(rl.KeyboardKey.KEY_ESCAPE):
|
|
129
|
+
# ESC behaves like selecting Back.
|
|
130
|
+
activated_index = self._entry_index_for_row(MENU_LABEL_ROW_BACK)
|
|
131
|
+
elif rl.is_key_pressed(rl.KeyboardKey.KEY_ENTER) and 0 <= self._selected_index < len(self._menu_entries):
|
|
132
|
+
entry = self._menu_entries[self._selected_index]
|
|
133
|
+
if self._menu_entry_enabled(entry):
|
|
134
|
+
activated_index = self._selected_index
|
|
135
|
+
|
|
136
|
+
if activated_index is None and self._hovered_index is not None:
|
|
137
|
+
if rl.is_mouse_button_pressed(rl.MOUSE_BUTTON_LEFT):
|
|
138
|
+
hovered = self._hovered_index
|
|
139
|
+
entry = self._menu_entries[hovered]
|
|
140
|
+
if self._menu_entry_enabled(entry):
|
|
141
|
+
self._selected_index = hovered
|
|
142
|
+
self._focus_timer_ms = 1000
|
|
143
|
+
activated_index = hovered
|
|
144
|
+
|
|
145
|
+
if activated_index is not None:
|
|
146
|
+
self._activate_menu_entry(activated_index)
|
|
147
|
+
|
|
148
|
+
self._update_ready_timers(dt_ms)
|
|
149
|
+
self._update_hover_amounts(dt_ms)
|
|
150
|
+
|
|
151
|
+
def draw(self) -> None:
|
|
152
|
+
rl.clear_background(rl.BLACK)
|
|
153
|
+
pause_background = self._pause_background()
|
|
154
|
+
if pause_background is not None:
|
|
155
|
+
pause_background.draw_pause_background()
|
|
156
|
+
_draw_screen_fade(self._state)
|
|
157
|
+
|
|
158
|
+
assets = self._assets
|
|
159
|
+
if assets is None:
|
|
160
|
+
return
|
|
161
|
+
self._draw_menu_items()
|
|
162
|
+
self._draw_menu_sign()
|
|
163
|
+
_draw_menu_cursor(self._state, pulse_time=self._cursor_pulse_time)
|
|
164
|
+
|
|
165
|
+
def take_action(self) -> str | None:
|
|
166
|
+
action = self._pending_action
|
|
167
|
+
self._pending_action = None
|
|
168
|
+
return action
|
|
169
|
+
|
|
170
|
+
def _pause_background(self) -> PauseBackground | None:
|
|
171
|
+
return self._state.pause_background
|
|
172
|
+
|
|
173
|
+
def _activate_menu_entry(self, index: int) -> None:
|
|
174
|
+
if not (0 <= index < len(self._menu_entries)):
|
|
175
|
+
return
|
|
176
|
+
entry = self._menu_entries[index]
|
|
177
|
+
action = self._action_for_entry(entry)
|
|
178
|
+
if action is None:
|
|
179
|
+
return
|
|
180
|
+
if self._state.audio is not None:
|
|
181
|
+
play_sfx(self._state.audio, "sfx_ui_buttonclick", rng=self._state.rng)
|
|
182
|
+
self._begin_close_transition(action)
|
|
183
|
+
|
|
184
|
+
@staticmethod
|
|
185
|
+
def _action_for_entry(entry: MenuEntry) -> str | None:
|
|
186
|
+
if entry.row == MENU_LABEL_ROW_OPTIONS:
|
|
187
|
+
return "open_options"
|
|
188
|
+
if entry.row == MENU_LABEL_ROW_QUIT:
|
|
189
|
+
return "back_to_menu"
|
|
190
|
+
if entry.row == MENU_LABEL_ROW_BACK:
|
|
191
|
+
return "back_to_previous"
|
|
192
|
+
return None
|
|
193
|
+
|
|
194
|
+
def _begin_close_transition(self, action: str) -> None:
|
|
195
|
+
if self._closing:
|
|
196
|
+
return
|
|
197
|
+
self._closing = True
|
|
198
|
+
self._close_action = action
|
|
199
|
+
|
|
200
|
+
def _menu_item_scale(self, slot: int) -> tuple[float, float]:
|
|
201
|
+
if self._menu_screen_width < (MENU_SCALE_SMALL_THRESHOLD + 1):
|
|
202
|
+
return 0.9, float(slot) * 11.0
|
|
203
|
+
return 1.0, 0.0
|
|
204
|
+
|
|
205
|
+
def _ui_element_anim(
|
|
206
|
+
self,
|
|
207
|
+
*,
|
|
208
|
+
index: int,
|
|
209
|
+
start_ms: int,
|
|
210
|
+
end_ms: int,
|
|
211
|
+
width: float,
|
|
212
|
+
) -> tuple[float, float]:
|
|
213
|
+
# Matches ui_element_update: angle lerps pi/2 -> 0 over [end_ms, start_ms].
|
|
214
|
+
# Direction flag (element+0x314) appears to be 0 for menu elements.
|
|
215
|
+
if start_ms <= end_ms or width <= 0.0:
|
|
216
|
+
return 0.0, 0.0
|
|
217
|
+
t = self._timeline_ms
|
|
218
|
+
if t < end_ms:
|
|
219
|
+
angle = 1.5707964
|
|
220
|
+
offset_x = -abs(width)
|
|
221
|
+
elif t < start_ms:
|
|
222
|
+
elapsed = t - end_ms
|
|
223
|
+
span = float(start_ms - end_ms)
|
|
224
|
+
p = float(elapsed) / span
|
|
225
|
+
angle = 1.5707964 * (1.0 - p)
|
|
226
|
+
offset_x = -((1.0 - p) * abs(width))
|
|
227
|
+
else:
|
|
228
|
+
angle = 0.0
|
|
229
|
+
offset_x = 0.0
|
|
230
|
+
if index == 0:
|
|
231
|
+
angle = -abs(angle)
|
|
232
|
+
return angle, offset_x
|
|
233
|
+
|
|
234
|
+
def _menu_item_bounds(self, entry: MenuEntry) -> tuple[float, float, float, float]:
|
|
235
|
+
assets = self._assets
|
|
236
|
+
if assets is None or assets.item is None:
|
|
237
|
+
return (0.0, 0.0, 0.0, 0.0)
|
|
238
|
+
item_w = float(assets.item.width)
|
|
239
|
+
item_h = float(assets.item.height)
|
|
240
|
+
item_scale, local_y_shift = self._menu_item_scale(entry.slot)
|
|
241
|
+
x0 = MENU_ITEM_OFFSET_X * item_scale
|
|
242
|
+
y0 = MENU_ITEM_OFFSET_Y * item_scale - local_y_shift
|
|
243
|
+
x2 = (MENU_ITEM_OFFSET_X + item_w) * item_scale
|
|
244
|
+
y2 = (MENU_ITEM_OFFSET_Y + item_h) * item_scale - local_y_shift
|
|
245
|
+
w = x2 - x0
|
|
246
|
+
h = y2 - y0
|
|
247
|
+
pos_x = MenuView._menu_slot_pos_x(entry.slot)
|
|
248
|
+
pos_y = entry.y
|
|
249
|
+
left = pos_x + x0 + w * 0.54
|
|
250
|
+
top = pos_y + y0 + h * 0.28
|
|
251
|
+
right = pos_x + x2 - w * 0.05
|
|
252
|
+
bottom = pos_y + y2 - h * 0.10
|
|
253
|
+
return left, top, right, bottom
|
|
254
|
+
|
|
255
|
+
def _hovered_entry_index(self) -> int | None:
|
|
256
|
+
if not self._menu_entries:
|
|
257
|
+
return None
|
|
258
|
+
mouse = rl.get_mouse_position()
|
|
259
|
+
mouse_x = float(mouse.x)
|
|
260
|
+
mouse_y = float(mouse.y)
|
|
261
|
+
for idx, entry in enumerate(self._menu_entries):
|
|
262
|
+
if not self._menu_entry_enabled(entry):
|
|
263
|
+
continue
|
|
264
|
+
left, top, right, bottom = self._menu_item_bounds(entry)
|
|
265
|
+
if left <= mouse_x <= right and top <= mouse_y <= bottom:
|
|
266
|
+
return idx
|
|
267
|
+
return None
|
|
268
|
+
|
|
269
|
+
def _update_ready_timers(self, dt_ms: int) -> None:
|
|
270
|
+
for entry in self._menu_entries:
|
|
271
|
+
if entry.ready_timer_ms < 0x100:
|
|
272
|
+
entry.ready_timer_ms = min(0x100, entry.ready_timer_ms + dt_ms)
|
|
273
|
+
|
|
274
|
+
def _update_hover_amounts(self, dt_ms: int) -> None:
|
|
275
|
+
hovered_index = self._hovered_index
|
|
276
|
+
for idx, entry in enumerate(self._menu_entries):
|
|
277
|
+
hover = hovered_index is not None and idx == hovered_index
|
|
278
|
+
if hover:
|
|
279
|
+
entry.hover_amount += dt_ms * 6
|
|
280
|
+
else:
|
|
281
|
+
entry.hover_amount -= dt_ms * 2
|
|
282
|
+
entry.hover_amount = max(0, min(1000, entry.hover_amount))
|
|
283
|
+
|
|
284
|
+
def _menu_entry_enabled(self, entry: MenuEntry) -> bool:
|
|
285
|
+
return self._timeline_ms >= MenuView._menu_slot_start_ms(entry.slot)
|
|
286
|
+
|
|
287
|
+
def _draw_menu_items(self) -> None:
|
|
288
|
+
assets = self._assets
|
|
289
|
+
if assets is None or assets.labels is None or not self._menu_entries:
|
|
290
|
+
return
|
|
291
|
+
item = assets.item
|
|
292
|
+
if item is None:
|
|
293
|
+
return
|
|
294
|
+
label_tex = assets.labels
|
|
295
|
+
item_w = float(item.width)
|
|
296
|
+
item_h = float(item.height)
|
|
297
|
+
fx_detail = bool(self._state.config.data.get("fx_detail_0", 0))
|
|
298
|
+
for idx in range(len(self._menu_entries) - 1, -1, -1):
|
|
299
|
+
entry = self._menu_entries[idx]
|
|
300
|
+
pos_x = MenuView._menu_slot_pos_x(entry.slot)
|
|
301
|
+
pos_y = entry.y
|
|
302
|
+
angle_rad, slide_x = self._ui_element_anim(
|
|
303
|
+
index=entry.slot + 2,
|
|
304
|
+
start_ms=MenuView._menu_slot_start_ms(entry.slot),
|
|
305
|
+
end_ms=MenuView._menu_slot_end_ms(entry.slot),
|
|
306
|
+
width=item_w,
|
|
307
|
+
)
|
|
308
|
+
_ = slide_x # slide is ignored for render_mode==0 (transform) elements
|
|
309
|
+
item_scale, local_y_shift = self._menu_item_scale(entry.slot)
|
|
310
|
+
offset_x = MENU_ITEM_OFFSET_X * item_scale
|
|
311
|
+
offset_y = MENU_ITEM_OFFSET_Y * item_scale - local_y_shift
|
|
312
|
+
dst = rl.Rectangle(
|
|
313
|
+
pos_x,
|
|
314
|
+
pos_y,
|
|
315
|
+
item_w * item_scale,
|
|
316
|
+
item_h * item_scale,
|
|
317
|
+
)
|
|
318
|
+
origin = rl.Vector2(-offset_x, -offset_y)
|
|
319
|
+
rotation_deg = math.degrees(angle_rad)
|
|
320
|
+
if fx_detail:
|
|
321
|
+
MenuView._draw_ui_quad_shadow(
|
|
322
|
+
texture=item,
|
|
323
|
+
src=rl.Rectangle(0.0, 0.0, item_w, item_h),
|
|
324
|
+
dst=rl.Rectangle(dst.x + UI_SHADOW_OFFSET, dst.y + UI_SHADOW_OFFSET, dst.width, dst.height),
|
|
325
|
+
origin=origin,
|
|
326
|
+
rotation_deg=rotation_deg,
|
|
327
|
+
)
|
|
328
|
+
MenuView._draw_ui_quad(
|
|
329
|
+
texture=item,
|
|
330
|
+
src=rl.Rectangle(0.0, 0.0, item_w, item_h),
|
|
331
|
+
dst=dst,
|
|
332
|
+
origin=origin,
|
|
333
|
+
rotation_deg=rotation_deg,
|
|
334
|
+
tint=rl.WHITE,
|
|
335
|
+
)
|
|
336
|
+
counter_value = entry.hover_amount
|
|
337
|
+
if idx == self._selected_index and self._focus_timer_ms > 0:
|
|
338
|
+
counter_value = self._focus_timer_ms
|
|
339
|
+
alpha = MenuView._label_alpha(counter_value)
|
|
340
|
+
tint = rl.Color(255, 255, 255, alpha)
|
|
341
|
+
src = rl.Rectangle(
|
|
342
|
+
0.0,
|
|
343
|
+
float(entry.row) * MENU_LABEL_ROW_HEIGHT,
|
|
344
|
+
MENU_LABEL_WIDTH,
|
|
345
|
+
MENU_LABEL_ROW_HEIGHT,
|
|
346
|
+
)
|
|
347
|
+
label_offset_x = MENU_LABEL_OFFSET_X * item_scale
|
|
348
|
+
label_offset_y = MENU_LABEL_OFFSET_Y * item_scale - local_y_shift
|
|
349
|
+
label_dst = rl.Rectangle(
|
|
350
|
+
pos_x,
|
|
351
|
+
pos_y,
|
|
352
|
+
MENU_LABEL_WIDTH * item_scale,
|
|
353
|
+
MENU_LABEL_HEIGHT * item_scale,
|
|
354
|
+
)
|
|
355
|
+
label_origin = rl.Vector2(-label_offset_x, -label_offset_y)
|
|
356
|
+
MenuView._draw_ui_quad(
|
|
357
|
+
texture=label_tex,
|
|
358
|
+
src=src,
|
|
359
|
+
dst=label_dst,
|
|
360
|
+
origin=label_origin,
|
|
361
|
+
rotation_deg=rotation_deg,
|
|
362
|
+
tint=tint,
|
|
363
|
+
)
|
|
364
|
+
if self._menu_entry_enabled(entry):
|
|
365
|
+
glow_alpha = alpha
|
|
366
|
+
if 0 <= entry.ready_timer_ms < 0x100:
|
|
367
|
+
glow_alpha = 0xFF - (entry.ready_timer_ms // 2)
|
|
368
|
+
rl.begin_blend_mode(rl.BLEND_ADDITIVE)
|
|
369
|
+
MenuView._draw_ui_quad(
|
|
370
|
+
texture=label_tex,
|
|
371
|
+
src=src,
|
|
372
|
+
dst=label_dst,
|
|
373
|
+
origin=label_origin,
|
|
374
|
+
rotation_deg=rotation_deg,
|
|
375
|
+
tint=rl.Color(255, 255, 255, glow_alpha),
|
|
376
|
+
)
|
|
377
|
+
rl.end_blend_mode()
|
|
378
|
+
|
|
379
|
+
def _draw_menu_sign(self) -> None:
|
|
380
|
+
assets = self._assets
|
|
381
|
+
if assets is None or assets.sign is None:
|
|
382
|
+
return
|
|
383
|
+
screen_w = float(self._state.config.screen_width)
|
|
384
|
+
scale, shift_x = MenuView._sign_layout_scale(int(screen_w))
|
|
385
|
+
pos_x = screen_w + MENU_SIGN_POS_X_PAD
|
|
386
|
+
pos_y = MENU_SIGN_POS_Y if screen_w > MENU_SCALE_SMALL_THRESHOLD else MENU_SIGN_POS_Y_SMALL
|
|
387
|
+
sign_w = MENU_SIGN_WIDTH * scale
|
|
388
|
+
sign_h = MENU_SIGN_HEIGHT * scale
|
|
389
|
+
offset_x = MENU_SIGN_OFFSET_X * scale + shift_x
|
|
390
|
+
offset_y = MENU_SIGN_OFFSET_Y * scale
|
|
391
|
+
rotation_deg = 0.0
|
|
392
|
+
if not self._state.menu_sign_locked:
|
|
393
|
+
angle_rad, slide_x = self._ui_element_anim(
|
|
394
|
+
index=0,
|
|
395
|
+
start_ms=300,
|
|
396
|
+
end_ms=0,
|
|
397
|
+
width=sign_w,
|
|
398
|
+
)
|
|
399
|
+
_ = slide_x
|
|
400
|
+
rotation_deg = math.degrees(angle_rad)
|
|
401
|
+
sign = assets.sign
|
|
402
|
+
fx_detail = bool(self._state.config.data.get("fx_detail_0", 0))
|
|
403
|
+
if fx_detail:
|
|
404
|
+
MenuView._draw_ui_quad_shadow(
|
|
405
|
+
texture=sign,
|
|
406
|
+
src=rl.Rectangle(0.0, 0.0, float(sign.width), float(sign.height)),
|
|
407
|
+
dst=rl.Rectangle(pos_x + UI_SHADOW_OFFSET, pos_y + UI_SHADOW_OFFSET, sign_w, sign_h),
|
|
408
|
+
origin=rl.Vector2(-offset_x, -offset_y),
|
|
409
|
+
rotation_deg=rotation_deg,
|
|
410
|
+
)
|
|
411
|
+
MenuView._draw_ui_quad(
|
|
412
|
+
texture=sign,
|
|
413
|
+
src=rl.Rectangle(0.0, 0.0, float(sign.width), float(sign.height)),
|
|
414
|
+
dst=rl.Rectangle(pos_x, pos_y, sign_w, sign_h),
|
|
415
|
+
origin=rl.Vector2(-offset_x, -offset_y),
|
|
416
|
+
rotation_deg=rotation_deg,
|
|
417
|
+
tint=rl.WHITE,
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
def _entry_index_for_row(self, row: int) -> int | None:
|
|
421
|
+
for idx, entry in enumerate(self._menu_entries):
|
|
422
|
+
if entry.row == row:
|
|
423
|
+
return idx
|
|
424
|
+
return None
|
|
425
|
+
|