crimsonland 0.1.0.dev5__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 (139) hide show
  1. crimson/__init__.py +24 -0
  2. crimson/assets_fetch.py +60 -0
  3. crimson/atlas.py +92 -0
  4. crimson/audio_router.py +155 -0
  5. crimson/bonuses.py +167 -0
  6. crimson/camera.py +75 -0
  7. crimson/cli.py +380 -0
  8. crimson/creatures/__init__.py +8 -0
  9. crimson/creatures/ai.py +186 -0
  10. crimson/creatures/anim.py +173 -0
  11. crimson/creatures/damage.py +103 -0
  12. crimson/creatures/runtime.py +1019 -0
  13. crimson/creatures/spawn.py +2871 -0
  14. crimson/debug.py +7 -0
  15. crimson/demo.py +1360 -0
  16. crimson/demo_trial.py +140 -0
  17. crimson/effects.py +1086 -0
  18. crimson/effects_atlas.py +73 -0
  19. crimson/frontend/__init__.py +1 -0
  20. crimson/frontend/assets.py +43 -0
  21. crimson/frontend/boot.py +424 -0
  22. crimson/frontend/menu.py +700 -0
  23. crimson/frontend/panels/__init__.py +1 -0
  24. crimson/frontend/panels/base.py +410 -0
  25. crimson/frontend/panels/controls.py +132 -0
  26. crimson/frontend/panels/mods.py +128 -0
  27. crimson/frontend/panels/options.py +409 -0
  28. crimson/frontend/panels/play_game.py +627 -0
  29. crimson/frontend/panels/stats.py +351 -0
  30. crimson/frontend/transitions.py +31 -0
  31. crimson/game.py +2533 -0
  32. crimson/game_modes.py +15 -0
  33. crimson/game_world.py +652 -0
  34. crimson/gameplay.py +2467 -0
  35. crimson/input_codes.py +176 -0
  36. crimson/modes/__init__.py +1 -0
  37. crimson/modes/base_gameplay_mode.py +219 -0
  38. crimson/modes/quest_mode.py +502 -0
  39. crimson/modes/rush_mode.py +300 -0
  40. crimson/modes/survival_mode.py +792 -0
  41. crimson/modes/tutorial_mode.py +648 -0
  42. crimson/modes/typo_mode.py +472 -0
  43. crimson/paths.py +23 -0
  44. crimson/perks.py +828 -0
  45. crimson/persistence/__init__.py +1 -0
  46. crimson/persistence/highscores.py +385 -0
  47. crimson/persistence/save_status.py +245 -0
  48. crimson/player_damage.py +77 -0
  49. crimson/projectiles.py +1133 -0
  50. crimson/quests/__init__.py +18 -0
  51. crimson/quests/helpers.py +147 -0
  52. crimson/quests/registry.py +49 -0
  53. crimson/quests/results.py +164 -0
  54. crimson/quests/runtime.py +91 -0
  55. crimson/quests/tier1.py +620 -0
  56. crimson/quests/tier2.py +652 -0
  57. crimson/quests/tier3.py +579 -0
  58. crimson/quests/tier4.py +721 -0
  59. crimson/quests/tier5.py +886 -0
  60. crimson/quests/timeline.py +115 -0
  61. crimson/quests/types.py +70 -0
  62. crimson/render/__init__.py +1 -0
  63. crimson/render/terrain_fx.py +88 -0
  64. crimson/render/world_renderer.py +1941 -0
  65. crimson/sim/__init__.py +1 -0
  66. crimson/sim/world_defs.py +67 -0
  67. crimson/sim/world_state.py +422 -0
  68. crimson/terrain_assets.py +19 -0
  69. crimson/tutorial/__init__.py +12 -0
  70. crimson/tutorial/timeline.py +291 -0
  71. crimson/typo/__init__.py +2 -0
  72. crimson/typo/names.py +233 -0
  73. crimson/typo/player.py +43 -0
  74. crimson/typo/spawns.py +73 -0
  75. crimson/typo/typing.py +52 -0
  76. crimson/ui/__init__.py +3 -0
  77. crimson/ui/cursor.py +95 -0
  78. crimson/ui/demo_trial_overlay.py +235 -0
  79. crimson/ui/game_over.py +660 -0
  80. crimson/ui/hud.py +601 -0
  81. crimson/ui/perk_menu.py +388 -0
  82. crimson/views/__init__.py +40 -0
  83. crimson/views/aim_debug.py +276 -0
  84. crimson/views/animations.py +274 -0
  85. crimson/views/arsenal_debug.py +404 -0
  86. crimson/views/audio_bootstrap.py +47 -0
  87. crimson/views/bonuses.py +201 -0
  88. crimson/views/camera_debug.py +359 -0
  89. crimson/views/camera_shake.py +229 -0
  90. crimson/views/corpse_stamp_debug.py +324 -0
  91. crimson/views/decals_debug.py +739 -0
  92. crimson/views/empty.py +19 -0
  93. crimson/views/fonts.py +114 -0
  94. crimson/views/game_over.py +117 -0
  95. crimson/views/ground.py +259 -0
  96. crimson/views/lighting_debug.py +1166 -0
  97. crimson/views/particles.py +293 -0
  98. crimson/views/perk_menu_debug.py +430 -0
  99. crimson/views/perks.py +398 -0
  100. crimson/views/player.py +434 -0
  101. crimson/views/player_sprite_debug.py +314 -0
  102. crimson/views/projectile_fx.py +609 -0
  103. crimson/views/projectile_render_debug.py +393 -0
  104. crimson/views/projectiles.py +221 -0
  105. crimson/views/quest_title_overlay.py +108 -0
  106. crimson/views/registry.py +34 -0
  107. crimson/views/rush.py +16 -0
  108. crimson/views/small_font_debug.py +204 -0
  109. crimson/views/spawn_plan.py +363 -0
  110. crimson/views/sprites.py +214 -0
  111. crimson/views/survival.py +15 -0
  112. crimson/views/terrain.py +132 -0
  113. crimson/views/ui.py +123 -0
  114. crimson/views/wicons.py +166 -0
  115. crimson/weapon_sfx.py +63 -0
  116. crimson/weapons.py +860 -0
  117. crimsonland-0.1.0.dev5.dist-info/METADATA +9 -0
  118. crimsonland-0.1.0.dev5.dist-info/RECORD +139 -0
  119. crimsonland-0.1.0.dev5.dist-info/WHEEL +4 -0
  120. crimsonland-0.1.0.dev5.dist-info/entry_points.txt +4 -0
  121. grim/__init__.py +20 -0
  122. grim/app.py +92 -0
  123. grim/assets.py +231 -0
  124. grim/audio.py +106 -0
  125. grim/config.py +294 -0
  126. grim/console.py +737 -0
  127. grim/fonts/__init__.py +7 -0
  128. grim/fonts/grim_mono.py +111 -0
  129. grim/fonts/small.py +120 -0
  130. grim/input.py +44 -0
  131. grim/jaz.py +103 -0
  132. grim/math.py +17 -0
  133. grim/music.py +403 -0
  134. grim/paq.py +76 -0
  135. grim/rand.py +37 -0
  136. grim/sfx.py +276 -0
  137. grim/sfx_map.py +103 -0
  138. grim/terrain_render.py +840 -0
  139. grim/view.py +16 -0
@@ -0,0 +1,409 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import TYPE_CHECKING
5
+
6
+ import pyray as rl
7
+
8
+ from grim.audio import set_music_volume, set_sfx_volume
9
+ from grim.config import apply_detail_preset
10
+ from grim.fonts.small import SmallFontData, draw_small_text, load_small_font, measure_small_text_width
11
+
12
+ from ..menu import (
13
+ MENU_LABEL_ROW_HEIGHT,
14
+ MENU_LABEL_ROW_OPTIONS,
15
+ MENU_LABEL_WIDTH,
16
+ MENU_PANEL_OFFSET_X,
17
+ MENU_PANEL_OFFSET_Y,
18
+ MENU_PANEL_WIDTH,
19
+ MenuView,
20
+ _draw_menu_cursor,
21
+ )
22
+ from ..transitions import _draw_screen_fade
23
+ from .base import PANEL_TIMELINE_END_MS, PANEL_TIMELINE_START_MS, PanelMenuView
24
+
25
+ if TYPE_CHECKING:
26
+ from ...game import GameState
27
+
28
+
29
+ @dataclass(slots=True)
30
+ class SliderState:
31
+ value: int
32
+ min_value: int
33
+ max_value: int
34
+
35
+
36
+ class OptionsMenuView(PanelMenuView):
37
+ _LABELS = (
38
+ "Sound volume:",
39
+ "Music volume:",
40
+ "Graphics detail:",
41
+ "Mouse sensitivity:",
42
+ )
43
+
44
+ def __init__(self, state: GameState) -> None:
45
+ super().__init__(state, title="Options")
46
+ self._small_font: SmallFontData | None = None
47
+ self._rect_on: rl.Texture2D | None = None
48
+ self._rect_off: rl.Texture2D | None = None
49
+ self._check_on: rl.Texture2D | None = None
50
+ self._check_off: rl.Texture2D | None = None
51
+ self._button_tex: rl.Texture2D | None = None
52
+ self._slider_sfx = SliderState(10, 0, 10)
53
+ self._slider_music = SliderState(10, 0, 10)
54
+ self._slider_detail = SliderState(5, 1, 5)
55
+ self._slider_mouse = SliderState(10, 1, 10)
56
+ self._ui_info_texts = True
57
+ self._active_slider: str | None = None
58
+ self._dirty = False
59
+
60
+ def open(self) -> None:
61
+ super().open()
62
+ cache = self._ensure_cache()
63
+ self._rect_on = cache.get_or_load("ui_rectOn", "ui/ui_rectOn.jaz").texture
64
+ self._rect_off = cache.get_or_load("ui_rectOff", "ui/ui_rectOff.jaz").texture
65
+ self._check_on = cache.get_or_load("ui_checkOn", "ui/ui_checkOn.jaz").texture
66
+ 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
68
+ self._active_slider = None
69
+ self._dirty = False
70
+ self._sync_from_config()
71
+
72
+ def update(self, dt: float) -> None:
73
+ super().update(dt)
74
+ if self._closing:
75
+ return
76
+ entry = self._entry
77
+ if entry is None or not self._entry_enabled(entry):
78
+ return
79
+
80
+ config = self._state.config
81
+ layout = self._content_layout()
82
+ label_x = layout["label_x"]
83
+ base_y = layout["base_y"]
84
+ scale = layout["scale"]
85
+ slider_x = layout["slider_x"]
86
+
87
+ rect_on = self._rect_on
88
+ rect_off = self._rect_off
89
+ if rect_on is None or rect_off is None:
90
+ return
91
+
92
+ if self._update_slider("sfx", self._slider_sfx, slider_x, base_y + 47.0 * scale, rect_on, rect_off, scale):
93
+ config.data["sfx_volume"] = float(self._slider_sfx.value) * 0.1
94
+ set_sfx_volume(self._state.audio, float(config.data["sfx_volume"]))
95
+ self._dirty = True
96
+
97
+ if self._update_slider("music", self._slider_music, slider_x, base_y + 67.0 * scale, rect_on, rect_off, scale):
98
+ config.data["music_volume"] = float(self._slider_music.value) * 0.1
99
+ set_music_volume(self._state.audio, float(config.data["music_volume"]))
100
+ self._dirty = True
101
+
102
+ if self._update_slider("detail", self._slider_detail, slider_x, base_y + 87.0 * scale, rect_on, rect_off, scale):
103
+ preset = apply_detail_preset(config, self._slider_detail.value)
104
+ self._slider_detail.value = preset
105
+ self._dirty = True
106
+
107
+ if self._update_slider("mouse", self._slider_mouse, slider_x, base_y + 107.0 * scale, rect_on, rect_off, scale):
108
+ sensitivity = float(self._slider_mouse.value) * 0.1
109
+ if sensitivity < 0.1:
110
+ sensitivity = 0.1
111
+ if sensitivity > 1.0:
112
+ sensitivity = 1.0
113
+ config.data["mouse_sensitivity"] = sensitivity
114
+ self._dirty = True
115
+
116
+ if self._update_checkbox(label_x, base_y + 135.0 * scale, scale):
117
+ value = 1 if self._ui_info_texts else 0
118
+ config.data["ui_info_texts"] = value
119
+ self._dirty = True
120
+
121
+ if self._update_controls_button(label_x - 8.0 * scale, base_y + 155.0 * scale, scale):
122
+ self._begin_close_transition("open_controls")
123
+
124
+ 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)
128
+ _draw_screen_fade(self._state)
129
+ assets = self._assets
130
+ entry = self._entry
131
+ if assets is None or entry is None:
132
+ return
133
+ self._draw_panel()
134
+ self._draw_entry(entry)
135
+ self._draw_sign()
136
+ self._draw_options_contents()
137
+ _draw_menu_cursor(self._state, pulse_time=self._cursor_pulse_time)
138
+
139
+ def _begin_close_transition(self, action: str) -> None:
140
+ if self._dirty:
141
+ try:
142
+ self._state.config.save()
143
+ except Exception:
144
+ pass
145
+ self._dirty = False
146
+ super()._begin_close_transition(action)
147
+
148
+ def _ensure_small_font(self) -> SmallFontData:
149
+ if self._small_font is not None:
150
+ return self._small_font
151
+ missing_assets: list[str] = []
152
+ self._small_font = load_small_font(self._state.assets_dir, missing_assets)
153
+ return self._small_font
154
+
155
+ def _sync_from_config(self) -> None:
156
+ config = self._state.config
157
+ self._ui_info_texts = bool(int(config.data.get("ui_info_texts", 1) or 0))
158
+
159
+ sfx_volume = float(config.data.get("sfx_volume", 1.0))
160
+ music_volume = float(config.data.get("music_volume", 1.0))
161
+ detail_preset = int(config.data.get("detail_preset", 5))
162
+ mouse_sensitivity = float(config.data.get("mouse_sensitivity", 1.0))
163
+
164
+ self._slider_sfx.value = max(self._slider_sfx.min_value, min(self._slider_sfx.max_value, int(sfx_volume * 10.0)))
165
+ self._slider_music.value = max(
166
+ self._slider_music.min_value, min(self._slider_music.max_value, int(music_volume * 10.0))
167
+ )
168
+ if detail_preset < self._slider_detail.min_value:
169
+ detail_preset = self._slider_detail.min_value
170
+ if detail_preset > self._slider_detail.max_value:
171
+ detail_preset = self._slider_detail.max_value
172
+ self._slider_detail.value = detail_preset
173
+ self._slider_mouse.value = max(
174
+ self._slider_mouse.min_value,
175
+ min(self._slider_mouse.max_value, int(mouse_sensitivity * 10.0 + 0.5)),
176
+ )
177
+
178
+ def _content_layout(self) -> dict[str, float]:
179
+ panel_scale, _local_shift = self._menu_item_scale(0)
180
+ panel_w = MENU_PANEL_WIDTH * panel_scale
181
+ _angle_rad, slide_x = MenuView._ui_element_anim(
182
+ self,
183
+ index=1,
184
+ start_ms=PANEL_TIMELINE_START_MS,
185
+ end_ms=PANEL_TIMELINE_END_MS,
186
+ width=panel_w,
187
+ )
188
+ panel_x = self._panel_pos_x + slide_x
189
+ panel_y = self._panel_pos_y + self._widescreen_y_shift
190
+ origin_x = -(MENU_PANEL_OFFSET_X * panel_scale)
191
+ origin_y = -(MENU_PANEL_OFFSET_Y * panel_scale)
192
+ panel_left = panel_x - origin_x
193
+ panel_top = panel_y - origin_y
194
+ base_x = panel_left + 212.0 * panel_scale
195
+ base_y = panel_top + 32.0 * panel_scale
196
+ label_x = base_x + 8.0 * panel_scale
197
+ slider_x = label_x + 130.0 * panel_scale
198
+ return {
199
+ "panel_left": panel_left,
200
+ "panel_top": panel_top,
201
+ "base_x": base_x,
202
+ "base_y": base_y,
203
+ "label_x": label_x,
204
+ "slider_x": slider_x,
205
+ "scale": panel_scale,
206
+ }
207
+
208
+ def _update_slider(
209
+ self,
210
+ slider_id: str,
211
+ slider: SliderState,
212
+ x: float,
213
+ y: float,
214
+ rect_on: rl.Texture2D,
215
+ rect_off: rl.Texture2D,
216
+ scale: float,
217
+ ) -> bool:
218
+ rect_w = float(rect_on.width) * scale
219
+ rect_h = float(rect_on.height) * scale
220
+ if rect_w <= 0.0 or rect_h <= 0.0:
221
+ return False
222
+ bar_w = rect_w * float(slider.max_value)
223
+ bar_h = rect_h
224
+ mouse = rl.get_mouse_position()
225
+ hovered = x <= mouse.x <= x + bar_w and y <= mouse.y <= y + bar_h
226
+
227
+ changed = False
228
+ if hovered:
229
+ if rl.is_key_pressed(rl.KeyboardKey.KEY_LEFT):
230
+ slider.value = max(slider.min_value, slider.value - 1)
231
+ changed = True
232
+ if rl.is_key_pressed(rl.KeyboardKey.KEY_RIGHT):
233
+ slider.value = min(slider.max_value, slider.value + 1)
234
+ changed = True
235
+
236
+ mouse_down = rl.is_mouse_button_down(rl.MOUSE_BUTTON_LEFT)
237
+ if hovered and rl.is_mouse_button_pressed(rl.MOUSE_BUTTON_LEFT):
238
+ self._active_slider = slider_id
239
+ if self._active_slider == slider_id and mouse_down:
240
+ relative = float(mouse.x) - x
241
+ idx = int(relative // rect_w) + 1
242
+ if idx < slider.min_value:
243
+ idx = slider.min_value
244
+ if idx > slider.max_value:
245
+ idx = slider.max_value
246
+ if slider.value != idx:
247
+ slider.value = idx
248
+ changed = True
249
+ if self._active_slider == slider_id and not mouse_down:
250
+ self._active_slider = None
251
+
252
+ return changed
253
+
254
+ def _update_checkbox(self, x: float, y: float, scale: float) -> bool:
255
+ check_on = self._check_on
256
+ check_off = self._check_off
257
+ if check_on is None or check_off is None:
258
+ return False
259
+ font = self._ensure_small_font()
260
+ text_scale = 1.0 * scale
261
+ label = "UI Info texts"
262
+ label_w = measure_small_text_width(font, label, text_scale)
263
+ rect_w = float(check_on.width) * scale + 6.0 * scale + label_w
264
+ rect_h = max(float(check_on.height) * scale, font.cell_size * text_scale)
265
+ mouse = rl.get_mouse_position()
266
+ hovered = x <= mouse.x <= x + rect_w and y <= mouse.y <= y + rect_h
267
+ if hovered and rl.is_mouse_button_pressed(rl.MOUSE_BUTTON_LEFT):
268
+ self._ui_info_texts = not self._ui_info_texts
269
+ return True
270
+ return False
271
+
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
+ def _draw_options_contents(self) -> None:
285
+ assets = self._assets
286
+ if assets is None:
287
+ return
288
+ labels_tex = assets.labels
289
+ layout = self._content_layout()
290
+ base_x = layout["base_x"]
291
+ base_y = layout["base_y"]
292
+ label_x = layout["label_x"]
293
+ slider_x = layout["slider_x"]
294
+ scale = layout["scale"]
295
+
296
+ font = self._ensure_small_font()
297
+ text_scale = 1.0 * scale
298
+ text_color = rl.Color(255, 255, 255, int(255 * 0.8))
299
+
300
+ if labels_tex is not None:
301
+ src = rl.Rectangle(
302
+ 0.0,
303
+ float(MENU_LABEL_ROW_OPTIONS) * MENU_LABEL_ROW_HEIGHT,
304
+ MENU_LABEL_WIDTH,
305
+ MENU_LABEL_ROW_HEIGHT,
306
+ )
307
+ dst = rl.Rectangle(
308
+ base_x,
309
+ base_y,
310
+ MENU_LABEL_WIDTH * scale,
311
+ MENU_LABEL_ROW_HEIGHT * scale,
312
+ )
313
+ MenuView._draw_ui_quad(
314
+ texture=labels_tex,
315
+ src=src,
316
+ dst=dst,
317
+ origin=rl.Vector2(0.0, 0.0),
318
+ rotation_deg=0.0,
319
+ tint=rl.WHITE,
320
+ )
321
+ else:
322
+ rl.draw_text(self._title, int(base_x), int(base_y), int(24 * scale), rl.WHITE)
323
+
324
+ y_offsets = (47.0, 67.0, 87.0, 107.0)
325
+ for label, offset in zip(self._LABELS, y_offsets, strict=False):
326
+ draw_small_text(font, label, label_x, base_y + offset * scale, text_scale, text_color)
327
+
328
+ rect_on = self._rect_on
329
+ rect_off = self._rect_off
330
+ if rect_on is None or rect_off is None:
331
+ return
332
+ rect_w = float(rect_on.width) * scale
333
+ rect_h = float(rect_on.height) * scale
334
+
335
+ self._draw_slider(self._slider_sfx, slider_x, base_y + 47.0 * scale, rect_on, rect_off, rect_w, rect_h)
336
+ self._draw_slider(self._slider_music, slider_x, base_y + 67.0 * scale, rect_on, rect_off, rect_w, rect_h)
337
+ self._draw_slider(self._slider_detail, slider_x, base_y + 87.0 * scale, rect_on, rect_off, rect_w, rect_h)
338
+ self._draw_slider(self._slider_mouse, slider_x, base_y + 107.0 * scale, rect_on, rect_off, rect_w, rect_h)
339
+
340
+ check_on = self._check_on
341
+ check_off = self._check_off
342
+ if check_on is not None and check_off is not None:
343
+ check_tex = check_on if self._ui_info_texts else check_off
344
+ check_w = float(check_tex.width) * scale
345
+ check_h = float(check_tex.height) * scale
346
+ check_x = label_x
347
+ check_y = base_y + 135.0 * scale
348
+ rl.draw_texture_pro(
349
+ check_tex,
350
+ rl.Rectangle(0.0, 0.0, float(check_tex.width), float(check_tex.height)),
351
+ rl.Rectangle(check_x, check_y, check_w, check_h),
352
+ rl.Vector2(0.0, 0.0),
353
+ 0.0,
354
+ rl.WHITE,
355
+ )
356
+ draw_small_text(
357
+ font,
358
+ "UI Info texts",
359
+ check_x + check_w + 6.0 * scale,
360
+ check_y + 1.0 * scale,
361
+ text_scale,
362
+ text_color,
363
+ )
364
+
365
+ button = self._button_tex
366
+ if button is not None:
367
+ button_x = label_x - 8.0 * scale
368
+ 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))
387
+
388
+ def _draw_slider(
389
+ self,
390
+ slider: SliderState,
391
+ x: float,
392
+ y: float,
393
+ rect_on: rl.Texture2D,
394
+ rect_off: rl.Texture2D,
395
+ rect_w: float,
396
+ rect_h: float,
397
+ ) -> None:
398
+ for idx in range(slider.max_value):
399
+ tex = rect_on if idx < slider.value else rect_off
400
+ dst = rl.Rectangle(x + float(idx) * rect_w, y, rect_w, rect_h)
401
+ tint = rl.WHITE if idx < slider.value else rl.Color(255, 255, 255, int(255 * 0.5))
402
+ rl.draw_texture_pro(
403
+ tex,
404
+ rl.Rectangle(0.0, 0.0, float(tex.width), float(tex.height)),
405
+ dst,
406
+ rl.Vector2(0.0, 0.0),
407
+ 0.0,
408
+ tint,
409
+ )