rewrote outlined text

This commit is contained in:
Yonokid
2025-07-23 18:28:12 -04:00
parent aed0c530a4
commit af4d6a3fd5
11 changed files with 2162 additions and 577 deletions

View File

@@ -18,6 +18,7 @@ from libs.utils import (
global_data, global_data,
load_all_textures_from_zip, load_all_textures_from_zip,
) )
from scenes.devtest import DevScreen
from scenes.entry import EntryScreen from scenes.entry import EntryScreen
from scenes.game import GameScreen from scenes.game import GameScreen
from scenes.result import ResultScreen from scenes.result import ResultScreen
@@ -33,6 +34,7 @@ class Screens:
GAME = "GAME" GAME = "GAME"
RESULT = "RESULT" RESULT = "RESULT"
SETTINGS = "SETTINGS" SETTINGS = "SETTINGS"
DEV_MENU = "DEV_MENU"
def create_song_db(): def create_song_db():
with sqlite3.connect('scores.db') as con: with sqlite3.connect('scores.db') as con:
@@ -84,24 +86,26 @@ def main():
if global_data.config["video"]["fullscreen"]: if global_data.config["video"]["fullscreen"]:
ray.maximize_window() ray.maximize_window()
current_screen = Screens.TITLE current_screen = Screens.DEV_MENU
audio.init_audio_device() audio.init_audio_device()
title_screen = TitleScreen(screen_width, screen_height) title_screen = TitleScreen(screen_width, screen_height)
entry_screen = EntryScreen(screen_width, screen_height) entry_screen = EntryScreen(screen_width, screen_height)
song_select_screen = SongSelectScreen(screen_width, screen_height) #song_select_screen = SongSelectScreen(screen_width, screen_height)
game_screen = GameScreen(screen_width, screen_height) game_screen = GameScreen(screen_width, screen_height)
result_screen = ResultScreen(screen_width, screen_height) result_screen = ResultScreen(screen_width, screen_height)
settings_screen = SettingsScreen(screen_width, screen_height) settings_screen = SettingsScreen(screen_width, screen_height)
dev_screen = DevScreen(screen_width, screen_height)
screen_mapping = { screen_mapping = {
Screens.ENTRY: entry_screen, Screens.ENTRY: entry_screen,
Screens.TITLE: title_screen, Screens.TITLE: title_screen,
Screens.SONG_SELECT: song_select_screen, #Screens.SONG_SELECT: song_select_screen,
Screens.GAME: game_screen, Screens.GAME: game_screen,
Screens.RESULT: result_screen, Screens.RESULT: result_screen,
Screens.SETTINGS: settings_screen Screens.SETTINGS: settings_screen,
Screens.DEV_MENU: dev_screen
} }
target = ray.load_render_texture(screen_width, screen_height) target = ray.load_render_texture(screen_width, screen_height)
ray.set_texture_filter(target.texture, ray.TextureFilter.TEXTURE_FILTER_TRILINEAR) ray.set_texture_filter(target.texture, ray.TextureFilter.TEXTURE_FILTER_TRILINEAR)

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -727,6 +727,10 @@ class AudioEngine:
self.running = False self.running = False
self.audio_device_ready = False self.audio_device_ready = False
self.sounds = {}
self.music_streams = {}
self.sound_queue = queue.Queue()
self.music_queue = queue.Queue()
print("Audio device closed") print("Audio device closed")
return return

View File

@@ -176,7 +176,6 @@ class TJAParser:
DIFFS = {0: "easy", 1: "normal", 2: "hard", 3: "oni", 4: "edit", 5: "tower", 6: "dan"} DIFFS = {0: "easy", 1: "normal", 2: "hard", 3: "oni", 4: "edit", 5: "tower", 6: "dan"}
def __init__(self, path: Path, start_delay: int = 0, distance: int = 866): def __init__(self, path: Path, start_delay: int = 0, distance: int = 866):
self.file_path: Path = path self.file_path: Path = path
print(self.file_path)
lines = self.file_path.read_text(encoding='utf-8-sig').splitlines() lines = self.file_path.read_text(encoding='utf-8-sig').splitlines()
self.data = [cleaned for line in lines self.data = [cleaned for line in lines

View File

@@ -7,10 +7,15 @@ import zipfile
from dataclasses import dataclass, field from dataclasses import dataclass, field
from functools import lru_cache from functools import lru_cache
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any, Optional
import pyray as ray import pyray as ray
import tomlkit import tomlkit
from raylib import (
SHADER_UNIFORM_FLOAT,
SHADER_UNIFORM_VEC2,
SHADER_UNIFORM_VEC4,
)
#TJA Format creator is unknown. I did not create the format, but I did write the parser though. #TJA Format creator is unknown. I did not create the format, but I did write the parser though.
@@ -231,600 +236,150 @@ class GlobalData:
global_data = GlobalData() global_data = GlobalData()
rotation_cache = dict()
char_size_cache = dict()
horizontal_cache = dict()
text_cache = set()
if not Path('cache/image').exists():
Path('cache').mkdir()
Path('cache/image').mkdir()
for file in Path('cache/image').iterdir():
text_cache.add(file.stem)
@dataclass
class OutlinedText: class OutlinedText:
text: str def __init__(self, text: str, font_size: int, color: ray.Color, outline_color: ray.Color, outline_thickness=5.0, vertical=False):
font_size: int self.font = self._load_font_for_text(text)
text_color: ray.Color if vertical:
outline_color: ray.Color self.texture = self._create_text_vertical(text, font_size, color, ray.BLANK, self.font)
font: ray.Font = ray.Font() else:
outline_thickness: int = 2 self.texture = self._create_text_horizontal(text, font_size, color, ray.BLANK, self.font)
vertical: bool = False outline_size = ray.ffi.new('float*', outline_thickness)
line_spacing: float = 1.0 # Line spacing for vertical text if isinstance(outline_color, tuple):
horizontal_spacing: float = 1.0 # Character spacing for horizontal text outline_color_alloc = ray.ffi.new("float[4]", [
lowercase_spacing_factor: float = 0.85 # Adjust spacing for lowercase letters and whitespace outline_color[0] / 255.0,
vertical_chars: set = field(default_factory=lambda: {'-', '', '|', '/', '\\', '', '', '~', '', '', '(', ')', outline_color[1] / 255.0,
'', '', '[', ']', '', '', '', '', '', '', '', ':', ''}) outline_color[2] / 255.0,
no_space_chars: set = field(default_factory=lambda: { outline_color[3] / 255.0
'', '','', '','', '','', '','', '', ])
'', '','', '','', '','', '','', '', else:
'', '','','','','','','','','','', outline_color_alloc = ray.ffi.new("float[4]", [
'','','','','','','' outline_color.r / 255.0,
}) outline_color.g / 255.0,
# New field for horizontal exception strings outline_color.b / 255.0,
horizontal_exceptions: set = field(default_factory=lambda: {'!!!!', '!!!', '!!', '','','!?', '', '??', '', '†††', '(°∀°)', '(°∀°)'}) outline_color.a / 255.0
# New field for adjacent punctuation characters ])
adjacent_punctuation: set = field(default_factory=lambda: {'.', ',', '', '', "'", '"', '´', '`'}) texture_size = ray.ffi.new("float[2]", [self.texture.width, self.texture.height])
def __post_init__(self): self.shader = ray.load_shader('', 'shader/outline.fs')
# Cache for rotated characters outline_size_loc = ray.get_shader_location(self.shader, "outlineSize")
self._rotation_cache = rotation_cache outline_color_loc = ray.get_shader_location(self.shader, "outlineColor")
# Cache for character measurements texture_size_loc = ray.get_shader_location(self.shader, "textureSize")
self._char_size_cache = char_size_cache self.alpha_loc = ray.get_shader_location(self.shader, "alpha")
# Cache for horizontal exception measurements ray.set_shader_value(self.shader, outline_size_loc, outline_size, SHADER_UNIFORM_FLOAT)
self._horizontal_cache = horizontal_cache ray.set_shader_value(self.shader, outline_color_loc, outline_color_alloc, SHADER_UNIFORM_VEC4)
self.hash = self._get_hash() ray.set_shader_value(self.shader, texture_size_loc, texture_size, SHADER_UNIFORM_VEC2)
self.texture = self._create_texture()
def _load_font_for_text(self, text: str) -> ray.Font: def _load_font_for_text(self, text: str) -> ray.Font:
codepoint_count = ray.ffi.new('int *', 0) codepoint_count = ray.ffi.new('int *', 0)
unique_codepoints = set(text) unique_codepoints = set(text)
codepoints = ray.load_codepoints(''.join(unique_codepoints), codepoint_count) codepoints = ray.load_codepoints(''.join(unique_codepoints), codepoint_count)
return ray.load_font_ex(str(Path('Graphics/Modified-DFPKanteiryu-XB.ttf')), self.font_size, codepoints, 0) return ray.load_font_ex(str(Path('Graphics/Modified-DFPKanteiryu-XB.ttf')), 40, codepoints, 0)
def _get_hash(self): def _create_text_vertical(self, text: str, font_size: int, color: ray.Color, bg_color: ray.Color, font: Optional[ray.Font]=None, padding: int=10):
n = hashlib.sha256() rotate_chars = {'-', '', '|', '/', '\\', '', '', '~', '', '', '(', ')',
n.update(self.text.encode('utf-8')) '', '', '[', ']', '', '', '', '', '', '', '', ':', ''}
n.update(str(self.vertical).encode('utf-8')) max_char_width = 0
n.update(str(self.horizontal_spacing).encode('utf-8')) # Include horizontal spacing in hash total_height = padding * 2 # Top and bottom padding
n.update(str(self.outline_color.a).encode('utf-8'))
n.update(str(self.outline_color.r).encode('utf-8'))
n.update(str(self.outline_color.g).encode('utf-8'))
n.update(str(self.outline_color.b).encode('utf-8'))
n.update(str(self.text_color.a).encode('utf-8'))
n.update(str(self.text_color.r).encode('utf-8'))
n.update(str(self.text_color.g).encode('utf-8'))
n.update(str(self.text_color.b).encode('utf-8'))
n.update(str(self.font_size).encode('utf-8'))
return n.hexdigest()
def _parse_text_segments(self): for char in text:
"""Parse text into segments, identifying horizontal exceptions""" if font:
if not self.vertical: char_size = ray.measure_text_ex(font, char, font_size, 0)
return [{'text': self.text, 'is_horizontal': False}]
segments = []
i = 0
current_segment = ""
while i < len(self.text):
# Check if any horizontal exception starts at current position
found_exception = None
for exception in self.horizontal_exceptions:
if self.text[i:].startswith(exception):
found_exception = exception
break
if found_exception:
# Save current segment if it exists
if current_segment:
segments.append({'text': current_segment, 'is_horizontal': False})
current_segment = ""
# Add horizontal exception as separate segment
segments.append({'text': found_exception, 'is_horizontal': True})
i += len(found_exception)
else: else:
# Add character to current segment char_width = ray.measure_text(char, font_size)
current_segment += self.text[i] char_size = ray.Vector2(char_width, font_size)
i += 1
# Add remaining segment # If character should be rotated, swap width and height for measurements
if current_segment: if char in rotate_chars:
segments.append({'text': current_segment, 'is_horizontal': False}) effective_width = char_size.y # Height becomes width when rotated 90°
return segments
def _group_characters_with_punctuation(self, text):
"""Group characters with their adjacent punctuation"""
groups = []
i = 0
while i < len(text):
current_char = text[i]
group = {'main_char': current_char, 'adjacent_punct': []}
# Look ahead for adjacent punctuation
j = i + 1
while j < len(text) and text[j] in self.adjacent_punctuation:
group['adjacent_punct'].append(text[j])
j += 1
groups.append(group)
i = j # Move to next non-punctuation character
return groups
def _get_horizontal_exception_texture(self, text: str, color):
"""Get or create a texture for horizontal exception text"""
cache_key = (text, color.r, color.g, color.b, color.a, 'horizontal')
if cache_key in self._horizontal_cache:
return self._horizontal_cache[cache_key]
# Measure the text
text_size = ray.measure_text_ex(self.font, text, self.font_size, 1.0)
padding = int(self.outline_thickness * 3)
# Create image with proper dimensions
img_width = int(text_size.x + padding * 2)
img_height = int(text_size.y + padding * 2)
temp_image = ray.gen_image_color(img_width, img_height, ray.Color(0, 0, 0, 0))
# Draw the text centered
ray.image_draw_text_ex(
temp_image,
self.font,
text,
ray.Vector2(padding, padding),
self.font_size,
1.0,
color
)
# Cache the image
self._horizontal_cache[cache_key] = temp_image
return temp_image
def _get_char_size(self, char):
"""Cache character size measurements"""
if char not in self._char_size_cache:
if char in self.vertical_chars:
# For vertical chars, width and height are swapped
self._char_size_cache[char] = ray.Vector2(self.font_size, self.font_size)
else: else:
self._char_size_cache[char] = ray.measure_text_ex(self.font, char, self.font_size, 1.0) effective_width = char_size.x
return self._char_size_cache[char]
def _calculate_vertical_spacing(self, current_char, next_char=None): max_char_width = max(max_char_width, effective_width)
"""Calculate vertical spacing between characters"""
# Check if current char is lowercase, whitespace or a special character
is_spacing_char = (current_char.islower() or
current_char.isspace())
# Additional check for capitalization transition total_height += len(text) * font_size
if next_char and ((current_char.isupper() and next_char.islower()) or width = int(max_char_width + (padding * 2)) # Add left and right padding
next_char in self.no_space_chars): height = total_height
is_spacing_char = True image = ray.gen_image_color(width, height, bg_color)
# Apply spacing factor if it's a spacing character for i, char in enumerate(text):
spacing = self.line_spacing * (self.lowercase_spacing_factor if is_spacing_char else 1.0) char_y = i * ray.measure_text_ex(self.font, char, font_size, 0).y
return self.font_size * spacing char_y += padding
def _get_rotated_char(self, char: str, color): if font:
"""Get or create a rotated character texture from cache""" char_size = ray.measure_text_ex(font, char, font_size, 0)
cache_key = (char, color.r, color.g, color.b, color.a) char_image = ray.image_text_ex(font, char, font_size, 0, color)
if cache_key in self._rotation_cache:
return self._rotation_cache[cache_key]
char_size = self._get_char_size(char)
padding = int(self.outline_thickness * 3) # Increased padding
temp_width = max(int(char_size.y) + padding, self.font_size + padding)
temp_height = max(int(char_size.x) + padding, self.font_size + padding)
temp_image = ray.gen_image_color(temp_width, temp_height, ray.Color(0, 0, 0, 0))
center_x = (temp_width - char_size.y) // 2
center_y = (temp_height - char_size.x) // 2
ray.image_draw_text_ex(
temp_image,
self.font,
char,
ray.Vector2(center_x-5, center_y), # Centered placement with padding
self.font_size,
1.0,
color
)
# Rotate the temporary image 90 degrees counterclockwise
rotated_image = ray.gen_image_color(temp_height, temp_width, ray.Color(0, 0, 0, 0))
for x in range(temp_width):
for y in range(temp_height):
pixel = ray.get_image_color(temp_image, x, temp_height - y - 1)
ray.image_draw_pixel(rotated_image, y, x, pixel)
# Unload temporary image
ray.unload_image(temp_image)
# Cache the rotated image
self._rotation_cache[cache_key] = rotated_image
return rotated_image
def _calculate_horizontal_text_width(self):
"""Calculate the total width of horizontal text with custom spacing"""
if not self.text:
return 0
total_width = 0
for i, char in enumerate(self.text):
char_size = ray.measure_text_ex(self.font, char, self.font_size, 1.0)
total_width += char_size.x
# Add spacing between characters (except for the last character)
if i < len(self.text) - 1:
total_width += (char_size.x * (self.horizontal_spacing - 1.0))
return total_width
def _calculate_dimensions(self):
padding = int(self.outline_thickness * 3)
if not self.vertical:
if self.horizontal_spacing == 1.0:
# Use default raylib measurement for normal spacing
text_size = ray.measure_text_ex(self.font, self.text, self.font_size, 1.0)
return int(text_size.x + padding * 2), int(text_size.y + padding * 2)
else: else:
# Calculate custom spacing width char_width = ray.measure_text(char, font_size)
text_width = self._calculate_horizontal_text_width() char_size = ray.Vector2(char_width, font_size)
text_height = ray.measure_text_ex(self.font, "Ag", self.font_size, 1.0).y # Use sample chars for height char_image = ray.image_text(char, font_size, color)
return int(text_width + padding * 2), int(text_height + padding * 2)
# Rotate character if it's in the rotate_chars set
if char in rotate_chars:
rotated_image = ray.gen_image_color(char_image.height, char_image.width, ray.BLANK)
# Manual 90-degree clockwise rotation
for y in range(char_image.height):
for x in range(char_image.width):
src_color = ray.get_image_color(char_image, x, y)
# 90° clockwise: new_x = old_y, new_y = width - 1 - old_x
new_x = y
new_y = char_image.width - 1 - x
ray.image_draw_pixel(rotated_image, new_x, new_y, src_color)
ray.unload_image(char_image)
char_image = rotated_image
effective_width = char_size.y # Height becomes width when rotated
else: else:
# Parse text into segments effective_width = char_size.x
segments = self._parse_text_segments()
char_heights = [] # Center the character horizontally
char_widths = [] char_x = width // 2 - effective_width // 2
for segment in segments: ray.image_draw(image, char_image,
if segment['is_horizontal']: ray.Rectangle(0, 0, char_image.width, char_image.height),
# For horizontal exceptions, add their height as spacing ray.Rectangle(char_x, char_y, char_image.width, char_image.height),
text_size = ray.measure_text_ex(self.font, segment['text'], self.font_size, 1.0) ray.WHITE)
char_heights.append(text_size.y * self.line_spacing) ray.unload_image(char_image)
char_widths.append(text_size.x)
else:
# Process vertical text with character grouping
char_groups = self._group_characters_with_punctuation(segment['text'])
for i, group in enumerate(char_groups): texture = ray.load_texture_from_image(image)
main_char = group['main_char'] ray.unload_image(image)
adjacent_punct = group['adjacent_punct']
# Get next group's main character for spacing calculation
next_char = char_groups[i+1]['main_char'] if i+1 < len(char_groups) else None
char_heights.append(self._calculate_vertical_spacing(main_char, next_char))
# Calculate width considering main char + adjacent punctuation
main_char_size = self._get_char_size(main_char)
group_width = main_char_size.x
# Add width for adjacent punctuation
for punct in adjacent_punct:
punct_size = self._get_char_size(punct)
group_width += punct_size.x
# For vertical characters, consider rotated dimensions
if main_char in self.vertical_chars:
char_widths.append(group_width + padding)
else:
char_widths.append(group_width)
max_char_width = max(char_widths) if char_widths else 0
total_height = sum(char_heights) if char_heights else 0
width = int(max_char_width + padding * 2) # Padding on both sides
height = int(total_height + padding * 2) # Padding on top and bottom
return width, height
def _draw_horizontal_text(self, image):
if self.horizontal_spacing == 1.0:
# Use original method for normal spacing
text_size = ray.measure_text_ex(self.font, self.text, self.font_size, 1.0)
position = ray.Vector2((image.width - text_size.x) / 2, (image.height - text_size.y) / 2)
for dx in range(-self.outline_thickness, self.outline_thickness + 1):
for dy in range(-self.outline_thickness, self.outline_thickness + 1):
# Skip the center position (will be drawn as main text)
if dx == 0 and dy == 0:
continue
# Calculate outline distance
dist = (dx*dx + dy*dy) ** 0.5
# Only draw outline positions that are near the outline thickness
if dist <= self.outline_thickness + 0.5:
ray.image_draw_text_ex(
image,
self.font,
self.text,
ray.Vector2(position.x + dx, position.y + dy),
self.font_size,
1.0,
self.outline_color
)
# Draw main text
ray.image_draw_text_ex(
image,
self.font,
self.text,
position,
self.font_size,
1.0,
self.text_color
)
else:
# Draw text with custom character spacing
text_width = self._calculate_horizontal_text_width()
text_height = ray.measure_text_ex(self.font, "Ag", self.font_size, 1.0).y
start_x = (image.width - text_width) / 2
start_y = (image.height - text_height) / 2
# First draw all outlines
current_x = start_x
for i, char in enumerate(self.text):
char_size = ray.measure_text_ex(self.font, char, self.font_size, 1.0)
for dx in range(-self.outline_thickness, self.outline_thickness + 1):
for dy in range(-self.outline_thickness, self.outline_thickness + 1):
if dx == 0 and dy == 0:
continue
dist = (dx*dx + dy*dy) ** 0.5
if dist <= self.outline_thickness + 0.5:
ray.image_draw_text_ex(
image,
self.font,
char,
ray.Vector2(current_x + dx, start_y + dy),
self.font_size,
1.0,
self.outline_color
)
# Move to next character position
current_x += char_size.x
if i < len(self.text) - 1: # Add spacing except for last character
current_x += (char_size.x * (self.horizontal_spacing - 1.0))
# Then draw all main text
current_x = start_x
for i, char in enumerate(self.text):
char_size = ray.measure_text_ex(self.font, char, self.font_size, 1.0)
ray.image_draw_text_ex(
image,
self.font,
char,
ray.Vector2(current_x, start_y),
self.font_size,
1.0,
self.text_color
)
# Move to next character position
current_x += char_size.x
if i < len(self.text) - 1: # Add spacing except for last character
current_x += (char_size.x * (self.horizontal_spacing - 1.0))
def _draw_vertical_text(self, image, width):
padding = int(self.outline_thickness * 2)
segments = self._parse_text_segments()
positions = []
current_y = padding # Start with padding at the top
for segment in segments:
if segment['is_horizontal']:
# Handle horizontal exception
text_size = ray.measure_text_ex(self.font, segment['text'], self.font_size, 1.0)
center_offset = (width - text_size.x) // 2
char_height = text_size.y * self.line_spacing
positions.append({
'type': 'horizontal',
'text': segment['text'],
'x': center_offset,
'y': current_y,
'height': char_height
})
current_y += char_height
else:
# Handle vertical text with character grouping
char_groups = self._group_characters_with_punctuation(segment['text'])
for i, group in enumerate(char_groups):
main_char = group['main_char']
adjacent_punct = group['adjacent_punct']
# Get next group for spacing calculation
next_char = char_groups[i+1]['main_char'] if i+1 < len(char_groups) else None
char_height = self._calculate_vertical_spacing(main_char, next_char)
# Calculate positioning for main character
main_char_size = self._get_char_size(main_char)
if main_char in self.vertical_chars:
rotated_img = self._get_rotated_char(main_char, self.text_color)
main_char_width = rotated_img.width
center_offset = (width - main_char_width) // 2
else:
main_char_width = main_char_size.x
center_offset = (width - main_char_width) // 2
# Add main character position
positions.append({
'type': 'vertical',
'char': main_char,
'x': center_offset,
'y': current_y,
'height': char_height,
'is_vertical_char': main_char in self.vertical_chars
})
# Add adjacent punctuation positions
punct_x_offset = center_offset + main_char_width
for punct in adjacent_punct:
punct_size = self._get_char_size(punct)
positions.append({
'type': 'vertical',
'char': punct,
'x': punct_x_offset,
'y': current_y+5,
'height': 0, # No additional height for punctuation
'is_vertical_char': punct in self.vertical_chars,
'is_adjacent': True
})
punct_x_offset += punct_size.x
current_y += char_height
# First draw all outlines
outline_thickness = int(self.outline_thickness)
for pos in positions:
if pos['type'] == 'horizontal':
# Draw horizontal text outline
for dx in range(-outline_thickness, outline_thickness + 1):
for dy in range(-outline_thickness, outline_thickness + 1):
if dx == 0 and dy == 0:
continue
dist = (dx*dx + dy*dy) ** 0.5
if dist <= outline_thickness + 0.5:
ray.image_draw_text_ex(
image,
self.font,
pos['text'],
ray.Vector2(pos['x'] + dx, pos['y'] + dy),
self.font_size,
1.0,
self.outline_color
)
else:
# Draw vertical character outline
for dx in range(-outline_thickness, outline_thickness + 1):
for dy in range(-outline_thickness, outline_thickness + 1):
if dx == 0 and dy == 0:
continue
dist = (dx*dx + dy*dy) ** 0.5
if dist <= outline_thickness + 0.5:
if pos['is_vertical_char']:
rotated_img = self._get_rotated_char(pos['char'], self.outline_color)
ray.image_draw(
image,
rotated_img,
ray.Rectangle(0, 0, rotated_img.width, rotated_img.height),
ray.Rectangle(
int(pos['x'] + dx),
int(pos['y'] + dy),
rotated_img.width,
rotated_img.height
),
ray.WHITE
)
else:
ray.image_draw_text_ex(
image,
self.font,
pos['char'],
ray.Vector2(pos['x'] + dx, pos['y'] + dy),
self.font_size,
1.0,
self.outline_color
)
# Then draw all main text
for pos in positions:
if pos['type'] == 'horizontal':
# Draw horizontal text
ray.image_draw_text_ex(
image,
self.font,
pos['text'],
ray.Vector2(pos['x'], pos['y']),
self.font_size,
1.0,
self.text_color
)
else:
# Draw vertical character
if pos['is_vertical_char']:
rotated_img = self._get_rotated_char(pos['char'], self.text_color)
ray.image_draw(
image,
rotated_img,
ray.Rectangle(0, 0, rotated_img.width, rotated_img.height),
ray.Rectangle(
int(pos['x']),
int(pos['y']),
rotated_img.width,
rotated_img.height
),
ray.WHITE
)
else:
ray.image_draw_text_ex(
image,
self.font,
pos['char'],
ray.Vector2(pos['x'], pos['y']),
self.font_size,
1.0,
self.text_color
)
def _create_texture(self):
if self.hash in text_cache:
texture = ray.load_texture(f'cache/image/{self.hash}.png')
return texture return texture
self.font = self._load_font_for_text(self.text) def _create_text_horizontal(self, text: str, font_size: int, color: ray.Color, bg_color: ray.Color, font: Optional[ray.Font]=None, padding: int=10):
if font:
width, height = self._calculate_dimensions() text_size = ray.measure_text_ex(font, text, font_size, 0)
total_width = text_size.x + (padding * 2)
width += int(self.outline_thickness * 1.5) total_height = text_size.y + (padding * 2)
height += int(self.outline_thickness * 1.5)
image = ray.gen_image_color(width, height, ray.Color(0, 0, 0, 0))
if not self.vertical:
self._draw_horizontal_text(image)
else: else:
self._draw_vertical_text(image, width) total_width = ray.measure_text(text, font_size) + (padding * 2)
total_height = font_size + (padding * 2)
image = ray.gen_image_color(int(total_width), int(total_height), bg_color)
if font:
text_image = ray.image_text_ex(font, text, font_size, 0, color)
else:
text_image = ray.image_text(text, font_size, color)
text_x = padding
text_y = padding
ray.image_draw(image, text_image,
ray.Rectangle(0, 0, text_image.width, text_image.height),
ray.Rectangle(text_x, text_y, text_image.width, text_image.height),
ray.WHITE)
ray.unload_image(text_image)
ray.export_image(image, f'cache/image/{self.hash}.png')
text_cache.add(self.hash)
texture = ray.load_texture_from_image(image) texture = ray.load_texture_from_image(image)
ray.unload_image(image) ray.unload_image(image)
return texture return texture
def draw(self, src: ray.Rectangle, dest: ray.Rectangle, origin: ray.Vector2, rotation: float, color: ray.Color): def draw(self, src: ray.Rectangle, dest: ray.Rectangle, origin: ray.Vector2, rotation: float, color: ray.Color):
if isinstance(color, tuple):
alpha_value = ray.ffi.new('float*', color[3] / 255.0)
else:
alpha_value = ray.ffi.new('float*', color.a / 255.0)
ray.set_shader_value(self.shader, self.alpha_loc, alpha_value, SHADER_UNIFORM_FLOAT)
ray.begin_shader_mode(self.shader)
ray.draw_texture_pro(self.texture, src, dest, origin, rotation, color) ray.draw_texture_pro(self.texture, src, dest, origin, rotation, color)
ray.end_shader_mode()
def unload(self): def unload(self):
for img in self._rotation_cache.values(): ray.unload_shader(self.shader)
ray.unload_image(img)
self._rotation_cache.clear()
for img in self._horizontal_cache.values():
ray.unload_image(img)
self._horizontal_cache.clear()
ray.unload_texture(self.texture) ray.unload_texture(self.texture)

32
scenes/devtest.py Normal file
View File

@@ -0,0 +1,32 @@
import pyray as ray
from libs.utils import OutlinedText, get_current_ms
class DevScreen:
def __init__(self, width: int, height: int):
self.width = width
self.height = height
self.screen_init = False
self.time_now = get_current_ms()
self.test = OutlinedText('Triple Helix', 40, ray.Color(255, 255, 255, 255), ray.Color(101, 0, 82, 255), outline_thickness=4, vertical=True)
def on_screen_start(self):
if not self.screen_init:
self.screen_init = True
def on_screen_end(self, next_screen: str):
self.screen_init = False
return next_screen
def update(self):
self.on_screen_start()
if ray.is_key_pressed(ray.KeyboardKey.KEY_ENTER):
return self.on_screen_end('TITLE')
def draw(self):
ray.draw_rectangle(0, 0, self.width, self.height, ray.WHITE)
src = ray.Rectangle(0, 0, self.test.texture.width, self.test.texture.height)
dest = ray.Rectangle(self.width//2 - self.test.texture.width//2, self.height//2 - self.test.texture.height//2, self.test.texture.width, self.test.texture.height)
self.test.draw(src, dest, ray.Vector2(0, 0), 0, ray.WHITE)

View File

@@ -279,12 +279,12 @@ class EntryScreen:
box_title = self.box_titles[i][1] box_title = self.box_titles[i][1]
src = ray.Rectangle(0, 0, box_title.texture.width, box_title.texture.height) src = ray.Rectangle(0, 0, box_title.texture.width, box_title.texture.height)
dest = ray.Rectangle(final_x + 12, y + 20, box_title.texture.width, box_title.texture.height) dest = ray.Rectangle(final_x + 25, y + 20, box_title.texture.width, box_title.texture.height)
box_title.draw(src, dest, ray.Vector2(0, 0), 0, color) box_title.draw(src, dest, ray.Vector2(0, 0), 0, color)
else: else:
box_title = self.box_titles[i][0] box_title = self.box_titles[i][0]
src = ray.Rectangle(0, 0, box_title.texture.width, box_title.texture.height) src = ray.Rectangle(0, 0, box_title.texture.width, box_title.texture.height)
dest = ray.Rectangle(final_x + 9, y + 20, box_title.texture.width, box_title.texture.height) dest = ray.Rectangle(final_x + 20, y + 20, box_title.texture.width, box_title.texture.height)
box_title.draw(src, dest, ray.Vector2(0, 0), 0, color) box_title.draw(src, dest, ray.Vector2(0, 0), 0, color)
def draw(self): def draw(self):

View File

@@ -1,6 +1,7 @@
import pyray as ray import pyray as ray
import sounddevice as sd import sounddevice as sd
from libs.audio import audio
from libs.utils import ( from libs.utils import (
global_data, global_data,
is_l_don_pressed, is_l_don_pressed,
@@ -34,6 +35,9 @@ class SettingsScreen:
self.screen_init = False self.screen_init = False
save_config(self.config) save_config(self.config)
global_data.config = self.config global_data.config = self.config
audio.close_audio_device()
audio.type = global_data.config["audio"]["device_type"]
audio.init_audio_device()
return "ENTRY" return "ENTRY"
def get_current_settings(self): def get_current_settings(self):

View File

@@ -400,7 +400,7 @@ class SongBox:
self.tja_count = tja_count self.tja_count = tja_count
self.tja_count_text = None self.tja_count_text = None
if self.tja_count is not None and self.tja_count != 0: if self.tja_count is not None and self.tja_count != 0:
self.tja_count_text = OutlinedText(str(self.tja_count), 35, ray.Color(255, 255, 255, 255), ray.Color(0, 0, 0, 255), outline_thickness=5, horizontal_spacing=1.2) self.tja_count_text = OutlinedText(str(self.tja_count), 35, ray.Color(255, 255, 255, 255), ray.Color(0, 0, 0, 255), outline_thickness=5)#, horizontal_spacing=1.2)
self.tja = tja self.tja = tja
self.hash = dict() self.hash = dict()
self.update(False) self.update(False)
@@ -1007,6 +1007,8 @@ class SongFile(FileSystemItem):
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, texture_index: int, tja=None, name_texture_index: Optional[int]=None):
super().__init__(path, name) super().__init__(path, name)
self.is_recent = (datetime.now() - datetime.fromtimestamp(path.stat().st_mtime)) <= timedelta(days=7) self.is_recent = (datetime.now() - datetime.fromtimestamp(path.stat().st_mtime)) <= timedelta(days=7)
if self.is_recent:
print(name, (datetime.now() - datetime.fromtimestamp(path.stat().st_mtime)))
self.tja = tja or TJAParser(path) self.tja = tja or TJAParser(path)
if self.is_recent: if self.is_recent:
self.tja.ex_data.new = True self.tja.ex_data.new = True

40
shader/outline.fs Normal file
View File

@@ -0,0 +1,40 @@
#version 330
in vec2 fragTexCoord;
in vec4 fragColor;
uniform sampler2D texture0;
uniform vec4 colDiffuse;
uniform vec2 textureSize;
uniform float outlineSize;
uniform vec4 outlineColor;
uniform float alpha;
uniform float smoothness = 1.0; // Add this uniform for control (0.0-1.0)
out vec4 finalColor;
void main()
{
vec4 texel = texture(texture0, fragTexCoord);
vec2 texelScale = vec2(outlineSize/textureSize.x, outlineSize/textureSize.y);
// Calculate outline
float outline = 0.0;
int ringSamples = 64;
int rings = 4;
for(int ring = 1; ring <= rings; ring++) {
float ringRadius = float(ring) / float(rings);
for(int i = 0; i < ringSamples; i++) {
float angle = 2.0 * 3.14159 * float(i) / float(ringSamples);
vec2 offset = vec2(cos(angle), sin(angle)) * texelScale * ringRadius;
outline += texture(texture0, fragTexCoord + offset).a / float(rings);
}
}
outline = min(outline, 1.0);
outline = smoothstep(0.1, 0.6, outline);
float edgeStart = 0.5 - smoothness * 0.3;
float edgeEnd = 0.5 + smoothness * 0.3;
float textAlpha = smoothstep(edgeStart, edgeEnd, texel.a);
vec3 color = mix(outlineColor.rgb, texel.rgb, textAlpha);
float combinedAlpha = mix(outline * outlineColor.a, texel.a, textAlpha);
finalColor = vec4(color, combinedAlpha * alpha);
}