mirror of
https://github.com/Yonokid/PyTaiko.git
synced 2026-02-04 03:30:13 +01:00
settings menu
This commit is contained in:
@@ -242,7 +242,7 @@ def init_audio():
|
||||
|
||||
def check_args():
|
||||
if len(sys.argv) == 1:
|
||||
return Screens.LOADING
|
||||
return Screens.SETTINGS
|
||||
|
||||
parser = argparse.ArgumentParser(description='Launch game with specified song file')
|
||||
parser.add_argument('song_path', type=str, help='Path to the TJA song file')
|
||||
@@ -265,7 +265,7 @@ def check_args():
|
||||
selected_difficulty = args.difficulty
|
||||
else:
|
||||
selected_difficulty = max(tja.metadata.course_data.keys())
|
||||
current_screen = Screens.GAME_PRACTICE if args.practice else Screens.AI_GAME
|
||||
current_screen = Screens.GAME_PRACTICE if args.practice else Screens.GAME
|
||||
global_data.session_data[PlayerNum.P1].selected_song = path
|
||||
global_data.session_data[PlayerNum.P1].selected_difficulty = selected_difficulty
|
||||
global_data.modifiers[PlayerNum.P1].auto = args.auto
|
||||
|
||||
Submodule Skins/PyTaikoGreen updated: ccd048aa2d...5527b51f3a
@@ -1,12 +1,16 @@
|
||||
import json
|
||||
import logging
|
||||
|
||||
import pyray as ray
|
||||
|
||||
from libs.audio import audio
|
||||
from libs.config import save_config
|
||||
from libs.global_objects import Indicator
|
||||
from libs.screen import Screen
|
||||
from libs.texture import tex
|
||||
from libs.utils import (
|
||||
OutlinedText,
|
||||
get_current_ms,
|
||||
global_data,
|
||||
is_l_don_pressed,
|
||||
is_l_kat_pressed,
|
||||
@@ -16,262 +20,169 @@ from libs.utils import (
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class BaseOptionBox:
|
||||
def __init__(self, name: str, description: str):
|
||||
self.name = OutlinedText(name, 30, ray.WHITE)
|
||||
self.description = description
|
||||
self.is_highlighted = False
|
||||
|
||||
def draw(self):
|
||||
if self.is_highlighted:
|
||||
tex.draw_texture('background', 'title_highlight')
|
||||
else:
|
||||
tex.draw_texture('background', 'title')
|
||||
text_x = tex.textures['background']['title'].x[0] + (tex.textures['background']['title'].width//2) - (self.name.texture.width//2)
|
||||
text_y = tex.textures['background']['title'].y[0]
|
||||
self.name.draw(outline_color=ray.BLACK, x=text_x, y=text_y)
|
||||
|
||||
class Box:
|
||||
"""Box class for the entry screen"""
|
||||
def __init__(self, text: OutlinedText, box_options: dict):
|
||||
self.text = text
|
||||
self.x = 10 * tex.screen_scale
|
||||
self.y = -50 * tex.screen_scale
|
||||
self.move = tex.get_animation(0)
|
||||
self.is_selected = False
|
||||
self.outline_color = ray.Color(109, 68, 24, 255)
|
||||
self.direction = 1
|
||||
self.target_position = float('inf')
|
||||
self.start_position = self.y
|
||||
language = global_data.config["general"]["language"]
|
||||
self.options = [BaseOptionBox(box_options[option]["name"][language], box_options[option]["description"][language]) for option in box_options]
|
||||
|
||||
def __repr__(self):
|
||||
return str(self.__dict__)
|
||||
|
||||
def move_left(self):
|
||||
"""Move the box left"""
|
||||
if self.y != self.target_position and self.target_position != float('inf'):
|
||||
return False
|
||||
self.move.start()
|
||||
self.direction = 1
|
||||
self.start_position = self.y
|
||||
self.target_position = self.y + (100 * tex.screen_scale * self.direction)
|
||||
|
||||
if self.target_position >= 650:
|
||||
self.target_position = -50 + (self.target_position - 650)
|
||||
|
||||
return True
|
||||
|
||||
def move_right(self):
|
||||
"""Move the box right"""
|
||||
if self.y != self.target_position and self.target_position != float('inf'):
|
||||
return False
|
||||
self.move.start()
|
||||
self.start_position = self.y
|
||||
self.direction = -1
|
||||
self.target_position = self.y + (100 * tex.screen_scale * self.direction)
|
||||
|
||||
if self.target_position < -50:
|
||||
self.target_position = 650 + (self.target_position + 50)
|
||||
|
||||
return True
|
||||
|
||||
def update(self, current_time_ms: float, is_selected: bool):
|
||||
self.move.update(current_time_ms)
|
||||
self.is_selected = is_selected
|
||||
if self.move.is_finished:
|
||||
self.y = self.target_position
|
||||
else:
|
||||
self.y = self.start_position + (self.move.attribute * self.direction)
|
||||
|
||||
def _draw_highlighted(self):
|
||||
tex.draw_texture('box', 'box_highlight', x=self.x, y=self.y)
|
||||
|
||||
def _draw_text(self):
|
||||
text_x = self.x + (tex.textures['box']['box'].width//2) - (self.text.texture.width//2)
|
||||
text_y = self.y + (tex.textures['box']['box'].height//2) - (self.text.texture.height//2)
|
||||
if self.is_selected:
|
||||
self.text.draw(outline_color=ray.BLACK, x=text_x, y=text_y)
|
||||
else:
|
||||
self.text.draw(outline_color=self.outline_color, x=text_x, y=text_y)
|
||||
|
||||
def draw(self):
|
||||
tex.draw_texture('box', 'box', x=self.x, y=self.y)
|
||||
if self.is_selected:
|
||||
self._draw_highlighted()
|
||||
self._draw_text()
|
||||
|
||||
class BoxManager:
|
||||
"""BoxManager class for the entry screen"""
|
||||
def __init__(self, settings_template: dict):
|
||||
language = global_data.config["general"]["language"]
|
||||
self.boxes = [Box(OutlinedText(settings_template[config_name]["name"][language], tex.skin_config["entry_box_text"].font_size - int(5*tex.screen_scale), ray.WHITE, outline_thickness=5), settings_template[config_name]["options"]) for config_name in settings_template]
|
||||
self.num_boxes = len(self.boxes)
|
||||
self.selected_box_index = 3
|
||||
self.is_2p = False
|
||||
|
||||
for i, box in enumerate(self.boxes):
|
||||
box.y += 100*i
|
||||
box.start_position += 100*i
|
||||
|
||||
def move_left(self):
|
||||
"""Move the cursor to the left"""
|
||||
moved = True
|
||||
for box in self.boxes:
|
||||
if not box.move_left():
|
||||
moved = False
|
||||
|
||||
if moved:
|
||||
self.selected_box_index = (self.selected_box_index - 1) % self.num_boxes
|
||||
|
||||
def move_right(self):
|
||||
"""Move the cursor to the right"""
|
||||
moved = True
|
||||
for box in self.boxes:
|
||||
if not box.move_right():
|
||||
moved = False
|
||||
|
||||
if moved:
|
||||
self.selected_box_index = (self.selected_box_index + 1) % self.num_boxes
|
||||
|
||||
def update(self, current_time_ms: float):
|
||||
for i, box in enumerate(self.boxes):
|
||||
is_selected = i == self.selected_box_index
|
||||
box.update(current_time_ms, is_selected)
|
||||
|
||||
def draw(self):
|
||||
for box in self.boxes:
|
||||
box.draw()
|
||||
|
||||
class SettingsScreen(Screen):
|
||||
def on_screen_start(self):
|
||||
super().on_screen_start()
|
||||
self.config = global_data.config
|
||||
self.headers = list(self.config.keys())
|
||||
self.headers.append('Exit')
|
||||
self.header_index = 0
|
||||
self.setting_index = 0
|
||||
self.in_setting_edit = False
|
||||
self.editing_key = False
|
||||
self.editing_gamepad = False
|
||||
self.indicator = Indicator(Indicator.State.SELECT)
|
||||
self.template = json.loads((tex.graphics_path / "settings_template.json").read_text(encoding='utf-8'))
|
||||
self.box_manager = BoxManager(self.template)
|
||||
|
||||
def on_screen_end(self, next_screen: str):
|
||||
save_config(self.config)
|
||||
global_data.config = self.config
|
||||
audio.close_audio_device()
|
||||
audio.device_type = global_data.config["audio"]["device_type"]
|
||||
sample_rate = global_data.config["audio"]["sample_rate"]
|
||||
if sample_rate < 0:
|
||||
sample_rate = 44100
|
||||
audio.target_sample_rate = sample_rate
|
||||
audio.buffer_size = global_data.config["audio"]["buffer_size"]
|
||||
audio.volume_presets = global_data.config["volume"]
|
||||
audio.init_audio_device()
|
||||
logger.info("Settings saved and audio device re-initialized")
|
||||
return next_screen
|
||||
|
||||
def get_current_settings(self):
|
||||
"""Get the current section's settings as a list"""
|
||||
current_header = self.headers[self.header_index]
|
||||
if current_header == 'Exit' or current_header not in self.config:
|
||||
return []
|
||||
return list(self.config[current_header].items())
|
||||
|
||||
def handle_boolean_toggle(self, section, key):
|
||||
"""Toggle boolean values"""
|
||||
self.config[section][key] = not self.config[section][key]
|
||||
logger.info(f"Toggled boolean setting: {section}.{key} -> {self.config[section][key]}")
|
||||
|
||||
def handle_numeric_change(self, section, key, increment):
|
||||
"""Handle numeric value changes"""
|
||||
current_value = self.config[section][key]
|
||||
|
||||
# Define step sizes for different settings
|
||||
step_sizes = {
|
||||
'judge_offset': 1,
|
||||
'visual_offset': 1,
|
||||
'sample_rate': 1000,
|
||||
}
|
||||
|
||||
step = step_sizes.get(key, 1)
|
||||
new_value = current_value + (step * increment)
|
||||
|
||||
if key == 'sample_rate':
|
||||
valid_rates = [-1, 22050, 44100, 48000, 88200, 96000]
|
||||
current_idx = valid_rates.index(current_value) if current_value in valid_rates else 2
|
||||
new_idx = max(0, min(len(valid_rates) - 1, current_idx + increment))
|
||||
new_value = valid_rates[new_idx]
|
||||
|
||||
if key == 'buffer_size':
|
||||
valid_sizes = [-1, 32, 64, 128, 256, 512, 1024]
|
||||
current_idx = valid_sizes.index(current_value) if current_value in valid_sizes else 2
|
||||
new_idx = max(0, min(len(valid_sizes) - 1, current_idx + increment))
|
||||
new_value = valid_sizes[new_idx]
|
||||
|
||||
self.config[section][key] = new_value
|
||||
logger.info(f"Changed numeric setting: {section}.{key} -> {new_value}")
|
||||
|
||||
def handle_string_cycle(self, section, key):
|
||||
"""Cycle through predefined string values"""
|
||||
current_value = self.config[section][key]
|
||||
|
||||
options = {
|
||||
'language': ['ja', 'en'],
|
||||
}
|
||||
|
||||
if key in options:
|
||||
values = options[key]
|
||||
try:
|
||||
current_idx = values.index(current_value)
|
||||
new_idx = (current_idx + 1) % len(values)
|
||||
self.config[section][key] = values[new_idx]
|
||||
except ValueError:
|
||||
self.config[section][key] = values[0]
|
||||
logger.info(f"Cycled string setting: {section}.{key} -> {self.config[section][key]}")
|
||||
|
||||
def handle_key_binding(self, section, key):
|
||||
"""Handle key binding changes"""
|
||||
self.editing_key = True
|
||||
logger.info(f"Started key binding edit for: {section}.{key}")
|
||||
|
||||
def update_key_binding(self):
|
||||
"""Update key binding based on input"""
|
||||
key_pressed = ray.get_key_pressed()
|
||||
if key_pressed != 0:
|
||||
# Convert keycode to character
|
||||
if 65 <= key_pressed <= 90: # A-Z
|
||||
new_key = chr(key_pressed)
|
||||
current_header = self.headers[self.header_index]
|
||||
settings = self.get_current_settings()
|
||||
if settings:
|
||||
setting_key, _ = settings[self.setting_index]
|
||||
self.config[current_header][setting_key] = [new_key]
|
||||
self.editing_key = False
|
||||
logger.info(f"Key binding updated: {current_header}.{setting_key} -> {new_key}")
|
||||
elif key_pressed == global_data.config["keys"]["back_key"]:
|
||||
self.editing_key = False
|
||||
logger.info("Key binding edit cancelled")
|
||||
|
||||
def handle_gamepad_binding(self, section, key):
|
||||
self.editing_gamepad = True
|
||||
logger.info(f"Started gamepad binding edit for: {section}.{key}")
|
||||
|
||||
def update_gamepad_binding(self):
|
||||
"""Update gamepad binding based on input"""
|
||||
button_pressed = ray.get_gamepad_button_pressed()
|
||||
if button_pressed != 0:
|
||||
current_header = self.headers[self.header_index]
|
||||
settings = self.get_current_settings()
|
||||
if settings:
|
||||
setting_key, _ = settings[self.setting_index]
|
||||
self.config[current_header][setting_key] = [button_pressed]
|
||||
self.editing_gamepad = False
|
||||
logger.info(f"Gamepad binding updated: {current_header}.{setting_key} -> {button_pressed}")
|
||||
if ray.is_key_pressed(global_data.config["keys"]["back_key"]):
|
||||
self.editing_gamepad = False
|
||||
logger.info("Gamepad binding edit cancelled")
|
||||
def handle_input(self):
|
||||
if is_l_kat_pressed():
|
||||
audio.play_sound('kat', 'sound')
|
||||
self.box_manager.move_left()
|
||||
elif is_r_kat_pressed():
|
||||
audio.play_sound('kat', 'sound')
|
||||
self.box_manager.move_right()
|
||||
|
||||
def update(self):
|
||||
super().update()
|
||||
|
||||
# Handle key binding editing
|
||||
if self.editing_key:
|
||||
self.update_key_binding()
|
||||
return
|
||||
self.handle_input()
|
||||
|
||||
if self.editing_gamepad:
|
||||
self.update_gamepad_binding()
|
||||
return
|
||||
|
||||
current_header = self.headers[self.header_index]
|
||||
|
||||
# Exit handling
|
||||
if current_header == 'Exit' and (is_l_don_pressed() or is_r_don_pressed()):
|
||||
logger.info("Exiting settings screen")
|
||||
return self.on_screen_end("ENTRY")
|
||||
|
||||
# Navigation between sections
|
||||
if not self.in_setting_edit:
|
||||
if is_r_kat_pressed():
|
||||
self.header_index = (self.header_index + 1) % len(self.headers)
|
||||
self.setting_index = 0
|
||||
logger.info(f"Navigated to next section: {self.headers[self.header_index]}")
|
||||
elif is_l_kat_pressed():
|
||||
self.header_index = (self.header_index - 1) % len(self.headers)
|
||||
self.setting_index = 0
|
||||
logger.info(f"Navigated to previous section: {self.headers[self.header_index]}")
|
||||
elif (is_l_don_pressed() or is_r_don_pressed()) and current_header != 'Exit':
|
||||
self.in_setting_edit = True
|
||||
logger.info(f"Entered section edit: {current_header}")
|
||||
else:
|
||||
# Navigation within settings
|
||||
settings = self.get_current_settings()
|
||||
if not settings:
|
||||
self.in_setting_edit = False
|
||||
return
|
||||
|
||||
if is_r_kat_pressed():
|
||||
self.setting_index = (self.setting_index + 1) % len(settings)
|
||||
logger.info(f"Navigated to next setting: {settings[self.setting_index][0]}")
|
||||
elif is_l_kat_pressed():
|
||||
self.setting_index = (self.setting_index - 1) % len(settings)
|
||||
logger.info(f"Navigated to previous setting: {settings[self.setting_index][0]}")
|
||||
elif is_r_don_pressed():
|
||||
# Modify setting value
|
||||
setting_key, setting_value = settings[self.setting_index]
|
||||
|
||||
if isinstance(setting_value, bool):
|
||||
self.handle_boolean_toggle(current_header, setting_key)
|
||||
elif isinstance(setting_value, (int, float)):
|
||||
self.handle_numeric_change(current_header, setting_key, 1)
|
||||
elif isinstance(setting_value, str):
|
||||
if 'keys' in current_header:
|
||||
self.handle_key_binding(current_header, setting_key)
|
||||
elif 'gamepad' in current_header:
|
||||
self.handle_gamepad_binding(current_header, setting_key)
|
||||
else:
|
||||
self.handle_string_cycle(current_header, setting_key)
|
||||
elif isinstance(setting_value, list) and len(setting_value) > 0:
|
||||
if isinstance(setting_value[0], str) and len(setting_value[0]) == 1:
|
||||
# Key binding
|
||||
self.handle_key_binding(current_header, setting_key)
|
||||
elif isinstance(setting_value[0], int):
|
||||
self.handle_gamepad_binding(current_header, setting_key)
|
||||
elif is_l_don_pressed():
|
||||
# Modify setting value (reverse direction for numeric)
|
||||
setting_key, setting_value = settings[self.setting_index]
|
||||
|
||||
if isinstance(setting_value, bool):
|
||||
self.handle_boolean_toggle(current_header, setting_key)
|
||||
elif isinstance(setting_value, (int, float)):
|
||||
self.handle_numeric_change(current_header, setting_key, -1)
|
||||
elif isinstance(setting_value, str):
|
||||
if ('keys' not in current_header) and ('gamepad' not in current_header):
|
||||
self.handle_string_cycle(current_header, setting_key)
|
||||
|
||||
elif ray.is_key_pressed(global_data.config["keys"]["back_key"]):
|
||||
self.in_setting_edit = False
|
||||
logger.info("Exited section edit")
|
||||
current_time = get_current_ms()
|
||||
self.indicator.update(current_time)
|
||||
self.box_manager.update(current_time)
|
||||
|
||||
def draw(self):
|
||||
ray.draw_rectangle(0, 0, tex.screen_width, tex.screen_height, ray.BLACK)
|
||||
# Draw title
|
||||
ray.draw_rectangle(0, 0, tex.screen_width, tex.screen_height, ray.BLACK)
|
||||
ray.draw_text("SETTINGS", 20, 20, 30, ray.WHITE)
|
||||
|
||||
# Draw section headers
|
||||
current_header = self.headers[self.header_index]
|
||||
for i, key in enumerate(self.headers):
|
||||
color = ray.GREEN
|
||||
if key == current_header:
|
||||
color = ray.YELLOW if not self.in_setting_edit else ray.ORANGE
|
||||
ray.draw_text(f'{key}', 20, i*25 + 70, 20, color)
|
||||
|
||||
# Draw current section settings
|
||||
if current_header != 'Exit' and current_header in self.config:
|
||||
settings = self.get_current_settings()
|
||||
|
||||
# Draw settings list
|
||||
for i, (key, value) in enumerate(settings):
|
||||
color = ray.GREEN
|
||||
if self.in_setting_edit and i == self.setting_index:
|
||||
color = ray.YELLOW
|
||||
|
||||
# Format value display
|
||||
if isinstance(value, list):
|
||||
display_value = ', '.join(map(str, value))
|
||||
else:
|
||||
display_value = str(value)
|
||||
if key == 'device_type' and not isinstance(value, list):
|
||||
display_value = f'{display_value} ({audio.get_host_api_name(value)})'
|
||||
ray.draw_text(f'{key}: {display_value}', 250, i*25 + 70, 20, color)
|
||||
|
||||
# Draw instructions
|
||||
y_offset = len(settings) * 25 + 150
|
||||
if not self.in_setting_edit:
|
||||
ray.draw_text("Don/Kat: Navigate sections", 20, y_offset, 16, ray.GRAY)
|
||||
ray.draw_text("L/R Don: Enter section", 20, y_offset + 20, 16, ray.GRAY)
|
||||
else:
|
||||
ray.draw_text("Don/Kat: Navigate settings", 20, y_offset, 16, ray.GRAY)
|
||||
ray.draw_text("L/R Don: Modify value", 20, y_offset + 20, 16, ray.GRAY)
|
||||
ray.draw_text("ESC: Back to sections", 20, y_offset + 40, 16, ray.GRAY)
|
||||
|
||||
if self.editing_key:
|
||||
ray.draw_text("Press a key to bind (ESC to cancel)", 20, y_offset + 60, 16, ray.RED)
|
||||
else:
|
||||
# Draw exit instruction
|
||||
ray.draw_text("Press Don to exit settings", 250, 100, 20, ray.GREEN)
|
||||
tex.draw_texture('background', 'background')
|
||||
self.box_manager.draw()
|
||||
tex.draw_texture('background', 'footer')
|
||||
self.indicator.draw(tex.skin_config['song_select_indicator'].x, tex.skin_config['song_select_indicator'].y)
|
||||
tex.draw_texture('background', 'overlay', scale=0.70)
|
||||
|
||||
Reference in New Issue
Block a user