diff --git a/Songs/13 Recommended/box.def b/Songs/13 Recommended/box.def index 5d2d740..2d6a615 100644 --- a/Songs/13 Recommended/box.def +++ b/Songs/13 Recommended/box.def @@ -1,3 +1,4 @@ #TITLE:Recommended #TITLEJA:おすすめ曲 -#COLLECTION:RECOMMENDED \ No newline at end of file +#COLLECTION:RECOMMENDED +#FORECOLOR:#8c275c diff --git a/Songs/14 Favorites/box.def b/Songs/14 Favorites/box.def index 36dae34..d702476 100644 --- a/Songs/14 Favorites/box.def +++ b/Songs/14 Favorites/box.def @@ -1,3 +1,4 @@ #TITLE:Favorites #TITLEJA:お気に入り -#COLLECTION:FAVORITE \ No newline at end of file +#COLLECTION:FAVORITE +#FORECOLOR:#97391e diff --git a/Songs/15 Recently Played/box.def b/Songs/15 Recently Played/box.def index 5b2d324..922dbb6 100644 --- a/Songs/15 Recently Played/box.def +++ b/Songs/15 Recently Played/box.def @@ -1,3 +1,4 @@ #TITLE:Recently Played #TITLEJA:最近あそんだ曲 -#COLLECTION:RECENT \ No newline at end of file +#COLLECTION:RECENT +#FORECOLOR:#237b67 diff --git a/Songs/16 Difficulty Sort/box.def b/Songs/16 Difficulty Sort/box.def index 299e514..bc6a5df 100644 --- a/Songs/16 Difficulty Sort/box.def +++ b/Songs/16 Difficulty Sort/box.def @@ -1,3 +1,5 @@ #TITLE:Diff Sort #TITLEJA:むずかしさからえらぶ -#COLLECTION:DIFFICULTY \ No newline at end of file +#COLLECTION:DIFFICULTY +#BACKCOLOR:#ff555f +#FORECOLOR:#9d0d1f diff --git a/libs/file_navigator.py b/libs/file_navigator.py index f775cc8..df0cfd0 100644 --- a/libs/file_navigator.py +++ b/libs/file_navigator.py @@ -1,11 +1,12 @@ from dataclasses import dataclass +from enum import IntEnum import json import logging from pathlib import Path import random from typing import Optional, Union -from raylib import SHADER_UNIFORM_FLOAT, SHADER_UNIFORM_VEC3 +from raylib import SHADER_UNIFORM_VEC3 from libs.audio import audio from libs.animation import Animation, MoveAnimation from libs.global_data import Crown, Difficulty @@ -51,7 +52,6 @@ def calculate_hue_shift(source_rgb, target_rgb): shift = (target_hue - source_hue) / 360.0 - # Normalize to 0.0-1.0 range while shift < 0: shift += 1.0 while shift >= 1.0: @@ -59,68 +59,78 @@ def calculate_hue_shift(source_rgb, target_rgb): return shift +def darken_color(rgb: tuple[int, int, int]): + r, g, b = rgb + darkening_factor = 0.63 + + darkened_r = int(r * darkening_factor) + darkened_g = int(g * darkening_factor) + darkened_b = int(b * darkening_factor) + + return (darkened_r, darkened_g, darkened_b) + +class TextureIndex(IntEnum): + BLANK = 0 + VOCALOID = 1 + DEFAULT = 2 + RECOMMENDED = 3 + FAVORITE = 4 + RECENT = 5 + +class GenreIndex(IntEnum): + TUTORIAL = 0 + JPOP = 1 + ANIME = 2 + VOCALOID = 3 + CHILDREN = 4 + VARIETY = 5 + CLASSICAL = 6 + GAME = 7 + NAMCO = 8 + DEFAULT = 9 + RECOMMENDED = 10 + FAVORITE = 11 + RECENT = 12 + DAN = 13 + DIFFICULTY = 14 + class BaseBox(): - OUTLINE_MAP = { - 1: ray.Color(0, 77, 104, 255), - 2: ray.Color(156, 64, 2, 255), - 3: ray.Color(84, 101, 126, 255), - 4: ray.Color(153, 4, 46, 255), - 5: ray.Color(60, 104, 0, 255), - 6: ray.Color(134, 88, 0, 255), - 7: ray.Color(79, 40, 134, 255), - 8: ray.Color(148, 24, 0, 255), - 9: ray.Color(101, 0, 82, 255), - 10: ray.Color(140, 39, 92, 255), - 11: ray.Color(151, 57, 30, 255), - 12: ray.Color(35, 123, 103, 255), - 13: ray.Color(25, 68, 137, 255), - 14: ray.Color(157, 13, 31, 255) - } - BACK_INDEX = 17 - DEFAULT_INDEX = 9 - DIFFICULTY_SORT_INDEX = 14 """Base class for all box types in the song select screen.""" - def __init__(self, name: str, texture_index: int): + def __init__(self, name: str, back_color: Optional[tuple[int, int, int]], fore_color: Optional[tuple[int, int, int]], texture_index: TextureIndex): self.text_name = name self.texture_index = texture_index + self.genre_index = GenreIndex.DEFAULT + self.back_color = back_color + if fore_color is not None: + self.fore_color = ray.Color(fore_color[0], fore_color[1], fore_color[2], 255) + elif self.back_color is not None: + dark_ver = darken_color(self.back_color) + self.fore_color = ray.Color(dark_ver[0], dark_ver[1], dark_ver[2], 255) + else: + self.fore_color = ray.Color(101, 0, 82, 255) self.position = float('inf') - self.start_position: float = -1 - self.target_position: float = -1 + self.start_position = -1.0 + self.target_position = -1.0 self.open_anim = Animation.create_move(233, total_distance=150*tex.screen_scale, delay=50) self.open_fade = Animation.create_fade(200, initial_opacity=0, final_opacity=1.0) self.move = None + self.shader = None self.is_open = False self.text_loaded = False self.wait = 0 - def __lt__(self, other): - return self.position < other.position - - def __le__(self, other): - return self.position <= other.position - - def __gt__(self, other): - return self.position > other.position - - def __ge__(self, other): - return self.position >= other.position - - def __eq__(self, other): - return self.position == other.position - def load_text(self): self.name = OutlinedText(self.text_name, tex.skin_config["song_box_name"].font_size, ray.WHITE, outline_thickness=5, vertical=True) - ''' - self.shader = ray.load_shader('', 'shader/colortransform.fs') - source_rgb = (142, 212, 30) - target_rgb = (209, 162, 19) - source_color = ray.ffi.new('float[3]', [source_rgb[0]/255.0, source_rgb[1]/255.0, source_rgb[2]/255.0]) - target_color = ray.ffi.new('float[3]', [target_rgb[0]/255.0, target_rgb[1]/255.0, target_rgb[2]/255.0]) - source_loc = ray.get_shader_location(self.shader, 'sourceColor') - target_loc = ray.get_shader_location(self.shader, 'targetColor') - ray.set_shader_value(self.shader, source_loc, source_color, SHADER_UNIFORM_VEC3) - ray.set_shader_value(self.shader, target_loc, target_color, SHADER_UNIFORM_VEC3) - ''' + if self.back_color is not None: + self.shader = ray.load_shader('', 'shader/colortransform.fs') + source_rgb = (142, 212, 30) + target_rgb = self.back_color + source_color = ray.ffi.new('float[3]', [source_rgb[0]/255.0, source_rgb[1]/255.0, source_rgb[2]/255.0]) + target_color = ray.ffi.new('float[3]', [target_rgb[0]/255.0, target_rgb[1]/255.0, target_rgb[2]/255.0]) + source_loc = ray.get_shader_location(self.shader, 'sourceColor') + target_loc = ray.get_shader_location(self.shader, 'targetColor') + ray.set_shader_value(self.shader, source_loc, source_color, SHADER_UNIFORM_VEC3) + ray.set_shader_value(self.shader, target_loc, target_color, SHADER_UNIFORM_VEC3) def move_box(self, current_time: float): if self.position != self.target_position and self.move is None: @@ -148,15 +158,16 @@ class BaseBox(): self.open_fade.update(current_time) def _draw_closed(self, x: float, y: float, outer_fade_override: float): - #ray.begin_shader_mode(self.shader) + if self.shader is not None and self.texture_index == TextureIndex.BLANK: + ray.begin_shader_mode(self.shader) tex.draw_texture('box', 'folder_texture_left', frame=self.texture_index, x=x, fade=outer_fade_override) - offset = 1 * tex.screen_scale if self.texture_index == 3 or self.texture_index >= 9 and self.texture_index not in {10,11,12} else 0 - tex.draw_texture('box', 'folder_texture', frame=self.texture_index, x=x, x2=tex.skin_config["song_box_bg"].width, y=offset, fade=outer_fade_override) + tex.draw_texture('box', 'folder_texture', frame=self.texture_index, x=x, x2=tex.skin_config["song_box_bg"].width, fade=outer_fade_override) tex.draw_texture('box', 'folder_texture_right', frame=self.texture_index, x=x, fade=outer_fade_override) - #ray.end_shader_mode() - if self.texture_index == BaseBox.DEFAULT_INDEX: + if self.shader is not None and self.texture_index == TextureIndex.BLANK: + ray.end_shader_mode() + if self.texture_index == TextureIndex.DEFAULT: tex.draw_texture('box', 'genre_overlay', x=x, y=y, fade=outer_fade_override) - elif self.texture_index == BaseBox.DIFFICULTY_SORT_INDEX: + if self.genre_index == GenreIndex.DIFFICULTY: tex.draw_texture('box', 'diff_overlay', x=x, y=y, fade=outer_fade_override) def _draw_open(self, x: float, y: float, fade_override: Optional[float], is_ura: bool): @@ -169,8 +180,9 @@ class BaseBox(): self._draw_closed(x, y, outer_fade_override) class BackBox(BaseBox): - def __init__(self, name: str, texture_index: int): - super().__init__(name, texture_index) + COLOR = (170, 115, 35) + def __init__(self, name: str): + super().__init__(name, BackBox.COLOR, BackBox.COLOR, TextureIndex.BLANK) self.yellow_box = None def load_text(self): @@ -200,12 +212,8 @@ class BackBox(BaseBox): self.yellow_box.draw(self, fade_override, is_ura, self.name) class SongBox(BaseBox): - def __init__(self, name: str, texture_index: int, tja: TJAParser, name_texture_index: Optional[int] = None): - super().__init__(name, texture_index) - if name_texture_index is None: - self.name_texture_index = texture_index - else: - self.name_texture_index = name_texture_index + def __init__(self, name: str, back_color: Optional[tuple[int, int, int]], fore_color: Optional[tuple[int, int, int]], texture_index: TextureIndex, tja: TJAParser): + super().__init__(name, back_color, fore_color, texture_index) self.scores = dict() self.hash = dict() self.score_history = None @@ -270,7 +278,7 @@ class SongBox(BaseBox): def _draw_closed(self, x: float, y: float, outer_fade_override: float): super()._draw_closed(x, y, outer_fade_override) - self.name.draw(outline_color=SongBox.OUTLINE_MAP.get(self.name_texture_index, ray.Color(101, 0, 82, 255)), x=x + tex.skin_config["song_box_name"].x - int(self.name.texture.width / 2), y=y+tex.skin_config["song_box_name"].y, y2=min(self.name.texture.height, tex.skin_config["song_box_name"].height)-self.name.texture.height, fade=outer_fade_override) + self.name.draw(outline_color=self.fore_color, x=x + tex.skin_config["song_box_name"].x - int(self.name.texture.width / 2), y=y+tex.skin_config["song_box_name"].y, y2=min(self.name.texture.height, tex.skin_config["song_box_name"].height)-self.name.texture.height, fade=outer_fade_override) if self.tja.ex_data.new: tex.draw_texture('yellow_box', 'ex_data_new_song_balloon', x=x, y=y, fade=outer_fade_override) @@ -295,13 +303,13 @@ class SongBox(BaseBox): self.score_history.draw() class FolderBox(BaseBox): - def __init__(self, name: str, texture_index: int, tja_count: int = 0, - box_texture: Optional[str] = None): - super().__init__(name, texture_index) + def __init__(self, name: str, back_color: Optional[tuple[int, int, int]], fore_color: Optional[tuple[int, int, int]], texture_index: TextureIndex, genre_index: GenreIndex, tja_count: int = 0, box_texture: Optional[str] = None): + super().__init__(name, back_color, fore_color, texture_index) self.box_texture_path = Path(box_texture) if box_texture else None - self.is_back = self.texture_index == SongBox.BACK_INDEX + self.is_back = self.back_color == BackBox.COLOR self.tja_count = tja_count self.crown = dict() + self.genre_index = genre_index def load_text(self): super().load_text() @@ -323,17 +331,20 @@ class FolderBox(BaseBox): self.open_anim.start() self.open_fade.start() self.wait = current_time - if self.texture_index != SongBox.BACK_INDEX and not audio.is_sound_playing('voice_enter'): - audio.play_sound(f'genre_voice_{self.texture_index}', 'voice') - elif not self.is_open and is_open_prev and self.texture_index != 17 and audio.is_sound_playing(f'genre_voice_{self.texture_index}'): - audio.stop_sound(f'genre_voice_{self.texture_index}') + if self.back_color != BackBox.COLOR and not audio.is_sound_playing('voice_enter'): + audio.play_sound(f'genre_voice_{self.genre_index}', 'voice') + elif not self.is_open and is_open_prev and self.back_color != BackBox.COLOR and audio.is_sound_playing(f'genre_voice_{self.genre_index}'): + audio.stop_sound(f'genre_voice_{self.genre_index}') def _draw_closed(self, x: float, y: float, outer_fade_override: float): super()._draw_closed(x, y, outer_fade_override) - offset = 1 * tex.screen_scale if self.texture_index == 3 or self.texture_index >= 9 and self.texture_index not in {10,11,12} else 0 - tex.draw_texture('box', 'folder_clip', frame=self.texture_index, x=x - ((1 * tex.screen_scale) - offset), y=y, fade=outer_fade_override) + if self.shader is not None and self.texture_index == TextureIndex.BLANK: + ray.begin_shader_mode(self.shader) + tex.draw_texture('box', 'folder_clip', frame=self.texture_index, x=x - ((1 * tex.screen_scale)), y=y, fade=outer_fade_override) + if self.shader is not None and self.texture_index == TextureIndex.BLANK: + ray.end_shader_mode() - self.name.draw(outline_color=SongBox.OUTLINE_MAP.get(self.texture_index, ray.Color(101, 0, 82, 255)), x=x + tex.skin_config["song_box_name"].x - int(self.name.texture.width / 2), y=y+tex.skin_config["song_box_name"].y, y2=min(self.name.texture.height, tex.skin_config["song_box_name"].height)-self.name.texture.height, fade=outer_fade_override) + self.name.draw(outline_color=self.fore_color, x=x + tex.skin_config["song_box_name"].x - int(self.name.texture.width / 2), y=y+tex.skin_config["song_box_name"].y, y2=min(self.name.texture.height, tex.skin_config["song_box_name"].height)-self.name.texture.height, fade=outer_fade_override) if self.crown: #Folder lamp highest_crown = max(self.crown) @@ -349,34 +360,42 @@ class FolderBox(BaseBox): if fade_override is not None: color = ray.fade(ray.WHITE, fade_override) if not self.is_back and self.open_anim.attribute >= (100 * tex.screen_scale): + if self.shader is not None and self.texture_index == TextureIndex.BLANK: + ray.begin_shader_mode(self.shader) tex.draw_texture('box', 'folder_top_edge', x=x, y=y - self.open_anim.attribute, color=color, mirror='horizontal', frame=self.texture_index) tex.draw_texture('box', 'folder_top', x=x, y=y - self.open_anim.attribute, color=color, frame=self.texture_index) tex.draw_texture('box', 'folder_top_edge', x=x+tex.skin_config["song_folder_top"].x, y=y - self.open_anim.attribute, color=color, frame=self.texture_index) dest_width = min(tex.skin_config["song_hori_name"].width, self.hori_name.texture.width) self.hori_name.draw(outline_color=ray.BLACK, x=(x + tex.skin_config["song_hori_name"].x) - (dest_width//2), y=y + tex.skin_config["song_hori_name"].y - self.open_anim.attribute, x2=dest_width-self.hori_name.texture.width, color=color) + if self.shader is not None and self.texture_index == TextureIndex.BLANK: + ray.end_shader_mode() + if self.shader is not None and self.texture_index == TextureIndex.BLANK: + ray.begin_shader_mode(self.shader) tex.draw_texture('box', 'folder_texture_left', frame=self.texture_index, x=x - self.open_anim.attribute) offset = 1 * tex.screen_scale if self.texture_index == 3 or self.texture_index >= 9 and self.texture_index not in {10,11,12} else 0 tex.draw_texture('box', 'folder_texture', frame=self.texture_index, x=x - self.open_anim.attribute, y=offset, x2=(self.open_anim.attribute*2)+tex.skin_config["song_box_bg"].width) tex.draw_texture('box', 'folder_texture_right', frame=self.texture_index, x=x + self.open_anim.attribute) + if self.shader is not None and self.texture_index == TextureIndex.BLANK: + ray.end_shader_mode() - if self.texture_index == BaseBox.DEFAULT_INDEX: + if self.texture_index == TextureIndex.DEFAULT: tex.draw_texture('box', 'genre_overlay_large', x=x, y=y, color=color) - elif self.texture_index == BaseBox.DIFFICULTY_SORT_INDEX: + if self.genre_index == GenreIndex.DIFFICULTY: tex.draw_texture('box', 'diff_overlay_large', x=x, y=y, color=color) color = ray.WHITE if fade_override is not None: color = ray.fade(ray.WHITE, fade_override) - if self.texture_index != BaseBox.DIFFICULTY_SORT_INDEX: + if self.genre_index != GenreIndex.DIFFICULTY: tex.draw_texture('yellow_box', 'song_count_back', color=color, fade=0.5) tex.draw_texture('yellow_box', 'song_count_num', color=color) tex.draw_texture('yellow_box', 'song_count_songs', color=color) dest_width = min(tex.skin_config["song_tja_count"].width, self.tja_count_text.texture.width) self.tja_count_text.draw(outline_color=ray.BLACK, x=tex.skin_config["song_tja_count"].x - (dest_width//2), y=tex.skin_config["song_tja_count"].y, x2=dest_width-self.tja_count_text.texture.width, color=color) - if self.texture_index != SongBox.DEFAULT_INDEX: - tex.draw_texture('box', 'folder_graphic', color=color, frame=self.texture_index) - tex.draw_texture('box', 'folder_text', color=color, frame=self.texture_index) + if self.texture_index != TextureIndex.DEFAULT: + tex.draw_texture('box', 'folder_graphic', color=color, frame=self.genre_index) + tex.draw_texture('box', 'folder_text', color=color, frame=self.genre_index) elif self.box_texture is not None: scaled_width = self.box_texture.width * tex.screen_scale scaled_height = self.box_texture.height * tex.screen_scale @@ -600,8 +619,8 @@ class YellowBox: self._draw_text(song_box, name) class DanBox(BaseBox): - def __init__(self, name, color: int, songs: list[tuple[TJAParser, int, int, int]], exams: list['Exam']): - super().__init__(name, color) + def __init__(self, name, color: TextureIndex, songs: list[tuple[TJAParser, int, int, int]], exams: list['Exam']): + super().__init__(name, None, None, color) self.songs = songs self.exams = exams self.song_text: list[tuple[OutlinedText, OutlinedText]] = [] @@ -718,29 +737,48 @@ class DanBox(BaseBox): class GenreBG: """The background for a genre box.""" def __init__(self, start_box: BaseBox, end_box: BaseBox, title: OutlinedText, diff_sort: Optional[int]): + self.title = title self.start_box = start_box self.end_box = end_box self.start_position = start_box.position self.end_position_final = end_box.position - self.title = title self.fade_in = Animation.create_fade(133, initial_opacity=0.0, final_opacity=1.0, ease_in='quadratic', delay=50) - self.fade_in.start() self.move = Animation.create_move(316, delay=self.fade_in.duration/2, total_distance=abs(self.end_position_final - self.start_position), ease_in='quadratic') - self.move.start() self.box_fade_in = Animation.create_fade(66.67*2, delay=self.move.duration, initial_opacity=0.0, final_opacity=1.0) + self.fade_in.start() + self.move.start() self.box_fade_in.start() self.end_position = self.start_position + self.move.attribute self.diff_num = diff_sort + self.color = self.end_box.back_color + self.shader = None + self.shader_loaded = False + + def load_shader(self): + if self.color is not None: + self.shader = ray.load_shader('', 'shader/colortransform.fs') + source_rgb = (142, 212, 30) + target_rgb = self.color + source_color = ray.ffi.new('float[3]', [source_rgb[0]/255.0, source_rgb[1]/255.0, source_rgb[2]/255.0]) + target_color = ray.ffi.new('float[3]', [target_rgb[0]/255.0, target_rgb[1]/255.0, target_rgb[2]/255.0]) + source_loc = ray.get_shader_location(self.shader, 'sourceColor') + target_loc = ray.get_shader_location(self.shader, 'targetColor') + ray.set_shader_value(self.shader, source_loc, source_color, SHADER_UNIFORM_VEC3) + ray.set_shader_value(self.shader, target_loc, target_color, SHADER_UNIFORM_VEC3) + self.shader_loaded = True + def update(self, current_ms): self.start_position = self.start_box.position - #self.end_position_final = self.end_box.position self.end_position = self.start_position + self.move.attribute if self.move.is_finished: self.end_position = self.end_box.position self.box_fade_in.update(current_ms) self.fade_in.update(current_ms) self.move.update(current_ms) + def draw(self, y): + if self.shader is not None and self.end_box.texture_index == TextureIndex.BLANK: + ray.begin_shader_mode(self.shader) offset = (tex.skin_config["genre_bg_offset"].x * -1) if self.start_box.is_open else 0 tex.draw_texture('box', 'folder_background_edge', frame=self.end_box.texture_index, x=self.start_position+offset, y=y, mirror="horizontal", fade=self.fade_in.attribute) @@ -767,13 +805,19 @@ class GenreBG: offset = tex.skin_config["genre_bg_offset"].x if self.end_box.is_open else 0 tex.draw_texture('box', 'folder_background_edge', x=self.end_position+tex.skin_config["genre_bg_folder_edge"].x+offset, y=y, fade=self.fade_in.attribute, frame=self.end_box.texture_index) + if self.shader is not None and self.end_box.texture_index == TextureIndex.BLANK: + ray.end_shader_mode() if ((self.start_position <= BOX_CENTER and self.end_position >= BOX_CENTER) or ((self.start_position <= BOX_CENTER or self.end_position >= BOX_CENTER) and (self.start_position > self.end_position))): offset = tex.skin_config["genre_bg_offset_3"].x if self.diff_num is not None else 0 dest_width = min(tex.skin_config["genre_bg_title"].width, self.title.texture.width) + if self.shader is not None and self.end_box.texture_index == TextureIndex.BLANK: + ray.begin_shader_mode(self.shader) tex.draw_texture('box', 'folder_background_folder', x=-((offset+dest_width)//2), y=y+tex.skin_config["genre_bg_folder_background_folder"].y, x2=dest_width+offset++tex.skin_config["genre_bg_folder_background_folder"].width, fade=self.fade_in.attribute, frame=self.end_box.texture_index) tex.draw_texture('box', 'folder_background_folder_edge', x=-((offset+dest_width)//2), y=y+tex.skin_config["genre_bg_folder_background_folder"].y, fade=self.fade_in.attribute, frame=self.end_box.texture_index, mirror="horizontal") tex.draw_texture('box', 'folder_background_folder_edge', x=((offset+dest_width)//2)+tex.skin_config["genre_bg_folder_background_folder"].x, y=y+tex.skin_config["genre_bg_folder_background_folder"].y, fade=self.fade_in.attribute, frame=self.end_box.texture_index) + if self.shader is not None and self.end_box.texture_index == TextureIndex.BLANK: + ray.end_shader_mode() if self.diff_num is not None: tex.draw_texture('diff_sort', 'star_num', frame=self.diff_num, x=(tex.skin_config["genre_bg_offset"].x * -1) + (dest_width//2), y=tex.skin_config["diff_sort_star_num"].y) self.title.draw(outline_color=ray.BLACK, x=(tex.screen_width//2) - (dest_width//2)-(offset//2), y=y+tex.skin_config["genre_bg_title"].y, x2=dest_width - self.title.texture.width, color=ray.fade(ray.WHITE, self.fade_in.attribute)) @@ -872,59 +916,108 @@ class ScoreHistory: for i in range(len(counter)): tex.draw_texture('leaderboard', 'counter', frame=int(counter[i]), x=-(total_width // 2) + (i * tex.skin_config["score_info_counter_margin"].width), y=tex.skin_config["score_info_bg_offset"].y, color=ray.WHITE) +def parse_hex_color(color) -> tuple[int, int, int]: + """Parse hex color to RGB tuple""" + color = color.lstrip('#') + + if len(color) == 3: + color = ''.join([c*2 for c in color]) + + res = tuple(int(color[i:i+2], 16) for i in (0, 2, 4)) + return (res[0], res[1], res[2]) + +def get_genre_index(genre_string: str) -> GenreIndex: + genre_upper = genre_string.upper() + for genre_index, genre_set in FileSystemItem.GENRE_MAP.items(): + if genre_upper in genre_set: + return genre_index + return GenreIndex.DEFAULT + +DEFAULT_COLORS = { + GenreIndex.JPOP: [(32, 160, 186), (0, 77, 104)], + GenreIndex.ANIME: [(255, 152, 0), (156, 64, 2)], + GenreIndex.VOCALOID: [None, (84, 101, 126)], + GenreIndex.CHILDREN: [(255, 82, 134), (153, 4, 46)], + GenreIndex.VARIETY: [(142, 212, 30), (60, 104, 0)], + GenreIndex.CLASSICAL: [(209, 162, 19), (134, 88, 0)], + GenreIndex.GAME: [(156, 117, 189), (79, 40, 134)], + GenreIndex.NAMCO: [(255, 90, 19), (148, 24, 0)], + GenreIndex.DEFAULT: [None, (101, 0, 82)], + GenreIndex.RECOMMENDED: [None, (140, 39, 92)], + GenreIndex.FAVORITE: [None, (151, 57, 30)], + GenreIndex.RECENT: [None, (35, 123, 103)], + GenreIndex.DAN: [(35, 102, 170), (25, 68, 137)], + GenreIndex.DIFFICULTY: [(255, 85, 95), (157, 13, 31)] +} + def parse_box_def(path: Path): """Parse box.def file for directory metadata""" - texture_index = SongBox.DEFAULT_INDEX name = path.name genre = '' + texture_index = TextureIndex.DEFAULT + genre_index = GenreIndex.DEFAULT collection = None + back_color = None + fore_color = None encoding = test_encodings(path / "box.def") - try: - with open(path / "box.def", 'r', encoding=encoding) as box_def: - for line in box_def: - line = line.strip() - if line.startswith("#GENRE:"): - genre = line.split(":", 1)[1].strip() - texture_index = FileSystemItem.GENRE_MAP.get(genre, SongBox.DEFAULT_INDEX) - if texture_index == SongBox.DEFAULT_INDEX: - texture_index = FileSystemItem.GENRE_MAP_2.get(genre, SongBox.DEFAULT_INDEX) - elif line.startswith("#TITLE:"): + with open(path / "box.def", 'r', encoding=encoding) as box_def: + for line in box_def: + line = line.strip() + if line.startswith("#GENRE:"): + genre = line.split(":", 1)[1].strip() + texture_index = FileSystemItem.TEXTURE_MAP.get(genre, texture_index) + genre_index = get_genre_index(genre) + elif line.startswith("#TITLE:"): + name = line.split(":", 1)[1].strip() + elif line.startswith("#TITLEJA:"): + if global_data.config['general']['language'] == 'ja': name = line.split(":", 1)[1].strip() - elif line.startswith("#TITLEJA:"): - if global_data.config['general']['language'] == 'ja': - name = line.split(":", 1)[1].strip() - elif line.startswith("#COLLECTION"): - collection = line.split(":", 1)[1].strip() - if name == '': - if genre: - name = genre - else: - name = path.name - except Exception as e: - logger.error(f"Error parsing box.def in {path}: {e}") + elif line.startswith("#COLLECTION:"): + collection = line.split(":", 1)[1].strip() + texture_index = FileSystemItem.TEXTURE_MAP.get(collection, texture_index) + genre_index = get_genre_index(collection) + elif line.startswith("#BACKCOLOR:"): + back_color = parse_hex_color(line.split(":", 1)[1].strip()) + texture_index = TextureIndex.BLANK + elif line.startswith("#FORECOLOR:"): + fore_color = parse_hex_color(line.split(":", 1)[1].strip()) + if name == '': + if genre: + name = genre + else: + name = path.name - return name, texture_index, collection + if back_color is None and fore_color is None and genre_index in DEFAULT_COLORS: + back_color, fore_color = DEFAULT_COLORS[genre_index] + if genre_index != GenreIndex.DEFAULT: + texture_index = TextureIndex.BLANK + + return name, texture_index, genre_index, collection, back_color, fore_color class FileSystemItem: - GENRE_MAP = { - 'J-POP': 1, - 'アニメ': 2, - 'VOCALOID': 3, - 'どうよう': 4, - 'バラエティー': 5, - 'クラシック': 6, - 'ゲームミュージック': 7, - 'ナムコオリジナル': 8, - 'RECOMMENDED': 10, - 'FAVORITE': 11, - 'RECENT': 12, - '段位道場': 13, - 'DIFFICULTY': 14 + TEXTURE_MAP = { + 'VOCALOID': TextureIndex.VOCALOID, + 'ボーカロイド': TextureIndex.VOCALOID, + 'RECOMMENDED': TextureIndex.RECOMMENDED, + 'FAVORITE': TextureIndex.FAVORITE, + 'RECENT': TextureIndex.RECENT, } - GENRE_MAP_2 = { - 'ボーカロイド': 3, - 'バラエティ': 5 + GENRE_MAP = { + GenreIndex.TUTORIAL: {"TUTORIAL"}, + GenreIndex.JPOP: {"J-POP"}, + GenreIndex.ANIME: {"ANIME", "アニメ"}, + GenreIndex.CHILDREN: {"CHILDREN", "どうよう"}, + GenreIndex.VOCALOID: {"VOCALOID", "ボーカロイド"}, + GenreIndex.VARIETY: {"VARIETY", "バラエティー", "バラエティ"}, + GenreIndex.CLASSICAL: {"CLASSICAL", "クラシック"}, + GenreIndex.GAME: {"GAME", "ゲームミュージック"}, + GenreIndex.NAMCO: {"NAMCO", "ナムコオリジナル"}, + GenreIndex.RECOMMENDED: {"RECOMMENDED"}, + GenreIndex.FAVORITE: {"FAVORITE"}, + GenreIndex.RECENT: {"RECENT"}, + GenreIndex.DAN: {"DAN", "段位道場"}, + GenreIndex.DIFFICULTY: {"DIFFICULTY"}, } """Base class for files and directories in the navigation system""" def __init__(self, path: Path, name: str): @@ -940,7 +1033,7 @@ class Directory(FileSystemItem): 'DIFFICULTY', 'RECOMMENDED' ] - def __init__(self, path: Path, name: str, texture_index: int, has_box_def=False, to_root=False, back=False, tja_count=0, box_texture=None, collection=None): + def __init__(self, path: Path, name: str, back_color: Optional[tuple[int, int, int]], fore_color: Optional[tuple[int, int, int]], texture_index: TextureIndex, genre_index: GenreIndex, has_box_def=False, to_root=False, back=False, tja_count=0, box_texture=None, collection=None): super().__init__(path, name) self.has_box_def = has_box_def self.to_root = to_root @@ -949,27 +1042,23 @@ class Directory(FileSystemItem): self.collection = None if collection in Directory.COLLECTIONS: self.collection = collection - if collection in FileSystemItem.GENRE_MAP: - texture_index = FileSystemItem.GENRE_MAP[collection] - elif self.to_root or self.back: - texture_index = SongBox.BACK_INDEX if self.back: - self.box = BackBox(name, texture_index) + self.box = BackBox(name) else: - self.box = FolderBox(name, texture_index, tja_count=tja_count, box_texture=box_texture) + self.box = FolderBox(name, back_color, fore_color, texture_index, genre_index, tja_count=tja_count, box_texture=box_texture) class SongFile(FileSystemItem): """Represents a song file (TJA) in the navigation system""" - def __init__(self, path: Path, name: str, texture_index: int, tja=None, name_texture_index: Optional[int]=None): + def __init__(self, path: Path, name: str, back_color: Optional[tuple[int, int, int]], fore_color: Optional[tuple[int, int, int]], texture_index: TextureIndex): super().__init__(path, name) self.is_recent = (datetime.now() - datetime.fromtimestamp(path.stat().st_mtime)) <= timedelta(days=7) - self.tja = tja or TJAParser(path) + self.tja = TJAParser(path) if self.is_recent: self.tja.ex_data.new = True title = self.tja.metadata.title.get(global_data.config['general']['language'].lower(), self.tja.metadata.title['en']) self.hash = global_data.song_paths[path] - self.box = SongBox(title, texture_index, self.tja, name_texture_index=name_texture_index if name_texture_index is not None else texture_index) + self.box = SongBox(title, back_color, fore_color, texture_index, self.tja) self.box.hash = global_data.song_hashes[self.hash][0]["diff_hashes"] self.box.get_scores() @@ -1008,11 +1097,11 @@ class DanCourse(FileSystemItem): path = Path(global_data.song_hashes[hash_val][i]["file_path"]) break if (path.parent.parent / "box.def").exists(): - _, genre_index, _ = parse_box_def(path.parent.parent) + texture_index = parse_box_def(path.parent.parent)[1] else: - genre_index = 9 + texture_index = TextureIndex.DEFAULT tja = TJAParser(path) - self.charts.append((tja, genre_index, difficulty, tja.metadata.course_data[difficulty].level)) + self.charts.append((tja, texture_index, difficulty, tja.metadata.course_data[difficulty].level)) self.exams = [] for exam in data["exams"]: self.exams.append(Exam(exam["type"], exam["value"][0], exam["value"][1], exam["range"])) @@ -1126,11 +1215,12 @@ class FileNavigator: if has_box_def: # Parse box.def if it exists name = dir_path.name if dir_path.name else str(dir_path) - texture_index = SongBox.DEFAULT_INDEX box_texture = None collection = None + back_color = None + fore_color = None - name, texture_index, collection = parse_box_def(dir_path) + name, texture_index, genre_index, collection, back_color, fore_color = parse_box_def(dir_path) box_png_path = dir_path / "box.png" if box_png_path.exists(): box_texture = str(box_png_path) @@ -1144,11 +1234,11 @@ class FileNavigator: # Create Directory object directory_obj = Directory( - dir_path, name, texture_index, + dir_path, name, back_color, fore_color, texture_index, genre_index, has_box_def=has_box_def, tja_count=tja_count, box_texture=box_texture, - collection=collection + collection=collection, ) if directory_obj.collection == Directory.COLLECTIONS[2]: self.favorite_folder = directory_obj @@ -1185,7 +1275,7 @@ class FileNavigator: song_obj = DanCourse(tja_path, tja_path.name) self.all_song_files[song_key] = song_obj elif song_key not in self.all_song_files and tja_path in global_data.song_paths: - song_obj = SongFile(tja_path, tja_path.name, texture_index) + song_obj = SongFile(tja_path, tja_path.name, back_color, fore_color, texture_index) song_obj.box.get_scores() for course in song_obj.tja.metadata.course_data: level = song_obj.tja.metadata.course_data[course].level @@ -1210,7 +1300,7 @@ class FileNavigator: elif is_cleared: self.diff_sort_statistics[course][level][2] += 1 if song_obj.is_recent: - self.new_items.append(SongFile(tja_path, tja_path.name, SongBox.DEFAULT_INDEX, name_texture_index=texture_index)) + self.new_items.append(SongFile(tja_path, tja_path.name, back_color, fore_color, texture_index)) self.song_count += 1 global_data.song_progress = self.song_count / global_data.total_songs self.all_song_files[song_key] = song_obj @@ -1232,7 +1322,7 @@ class FileNavigator: song_key = str(tja_path) if song_key not in self.all_song_files: try: - song_obj = SongFile(tja_path, tja_path.name, SongBox.DEFAULT_INDEX) + song_obj = SongFile(tja_path, tja_path.name, None, None, TextureIndex.DEFAULT) self.song_count += 1 global_data.song_progress = self.song_count / global_data.total_songs self.all_song_files[song_key] = song_obj @@ -1269,7 +1359,7 @@ class FileNavigator: # Add back navigation item (only if not at root) if not self.is_at_root(): - back_dir = Directory(self.current_dir.parent, "", SongBox.BACK_INDEX, back=True) + back_dir = Directory(self.current_dir.parent, "", BackBox.COLOR, BackBox.COLOR, TextureIndex.BLANK, GenreIndex.DEFAULT, back=True) if not has_children: start_box = back_dir.box self.items.insert(self.selected_index, back_dir) @@ -1330,11 +1420,13 @@ class FileNavigator: for item in content_items: if isinstance(item, SongFile) and not has_children: if i % 10 == 0 and i != 0: - back_dir = Directory(self.current_dir.parent, "", SongBox.BACK_INDEX, back=True) + back_dir = Directory(self.current_dir.parent, "", BackBox.COLOR, BackBox.COLOR, TextureIndex.BLANK, GenreIndex.DEFAULT, back=True) self.items.insert(self.selected_index+i, back_dir) i += 1 if not has_children: if selected_item is not None: + item.box.back_color = selected_item.box.back_color + item.box.genre_index = selected_item.box.genre_index item.box.texture_index = selected_item.box.texture_index self.items.insert(self.selected_index+i, item) else: diff --git a/scenes/song_select.py b/scenes/song_select.py index 2127065..80c94c1 100644 --- a/scenes/song_select.py +++ b/scenes/song_select.py @@ -5,7 +5,9 @@ from pathlib import Path import pyray as ray import logging -from libs.file_navigator import BackBox, DanCourse, navigator +from raylib import SHADER_UNIFORM_VEC3 + +from libs.file_navigator import DEFAULT_COLORS, BackBox, DanCourse, GenreIndex, navigator from libs.audio import audio from libs.chara_2d import Chara2D from libs.file_navigator import Directory, SongBox, SongFile @@ -57,14 +59,17 @@ class SongSelectScreen(Screen): self.coin_overlay = CoinOverlay() self.allnet_indicator = AllNetIcon() self.indicator = Indicator(Indicator.State.SELECT) - self.texture_index = SongBox.DEFAULT_INDEX - self.last_texture_index = SongBox.DEFAULT_INDEX + self.genre_index = GenreIndex.DEFAULT + self.last_genre_index = self.genre_index self.last_moved = get_current_ms() self.timer_browsing = Timer(100, get_current_ms(), self.navigator.select_current_item) self.timer_selected = Timer(40, get_current_ms(), self._confirm_selection_wrapper) self.screen_init = True self.ura_switch_animation = UraSwitchAnimation() self.dan_transition = DanTransition() + self.shader = ray.load_shader('', 'shader/colortransform.fs') + self.color = None + self.load_shader_values(self.color) session_data = global_data.session_data[global_data.player_num] self.player_1 = SongSelectPlayer(global_data.player_num, self.text_fade_in) @@ -83,11 +88,22 @@ class SongSelectScreen(Screen): curr_item.box.get_scores() self.navigator.add_recent() + def load_shader_values(self, target_rgb): + source_rgb = (142, 212, 30) + if target_rgb is None: + target_rgb = source_rgb + source_color = ray.ffi.new('float[3]', [source_rgb[0]/255.0, source_rgb[1]/255.0, source_rgb[2]/255.0]) + target_color = ray.ffi.new('float[3]', [target_rgb[0]/255.0, target_rgb[1]/255.0, target_rgb[2]/255.0]) + source_loc = ray.get_shader_location(self.shader, 'sourceColor') + target_loc = ray.get_shader_location(self.shader, 'targetColor') + ray.set_shader_value(self.shader, source_loc, source_color, SHADER_UNIFORM_VEC3) + ray.set_shader_value(self.shader, target_loc, target_color, SHADER_UNIFORM_VEC3) + def finalize_song(self, current_item: SongFile): global_data.session_data[global_data.player_num].selected_song = current_item.path global_data.session_data[global_data.player_num].song_hash = global_data.song_hashes[current_item.hash][0]["diff_hashes"][self.player_1.selected_difficulty] global_data.session_data[global_data.player_num].selected_difficulty = self.player_1.selected_difficulty - global_data.session_data[global_data.player_num].genre_index = current_item.box.name_texture_index + global_data.session_data[global_data.player_num].genre_index = current_item.box.genre_index def on_screen_end(self, next_screen): self.screen_init = False @@ -268,11 +284,11 @@ class SongSelectScreen(Screen): elif self.state == State.SONG_SELECTED: self.timer_selected.update(current_time) - if self.last_texture_index != self.texture_index: + if self.last_genre_index != self.genre_index: if not self.background_fade_change.is_started: self.background_fade_change.start() if self.background_fade_change.is_finished: - self.last_texture_index = self.texture_index + self.last_genre_index = self.genre_index self.background_fade_change.reset() if self.game_transition is not None: @@ -287,6 +303,8 @@ class SongSelectScreen(Screen): if self.navigator.genre_bg is not None: self.navigator.genre_bg.update(current_time) + if not self.navigator.genre_bg.shader_loaded: + self.navigator.genre_bg.load_shader() if self.diff_sort_selector is not None: self.diff_sort_selector.update(current_time) @@ -309,7 +327,9 @@ class SongSelectScreen(Screen): if song.box.is_open: current_box = song.box if not isinstance(current_box, BackBox) and current_time >= song.box.wait + (83.33*3): - self.texture_index = current_box.texture_index + self.genre_index = current_box.genre_index + self.color = current_box.back_color + self.load_shader_values(self.color) if ray.is_key_pressed(global_data.config["keys"]["back_key"]): logger.info("Back key pressed, returning to ENTRY screen") @@ -323,9 +343,16 @@ class SongSelectScreen(Screen): def draw(self): width = tex.textures['box']['background'].width + genre_index = self.genre_index + last_genre_index = self.last_genre_index + if genre_index in DEFAULT_COLORS and self.color != DEFAULT_COLORS[genre_index][0]: + ray.begin_shader_mode(self.shader) + genre_index = GenreIndex.VARIETY + last_genre_index = GenreIndex.VARIETY for i in range(0, width * 4, width): - tex.draw_texture('box', 'background', frame=self.last_texture_index, x=i-self.background_move.attribute) - tex.draw_texture('box', 'background', frame=self.texture_index, x=i-self.background_move.attribute, fade=1 - self.background_fade_change.attribute) + tex.draw_texture('box', 'background', frame=last_genre_index, x=i-self.background_move.attribute) + tex.draw_texture('box', 'background', frame=genre_index, x=i-self.background_move.attribute, fade=1 - self.background_fade_change.attribute) + ray.end_shader_mode() self.draw_background_diffs()