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,
load_all_textures_from_zip,
)
from scenes.devtest import DevScreen
from scenes.entry import EntryScreen
from scenes.game import GameScreen
from scenes.result import ResultScreen
@@ -33,6 +34,7 @@ class Screens:
GAME = "GAME"
RESULT = "RESULT"
SETTINGS = "SETTINGS"
DEV_MENU = "DEV_MENU"
def create_song_db():
with sqlite3.connect('scores.db') as con:
@@ -84,24 +86,26 @@ def main():
if global_data.config["video"]["fullscreen"]:
ray.maximize_window()
current_screen = Screens.TITLE
current_screen = Screens.DEV_MENU
audio.init_audio_device()
title_screen = TitleScreen(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)
result_screen = ResultScreen(screen_width, screen_height)
settings_screen = SettingsScreen(screen_width, screen_height)
dev_screen = DevScreen(screen_width, screen_height)
screen_mapping = {
Screens.ENTRY: entry_screen,
Screens.TITLE: title_screen,
Screens.SONG_SELECT: song_select_screen,
#Screens.SONG_SELECT: song_select_screen,
Screens.GAME: game_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)
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.audio_device_ready = False
self.sounds = {}
self.music_streams = {}
self.sound_queue = queue.Queue()
self.music_queue = queue.Queue()
print("Audio device closed")
return

View File

@@ -176,7 +176,6 @@ class TJAParser:
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):
self.file_path: Path = path
print(self.file_path)
lines = self.file_path.read_text(encoding='utf-8-sig').splitlines()
self.data = [cleaned for line in lines

View File

@@ -7,10 +7,15 @@ import zipfile
from dataclasses import dataclass, field
from functools import lru_cache
from pathlib import Path
from typing import Any
from typing import Any, Optional
import pyray as ray
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.
@@ -231,600 +236,150 @@ class 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:
text: str
font_size: int
text_color: ray.Color
outline_color: ray.Color
font: ray.Font = ray.Font()
outline_thickness: int = 2
vertical: bool = False
line_spacing: float = 1.0 # Line spacing for vertical text
horizontal_spacing: float = 1.0 # Character spacing for horizontal text
lowercase_spacing_factor: float = 0.85 # Adjust spacing for lowercase letters and whitespace
vertical_chars: set = field(default_factory=lambda: {'-', '', '|', '/', '\\', '', '', '~', '', '', '(', ')',
'', '', '[', ']', '', '', '', '', '', '', '', ':', ''})
no_space_chars: set = field(default_factory=lambda: {
'', '','', '','', '','', '','', '',
'', '','', '','', '','', '','', '',
'', '','','','','','','','','','',
'','','','','','',''
})
# New field for horizontal exception strings
horizontal_exceptions: set = field(default_factory=lambda: {'!!!!', '!!!', '!!', '','','!?', '', '??', '', '†††', '(°∀°)', '(°∀°)'})
# New field for adjacent punctuation characters
adjacent_punctuation: set = field(default_factory=lambda: {'.', ',', '', '', "'", '"', '´', '`'})
def __init__(self, text: str, font_size: int, color: ray.Color, outline_color: ray.Color, outline_thickness=5.0, vertical=False):
self.font = self._load_font_for_text(text)
if vertical:
self.texture = self._create_text_vertical(text, font_size, color, ray.BLANK, self.font)
else:
self.texture = self._create_text_horizontal(text, font_size, color, ray.BLANK, self.font)
outline_size = ray.ffi.new('float*', outline_thickness)
if isinstance(outline_color, tuple):
outline_color_alloc = ray.ffi.new("float[4]", [
outline_color[0] / 255.0,
outline_color[1] / 255.0,
outline_color[2] / 255.0,
outline_color[3] / 255.0
])
else:
outline_color_alloc = ray.ffi.new("float[4]", [
outline_color.r / 255.0,
outline_color.g / 255.0,
outline_color.b / 255.0,
outline_color.a / 255.0
])
texture_size = ray.ffi.new("float[2]", [self.texture.width, self.texture.height])
def __post_init__(self):
# Cache for rotated characters
self._rotation_cache = rotation_cache
# Cache for character measurements
self._char_size_cache = char_size_cache
# Cache for horizontal exception measurements
self._horizontal_cache = horizontal_cache
self.hash = self._get_hash()
self.texture = self._create_texture()
self.shader = ray.load_shader('', 'shader/outline.fs')
outline_size_loc = ray.get_shader_location(self.shader, "outlineSize")
outline_color_loc = ray.get_shader_location(self.shader, "outlineColor")
texture_size_loc = ray.get_shader_location(self.shader, "textureSize")
self.alpha_loc = ray.get_shader_location(self.shader, "alpha")
ray.set_shader_value(self.shader, outline_size_loc, outline_size, SHADER_UNIFORM_FLOAT)
ray.set_shader_value(self.shader, outline_color_loc, outline_color_alloc, SHADER_UNIFORM_VEC4)
ray.set_shader_value(self.shader, texture_size_loc, texture_size, SHADER_UNIFORM_VEC2)
def _load_font_for_text(self, text: str) -> ray.Font:
codepoint_count = ray.ffi.new('int *', 0)
unique_codepoints = set(text)
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):
n = hashlib.sha256()
n.update(self.text.encode('utf-8'))
n.update(str(self.vertical).encode('utf-8'))
n.update(str(self.horizontal_spacing).encode('utf-8')) # Include horizontal spacing in hash
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 _create_text_vertical(self, text: str, font_size: int, color: ray.Color, bg_color: ray.Color, font: Optional[ray.Font]=None, padding: int=10):
rotate_chars = {'-', '', '|', '/', '\\', '', '', '~', '', '', '(', ')',
'', '', '[', ']', '', '', '', '', '', '', '', ':', ''}
max_char_width = 0
total_height = padding * 2 # Top and bottom padding
def _parse_text_segments(self):
"""Parse text into segments, identifying horizontal exceptions"""
if not self.vertical:
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)
for char in text:
if font:
char_size = ray.measure_text_ex(font, char, font_size, 0)
else:
# Add character to current segment
current_segment += self.text[i]
i += 1
char_width = ray.measure_text(char, font_size)
char_size = ray.Vector2(char_width, font_size)
# Add remaining segment
if current_segment:
segments.append({'text': current_segment, 'is_horizontal': False})
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)
# If character should be rotated, swap width and height for measurements
if char in rotate_chars:
effective_width = char_size.y # Height becomes width when rotated 90°
else:
self._char_size_cache[char] = ray.measure_text_ex(self.font, char, self.font_size, 1.0)
return self._char_size_cache[char]
effective_width = char_size.x
def _calculate_vertical_spacing(self, current_char, next_char=None):
"""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())
max_char_width = max(max_char_width, effective_width)
# Additional check for capitalization transition
if next_char and ((current_char.isupper() and next_char.islower()) or
next_char in self.no_space_chars):
is_spacing_char = True
total_height += len(text) * font_size
width = int(max_char_width + (padding * 2)) # Add left and right padding
height = total_height
image = ray.gen_image_color(width, height, bg_color)
# Apply spacing factor if it's a spacing character
spacing = self.line_spacing * (self.lowercase_spacing_factor if is_spacing_char else 1.0)
return self.font_size * spacing
for i, char in enumerate(text):
char_y = i * ray.measure_text_ex(self.font, char, font_size, 0).y
char_y += padding
def _get_rotated_char(self, char: str, color):
"""Get or create a rotated character texture from cache"""
cache_key = (char, color.r, color.g, color.b, color.a)
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)
if font:
char_size = ray.measure_text_ex(font, char, font_size, 0)
char_image = ray.image_text_ex(font, char, font_size, 0, color)
else:
# Calculate custom spacing width
text_width = self._calculate_horizontal_text_width()
text_height = ray.measure_text_ex(self.font, "Ag", self.font_size, 1.0).y # Use sample chars for height
return int(text_width + padding * 2), int(text_height + padding * 2)
char_width = ray.measure_text(char, font_size)
char_size = ray.Vector2(char_width, font_size)
char_image = ray.image_text(char, font_size, color)
# 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:
effective_width = char_size.x
# Center the character horizontally
char_x = width // 2 - effective_width // 2
ray.image_draw(image, char_image,
ray.Rectangle(0, 0, char_image.width, char_image.height),
ray.Rectangle(char_x, char_y, char_image.width, char_image.height),
ray.WHITE)
ray.unload_image(char_image)
texture = ray.load_texture_from_image(image)
ray.unload_image(image)
return texture
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:
text_size = ray.measure_text_ex(font, text, font_size, 0)
total_width = text_size.x + (padding * 2)
total_height = text_size.y + (padding * 2)
else:
# Parse text into segments
segments = self._parse_text_segments()
char_heights = []
char_widths = []
for segment in segments:
if segment['is_horizontal']:
# For horizontal exceptions, add their height as spacing
text_size = ray.measure_text_ex(self.font, segment['text'], self.font_size, 1.0)
char_heights.append(text_size.y * self.line_spacing)
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):
main_char = group['main_char']
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
)
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:
# 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
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)
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
self.font = self._load_font_for_text(self.text)
width, height = self._calculate_dimensions()
width += int(self.outline_thickness * 1.5)
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:
self._draw_vertical_text(image, width)
ray.export_image(image, f'cache/image/{self.hash}.png')
text_cache.add(self.hash)
texture = ray.load_texture_from_image(image)
ray.unload_image(image)
return texture
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.end_shader_mode()
def unload(self):
for img in self._rotation_cache.values():
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_shader(self.shader)
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]
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)
else:
box_title = self.box_titles[i][0]
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)
def draw(self):

View File

@@ -1,6 +1,7 @@
import pyray as ray
import sounddevice as sd
from libs.audio import audio
from libs.utils import (
global_data,
is_l_don_pressed,
@@ -34,6 +35,9 @@ class SettingsScreen:
self.screen_init = False
save_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"
def get_current_settings(self):

View File

@@ -400,7 +400,7 @@ class SongBox:
self.tja_count = tja_count
self.tja_count_text = None
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.hash = dict()
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):
super().__init__(path, name)
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)
if self.is_recent:
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);
}