crimsonland 0.1.0.dev11__py3-none-any.whl → 0.1.0.dev13__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 (43) hide show
  1. crimson/assets_fetch.py +23 -8
  2. crimson/creatures/runtime.py +15 -0
  3. crimson/demo.py +47 -38
  4. crimson/effects.py +46 -1
  5. crimson/frontend/boot.py +2 -1
  6. crimson/frontend/high_scores_layout.py +26 -0
  7. crimson/frontend/menu.py +24 -43
  8. crimson/frontend/panels/base.py +27 -29
  9. crimson/frontend/panels/controls.py +152 -65
  10. crimson/frontend/panels/credits.py +221 -0
  11. crimson/frontend/panels/databases.py +307 -0
  12. crimson/frontend/panels/mods.py +1 -3
  13. crimson/frontend/panels/options.py +36 -42
  14. crimson/frontend/panels/play_game.py +82 -74
  15. crimson/frontend/panels/stats.py +255 -298
  16. crimson/frontend/pause_menu.py +425 -0
  17. crimson/game.py +512 -505
  18. crimson/gameplay.py +35 -6
  19. crimson/modes/base_gameplay_mode.py +3 -0
  20. crimson/modes/quest_mode.py +54 -44
  21. crimson/modes/rush_mode.py +4 -1
  22. crimson/modes/survival_mode.py +15 -10
  23. crimson/modes/tutorial_mode.py +15 -5
  24. crimson/modes/typo_mode.py +4 -1
  25. crimson/persistence/highscores.py +6 -2
  26. crimson/render/world_renderer.py +1 -1
  27. crimson/sim/world_state.py +8 -1
  28. crimson/typo/spawns.py +3 -4
  29. crimson/ui/demo_trial_overlay.py +3 -3
  30. crimson/ui/game_over.py +18 -2
  31. crimson/ui/menu_panel.py +127 -0
  32. crimson/ui/perk_menu.py +101 -44
  33. crimson/ui/quest_results.py +669 -0
  34. crimson/ui/shadow.py +39 -0
  35. crimson/views/particles.py +1 -1
  36. crimson/views/perk_menu_debug.py +2 -2
  37. crimson/views/perks.py +2 -2
  38. crimson/weapons.py +110 -110
  39. {crimsonland-0.1.0.dev11.dist-info → crimsonland-0.1.0.dev13.dist-info}/METADATA +1 -1
  40. {crimsonland-0.1.0.dev11.dist-info → crimsonland-0.1.0.dev13.dist-info}/RECORD +43 -36
  41. grim/app.py +3 -0
  42. {crimsonland-0.1.0.dev11.dist-info → crimsonland-0.1.0.dev13.dist-info}/WHEEL +0 -0
  43. {crimsonland-0.1.0.dev11.dist-info → crimsonland-0.1.0.dev13.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,307 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ import pyray as rl
6
+
7
+ from grim.audio import play_sfx, update_audio
8
+ from grim.fonts.small import SmallFontData, draw_small_text, load_small_font
9
+
10
+ from ...ui.menu_panel import draw_classic_menu_panel
11
+ from ...ui.perk_menu import UiButtonState, UiButtonTextureSet, button_draw, button_update, button_width
12
+ from ..assets import MenuAssets, _ensure_texture_cache, load_menu_assets
13
+ from ..menu import (
14
+ MENU_PANEL_OFFSET_X,
15
+ MENU_PANEL_OFFSET_Y,
16
+ MENU_PANEL_WIDTH,
17
+ MENU_SCALE_SMALL_THRESHOLD,
18
+ MENU_SIGN_HEIGHT,
19
+ MENU_SIGN_OFFSET_X,
20
+ MENU_SIGN_OFFSET_Y,
21
+ MENU_SIGN_POS_X_PAD,
22
+ MENU_SIGN_POS_Y,
23
+ MENU_SIGN_POS_Y_SMALL,
24
+ MENU_SIGN_WIDTH,
25
+ UI_SHADOW_OFFSET,
26
+ MenuView,
27
+ _draw_menu_cursor,
28
+ ensure_menu_ground,
29
+ )
30
+ from ..transitions import _draw_screen_fade
31
+ from .base import PANEL_TIMELINE_END_MS, PANEL_TIMELINE_START_MS
32
+
33
+ if TYPE_CHECKING:
34
+ from ...game import GameState
35
+ from grim.terrain_render import GroundRenderer
36
+
37
+
38
+ # Shared panel layout (state_14/15/16 in the oracle): tall left panel + short right panel.
39
+ LEFT_PANEL_POS_X = -119.0
40
+ LEFT_PANEL_POS_Y = 185.0
41
+ LEFT_PANEL_HEIGHT = 378.0
42
+
43
+ RIGHT_PANEL_POS_X = 609.0
44
+ RIGHT_PANEL_POS_Y = 200.0
45
+ RIGHT_PANEL_HEIGHT = 254.0
46
+
47
+
48
+ class _DatabaseBaseView:
49
+ def __init__(self, state: GameState) -> None:
50
+ self._state = state
51
+ self._assets: MenuAssets | None = None
52
+ self._ground: GroundRenderer | None = None
53
+ self._small_font: SmallFontData | None = None
54
+ self._button_textures: UiButtonTextureSet | None = None
55
+
56
+ self._cursor_pulse_time = 0.0
57
+ self._widescreen_y_shift = 0.0
58
+ self._timeline_ms = 0
59
+ self._timeline_max_ms = PANEL_TIMELINE_START_MS
60
+ self._action: str | None = None
61
+
62
+ self._back_button = UiButtonState("Back", force_wide=False)
63
+
64
+ def open(self) -> None:
65
+ layout_w = float(self._state.config.screen_width)
66
+ self._widescreen_y_shift = MenuView._menu_widescreen_y_shift(layout_w)
67
+ self._assets = load_menu_assets(self._state)
68
+ self._ground = None if self._state.pause_background is not None else ensure_menu_ground(self._state)
69
+ self._small_font = None
70
+ self._cursor_pulse_time = 0.0
71
+ self._timeline_ms = 0
72
+ self._timeline_max_ms = PANEL_TIMELINE_START_MS
73
+ self._action = None
74
+
75
+ cache = _ensure_texture_cache(self._state)
76
+ button_md = cache.get_or_load("ui_buttonMd", "ui/ui_button_128x32.jaz").texture
77
+ button_sm = cache.get_or_load("ui_buttonSm", "ui/ui_button_64x32.jaz").texture
78
+ self._button_textures = UiButtonTextureSet(button_sm=button_sm, button_md=button_md)
79
+ self._back_button = UiButtonState("Back", force_wide=False)
80
+
81
+ if self._state.audio is not None:
82
+ play_sfx(self._state.audio, "sfx_ui_panelclick", rng=self._state.rng)
83
+
84
+ def close(self) -> None:
85
+ if self._small_font is not None:
86
+ rl.unload_texture(self._small_font.texture)
87
+ self._small_font = None
88
+ self._button_textures = None
89
+ self._assets = None
90
+ self._ground = None
91
+ self._action = None
92
+
93
+ def take_action(self) -> str | None:
94
+ action = self._action
95
+ self._action = None
96
+ return action
97
+
98
+ def _ensure_small_font(self) -> SmallFontData:
99
+ if self._small_font is not None:
100
+ return self._small_font
101
+ missing_assets: list[str] = []
102
+ self._small_font = load_small_font(self._state.assets_dir, missing_assets)
103
+ return self._small_font
104
+
105
+ def _panel_top_left(self, *, pos_x: float, pos_y: float, scale: float) -> tuple[float, float]:
106
+ x0 = pos_x + MENU_PANEL_OFFSET_X * scale
107
+ y0 = pos_y + self._widescreen_y_shift + MENU_PANEL_OFFSET_Y * scale
108
+ return float(x0), float(y0)
109
+
110
+ def _draw_sign(self) -> None:
111
+ assets = self._assets
112
+ if assets is None or assets.sign is None:
113
+ return
114
+ sign = assets.sign
115
+ screen_w = float(self._state.config.screen_width)
116
+ sign_scale, shift_x = MenuView._sign_layout_scale(int(screen_w))
117
+ pos_x = screen_w + MENU_SIGN_POS_X_PAD
118
+ pos_y = MENU_SIGN_POS_Y if screen_w > MENU_SCALE_SMALL_THRESHOLD else MENU_SIGN_POS_Y_SMALL
119
+ sign_w = MENU_SIGN_WIDTH * sign_scale
120
+ sign_h = MENU_SIGN_HEIGHT * sign_scale
121
+ offset_x = MENU_SIGN_OFFSET_X * sign_scale + shift_x
122
+ offset_y = MENU_SIGN_OFFSET_Y * sign_scale
123
+ rotation_deg = 0.0
124
+ fx_detail = bool(self._state.config.data.get("fx_detail_0", 0))
125
+ if fx_detail:
126
+ MenuView._draw_ui_quad_shadow(
127
+ texture=sign,
128
+ src=rl.Rectangle(0.0, 0.0, float(sign.width), float(sign.height)),
129
+ dst=rl.Rectangle(pos_x + UI_SHADOW_OFFSET, pos_y + UI_SHADOW_OFFSET, sign_w, sign_h),
130
+ origin=rl.Vector2(-offset_x, -offset_y),
131
+ rotation_deg=rotation_deg,
132
+ )
133
+ MenuView._draw_ui_quad(
134
+ texture=sign,
135
+ src=rl.Rectangle(0.0, 0.0, float(sign.width), float(sign.height)),
136
+ dst=rl.Rectangle(pos_x, pos_y, sign_w, sign_h),
137
+ origin=rl.Vector2(-offset_x, -offset_y),
138
+ rotation_deg=rotation_deg,
139
+ tint=rl.WHITE,
140
+ )
141
+
142
+ def update(self, dt: float) -> None:
143
+ if self._state.audio is not None:
144
+ update_audio(self._state.audio, dt)
145
+ if self._ground is not None:
146
+ self._ground.process_pending()
147
+ self._cursor_pulse_time += min(float(dt), 0.1) * 1.1
148
+
149
+ dt_ms = int(min(float(dt), 0.1) * 1000.0)
150
+ if dt_ms > 0:
151
+ self._timeline_ms = min(self._timeline_max_ms, int(self._timeline_ms + dt_ms))
152
+
153
+ enabled = self._timeline_ms >= self._timeline_max_ms
154
+
155
+ if rl.is_key_pressed(rl.KeyboardKey.KEY_ESCAPE) and enabled:
156
+ if self._state.audio is not None:
157
+ play_sfx(self._state.audio, "sfx_ui_buttonclick", rng=self._state.rng)
158
+ self._action = "back_to_previous"
159
+ return
160
+
161
+ textures = self._button_textures
162
+ if textures is None or (textures.button_md is None and textures.button_sm is None):
163
+ return
164
+ if not enabled:
165
+ return
166
+
167
+ scale = 0.9 if float(self._state.config.screen_width) < 641.0 else 1.0
168
+ left_x0, left_y0 = self._panel_top_left(pos_x=LEFT_PANEL_POS_X, pos_y=LEFT_PANEL_POS_Y, scale=scale)
169
+
170
+ mouse = rl.get_mouse_position()
171
+ click = rl.is_mouse_button_pressed(rl.MOUSE_BUTTON_LEFT)
172
+
173
+ bx, by = self._back_button_pos()
174
+ back_w = button_width(None, self._back_button.label, scale=scale, force_wide=self._back_button.force_wide)
175
+ if button_update(
176
+ self._back_button,
177
+ x=left_x0 + float(bx) * scale,
178
+ y=left_y0 + float(by) * scale,
179
+ width=back_w,
180
+ dt_ms=dt_ms,
181
+ mouse=mouse,
182
+ click=click,
183
+ ):
184
+ if self._state.audio is not None:
185
+ play_sfx(self._state.audio, "sfx_ui_buttonclick", rng=self._state.rng)
186
+ self._action = "back_to_previous"
187
+
188
+ def draw(self) -> None:
189
+ rl.clear_background(rl.BLACK)
190
+ pause_background = self._state.pause_background
191
+ if pause_background is not None:
192
+ pause_background.draw_pause_background()
193
+ elif self._ground is not None:
194
+ self._ground.draw(0.0, 0.0)
195
+ _draw_screen_fade(self._state)
196
+
197
+ assets = self._assets
198
+ if assets is None or assets.panel is None:
199
+ return
200
+
201
+ scale = 0.9 if float(self._state.config.screen_width) < 641.0 else 1.0
202
+ fx_detail = bool(self._state.config.data.get("fx_detail_0", 0))
203
+
204
+ panel_w = MENU_PANEL_WIDTH * scale
205
+ _angle_rad, left_slide_x = MenuView._ui_element_anim(
206
+ self,
207
+ index=1,
208
+ start_ms=PANEL_TIMELINE_START_MS,
209
+ end_ms=PANEL_TIMELINE_END_MS,
210
+ width=panel_w,
211
+ direction_flag=0,
212
+ )
213
+ _angle_rad, right_slide_x = MenuView._ui_element_anim(
214
+ self,
215
+ index=2,
216
+ start_ms=PANEL_TIMELINE_START_MS,
217
+ end_ms=PANEL_TIMELINE_END_MS,
218
+ width=panel_w,
219
+ direction_flag=1,
220
+ )
221
+
222
+ left_x0, left_y0 = self._panel_top_left(pos_x=LEFT_PANEL_POS_X, pos_y=LEFT_PANEL_POS_Y, scale=scale)
223
+ right_x0, right_y0 = self._panel_top_left(pos_x=RIGHT_PANEL_POS_X, pos_y=RIGHT_PANEL_POS_Y, scale=scale)
224
+ left_x0 += float(left_slide_x)
225
+ right_x0 += float(right_slide_x)
226
+
227
+ draw_classic_menu_panel(
228
+ assets.panel,
229
+ dst=rl.Rectangle(left_x0, left_y0, panel_w, LEFT_PANEL_HEIGHT * scale),
230
+ tint=rl.WHITE,
231
+ shadow=fx_detail,
232
+ )
233
+ draw_classic_menu_panel(
234
+ assets.panel,
235
+ dst=rl.Rectangle(right_x0, right_y0, panel_w, RIGHT_PANEL_HEIGHT * scale),
236
+ tint=rl.WHITE,
237
+ shadow=fx_detail,
238
+ )
239
+
240
+ font = self._ensure_small_font()
241
+ self._draw_contents(left_x0, left_y0, right_x0, right_y0, scale=scale, font=font)
242
+
243
+ textures = self._button_textures
244
+ if textures is not None and (textures.button_md is not None or textures.button_sm is not None):
245
+ bx, by = self._back_button_pos()
246
+ back_w = button_width(None, self._back_button.label, scale=scale, force_wide=self._back_button.force_wide)
247
+ button_draw(
248
+ textures,
249
+ font,
250
+ self._back_button,
251
+ x=left_x0 + float(bx) * scale,
252
+ y=left_y0 + float(by) * scale,
253
+ width=back_w,
254
+ scale=scale,
255
+ )
256
+
257
+ self._draw_sign()
258
+ _draw_menu_cursor(self._state, pulse_time=self._cursor_pulse_time)
259
+
260
+ def _back_button_pos(self) -> tuple[float, float]:
261
+ raise NotImplementedError
262
+
263
+ def _draw_contents(
264
+ self,
265
+ left_x0: float,
266
+ left_y0: float,
267
+ right_x0: float,
268
+ right_y0: float,
269
+ *,
270
+ scale: float,
271
+ font: SmallFontData,
272
+ ) -> None:
273
+ raise NotImplementedError
274
+
275
+
276
+ class UnlockedWeaponsDatabaseView(_DatabaseBaseView):
277
+ def _back_button_pos(self) -> tuple[float, float]:
278
+ # state_15: ui_buttonSm bbox [270,507]..[352,539] => relative to left panel (-98,194): (368, 313)
279
+ return (368.0, 313.0)
280
+
281
+ def _draw_contents(self, left_x0: float, left_y0: float, right_x0: float, right_y0: float, *, scale: float, font: SmallFontData) -> None:
282
+ # state_15 title at (153,244) => relative to left panel (-98,194): (251,50)
283
+ draw_small_text(
284
+ font,
285
+ "Unlocked Weapons Database",
286
+ left_x0 + 251.0 * scale,
287
+ left_y0 + 50.0 * scale,
288
+ 1.0 * scale,
289
+ rl.Color(255, 255, 255, 255),
290
+ )
291
+
292
+
293
+ class UnlockedPerksDatabaseView(_DatabaseBaseView):
294
+ def _back_button_pos(self) -> tuple[float, float]:
295
+ # state_16: ui_buttonSm bbox [258,509]..[340,541] => relative to left panel (-98,194): (356, 315)
296
+ return (356.0, 315.0)
297
+
298
+ def _draw_contents(self, left_x0: float, left_y0: float, right_x0: float, right_y0: float, *, scale: float, font: SmallFontData) -> None:
299
+ # state_16 title at (163,244) => relative to left panel (-98,194): (261,50)
300
+ draw_small_text(
301
+ font,
302
+ "Unlocked Perks Database",
303
+ left_x0 + 261.0 * scale,
304
+ left_y0 + 50.0 * scale,
305
+ 1.0 * scale,
306
+ rl.Color(255, 255, 255, 255),
307
+ )
@@ -32,9 +32,7 @@ class ModsMenuView(PanelMenuView):
32
32
  self._lines = self._build_lines()
33
33
 
34
34
  def draw(self) -> None:
35
- rl.clear_background(rl.BLACK)
36
- if self._ground is not None:
37
- self._ground.draw(0.0, 0.0)
35
+ self._draw_background()
38
36
  _draw_screen_fade(self._state)
39
37
  assets = self._assets
40
38
  entry = self._entry
@@ -9,6 +9,7 @@ from grim.audio import set_music_volume, set_sfx_volume
9
9
  from grim.config import apply_detail_preset
10
10
  from grim.fonts.small import SmallFontData, draw_small_text, load_small_font, measure_small_text_width
11
11
 
12
+ from ...ui.perk_menu import UiButtonState, UiButtonTextureSet, button_draw, button_update, button_width
12
13
  from ..menu import (
13
14
  MENU_LABEL_ROW_HEIGHT,
14
15
  MENU_LABEL_ROW_OPTIONS,
@@ -42,13 +43,15 @@ class OptionsMenuView(PanelMenuView):
42
43
  )
43
44
 
44
45
  def __init__(self, state: GameState) -> None:
45
- super().__init__(state, title="Options")
46
+ super().__init__(state, title="Options", back_action="open_pause_menu")
46
47
  self._small_font: SmallFontData | None = None
47
48
  self._rect_on: rl.Texture2D | None = None
48
49
  self._rect_off: rl.Texture2D | None = None
49
50
  self._check_on: rl.Texture2D | None = None
50
51
  self._check_off: rl.Texture2D | None = None
51
52
  self._button_tex: rl.Texture2D | None = None
53
+ self._button_textures: UiButtonTextureSet | None = None
54
+ self._controls_button: UiButtonState = UiButtonState("Controls", force_wide=True)
52
55
  self._slider_sfx = SliderState(10, 0, 10)
53
56
  self._slider_music = SliderState(10, 0, 10)
54
57
  self._slider_detail = SliderState(5, 1, 5)
@@ -64,7 +67,10 @@ class OptionsMenuView(PanelMenuView):
64
67
  self._rect_off = cache.get_or_load("ui_rectOff", "ui/ui_rectOff.jaz").texture
65
68
  self._check_on = cache.get_or_load("ui_checkOn", "ui/ui_checkOn.jaz").texture
66
69
  self._check_off = cache.get_or_load("ui_checkOff", "ui/ui_checkOff.jaz").texture
67
- self._button_tex = cache.get_or_load("ui_button_md", "ui/ui_button_145x32.jaz").texture
70
+ self._button_tex = cache.get_or_load("ui_buttonMd", "ui/ui_button_128x32.jaz").texture
71
+ button_sm = cache.get_or_load("ui_buttonSm", "ui/ui_button_64x32.jaz").texture
72
+ self._button_textures = UiButtonTextureSet(button_sm=button_sm, button_md=self._button_tex)
73
+ self._controls_button = UiButtonState("Controls", force_wide=True)
68
74
  self._active_slider = None
69
75
  self._dirty = False
70
76
  self._sync_from_config()
@@ -79,6 +85,7 @@ class OptionsMenuView(PanelMenuView):
79
85
 
80
86
  config = self._state.config
81
87
  layout = self._content_layout()
88
+ base_x = layout["base_x"]
82
89
  label_x = layout["label_x"]
83
90
  base_y = layout["base_y"]
84
91
  scale = layout["scale"]
@@ -118,13 +125,25 @@ class OptionsMenuView(PanelMenuView):
118
125
  config.data["ui_info_texts"] = value
119
126
  self._dirty = True
120
127
 
121
- if self._update_controls_button(label_x - 8.0 * scale, base_y + 155.0 * scale, scale):
122
- self._begin_close_transition("open_controls")
128
+ textures = self._button_textures
129
+ if textures is not None and textures.button_md is not None:
130
+ # `sub_4475d0`: controls button is aligned with the panel content base.
131
+ x = base_x
132
+ y = base_y + 155.0 * scale
133
+ dt_ms = min(float(dt), 0.1) * 1000.0
134
+ mouse = rl.get_mouse_position()
135
+ click = rl.is_mouse_button_pressed(rl.MOUSE_BUTTON_LEFT)
136
+ width = button_width(
137
+ self._ensure_small_font(),
138
+ self._controls_button.label,
139
+ scale=scale,
140
+ force_wide=self._controls_button.force_wide,
141
+ )
142
+ if button_update(self._controls_button, x=x, y=y, width=width, dt_ms=dt_ms, mouse=mouse, click=click):
143
+ self._begin_close_transition("open_controls")
123
144
 
124
145
  def draw(self) -> None:
125
- rl.clear_background(rl.BLACK)
126
- if self._ground is not None:
127
- self._ground.draw(0.0, 0.0)
146
+ self._draw_background()
128
147
  _draw_screen_fade(self._state)
129
148
  assets = self._assets
130
149
  entry = self._entry
@@ -192,7 +211,8 @@ class OptionsMenuView(PanelMenuView):
192
211
  panel_left = panel_x - origin_x
193
212
  panel_top = panel_y - origin_y
194
213
  base_x = panel_left + 212.0 * panel_scale
195
- base_y = panel_top + 32.0 * panel_scale
214
+ # `sub_4475d0`: title label is anchored at panel_top + 40.
215
+ base_y = panel_top + 40.0 * panel_scale
196
216
  label_x = base_x + 8.0 * panel_scale
197
217
  slider_x = label_x + 130.0 * panel_scale
198
218
  return {
@@ -269,18 +289,6 @@ class OptionsMenuView(PanelMenuView):
269
289
  return True
270
290
  return False
271
291
 
272
- def _update_controls_button(self, x: float, y: float, scale: float) -> bool:
273
- tex = self._button_tex
274
- if tex is None:
275
- return False
276
- w = float(tex.width) * scale
277
- h = float(tex.height) * scale
278
- mouse = rl.get_mouse_position()
279
- hovered = x <= mouse.x <= x + w and y <= mouse.y <= y + h
280
- if hovered and rl.is_mouse_button_pressed(rl.MOUSE_BUTTON_LEFT):
281
- return True
282
- return False
283
-
284
292
  def _draw_options_contents(self) -> None:
285
293
  assets = self._assets
286
294
  if assets is None:
@@ -298,16 +306,17 @@ class OptionsMenuView(PanelMenuView):
298
306
  text_color = rl.Color(255, 255, 255, int(255 * 0.8))
299
307
 
300
308
  if labels_tex is not None:
309
+ title_w = 128.0
301
310
  src = rl.Rectangle(
302
311
  0.0,
303
312
  float(MENU_LABEL_ROW_OPTIONS) * MENU_LABEL_ROW_HEIGHT,
304
- MENU_LABEL_WIDTH,
313
+ title_w,
305
314
  MENU_LABEL_ROW_HEIGHT,
306
315
  )
307
316
  dst = rl.Rectangle(
308
317
  base_x,
309
318
  base_y,
310
- MENU_LABEL_WIDTH * scale,
319
+ title_w * scale,
311
320
  MENU_LABEL_ROW_HEIGHT * scale,
312
321
  )
313
322
  MenuView._draw_ui_quad(
@@ -363,27 +372,12 @@ class OptionsMenuView(PanelMenuView):
363
372
  )
364
373
 
365
374
  button = self._button_tex
366
- if button is not None:
367
- button_x = label_x - 8.0 * scale
375
+ textures = self._button_textures
376
+ if button is not None and textures is not None:
377
+ button_x = base_x
368
378
  button_y = base_y + 155.0 * scale
369
- button_w = float(button.width) * scale
370
- button_h = float(button.height) * scale
371
- mouse = rl.get_mouse_position()
372
- hovered = button_x <= mouse.x <= button_x + button_w and button_y <= mouse.y <= button_y + button_h
373
- alpha = 255 if hovered else 220
374
- rl.draw_texture_pro(
375
- button,
376
- rl.Rectangle(0.0, 0.0, float(button.width), float(button.height)),
377
- rl.Rectangle(button_x, button_y, button_w, button_h),
378
- rl.Vector2(0.0, 0.0),
379
- 0.0,
380
- rl.Color(255, 255, 255, alpha),
381
- )
382
- label = "Controls"
383
- label_w = measure_small_text_width(font, label, text_scale)
384
- text_x = button_x + (button_w - label_w) * 0.5
385
- text_y = button_y + (button_h - font.cell_size * text_scale) * 0.5
386
- draw_small_text(font, label, text_x, text_y, text_scale, rl.Color(20, 20, 20, 255))
379
+ button_w = button_width(font, self._controls_button.label, scale=scale, force_wide=self._controls_button.force_wide)
380
+ button_draw(textures, font, self._controls_button, x=button_x, y=button_y, width=button_w, scale=scale)
387
381
 
388
382
  def _draw_slider(
389
383
  self,