// Common GUI for media player // Created by CuriousTorvald on 2025-09-30. // Subtitle display functions function clearSubtitleArea() { // Clear the subtitle area at the bottom of the screen // Text mode is 80x32, so clear the bottom few lines let oldFgColour = con.get_color_fore() let oldBgColour = con.get_color_back() con.color_pair(255, 255) // transparent to clear // Clear bottom 4 lines for subtitles for (let row = 28; row <= 31; row++) { con.move(row, 1) for (let col = 1; col <= 80; col++) { print(" ") } } con.color_pair(oldFgColour, oldBgColour) } function getVisualLength(line) { // Calculate the visual length of a line excluding formatting tags let visualLength = 0 let i = 0 while (i < line.length) { if (i < line.length - 2 && line[i] === '<') { // Check for formatting tags and skip them if (line.substring(i, i + 3).toLowerCase() === '' || line.substring(i, i + 3).toLowerCase() === '') { i += 3 // Skip tag } else if (i < line.length - 3 && (line.substring(i, i + 4).toLowerCase() === '' || line.substring(i, i + 4).toLowerCase() === '')) { i += 4 // Skip closing tag } else { // Not a formatting tag, count the character visualLength++ i++ } } else { // Regular character, count it visualLength++ i++ } } return visualLength } function displayFormattedLine(line) { // Parse line and handle and tags with colour changes // Default subtitle colour: yellow (231), formatted text: white (254) let i = 0 let inBoldOrItalic = false // insert initial padding block con.color_pair(0, 255) con.prnch(0xDE) con.color_pair(231, 0) while (i < line.length) { if (i < line.length - 2 && line[i] === '<') { // Check for opening tags if (line.substring(i, i + 3).toLowerCase() === '' || line.substring(i, i + 3).toLowerCase() === '') { con.color_pair(254, 0) // Switch to white for formatted text inBoldOrItalic = true i += 3 } else if (i < line.length - 3 && (line.substring(i, i + 4).toLowerCase() === '' || line.substring(i, i + 4).toLowerCase() === '')) { con.color_pair(231, 0) // Switch back to yellow for normal text inBoldOrItalic = false i += 4 } else { // Not a formatting tag, print the character print(line[i]) i++ } } else { // Regular character, print it print(line[i]) i++ } } // insert final padding block con.color_pair(0, 255) con.prnch(0xDD) con.color_pair(231, 0) } function displaySubtitle(text, position = 0) { if (!text || text.length === 0) { clearSubtitleArea() return } // Set subtitle colours: yellow (231) on black (0) let oldFgColour = con.get_color_fore() let oldBgColour = con.get_color_back() con.color_pair(231, 0) // Split text into lines let lines = text.split('\n') // Calculate position based on subtitle position setting let startRow, startCol // Calculate visual length without formatting tags for positioning let longestLineLength = lines.map(s => getVisualLength(s)).sort().last() switch (position) { case 2: // center left case 6: // center right case 8: // dead center startRow = 16 - Math.floor(lines.length / 2) break case 3: // top left case 4: // top center case 5: // top right startRow = 2 break case 0: // bottom center case 1: // bottom left case 7: // bottom right default: startRow = 31 - lines.length startRow = 31 - lines.length startRow = 31 - lines.length // Default to bottom center } // Display each line for (let i = 0; i < lines.length; i++) { let line = lines[i].trim() if (line.length === 0) continue let row = startRow + i if (row < 1) row = 1 if (row > 32) row = 32 // Calculate column based on alignment switch (position) { case 1: // bottom left case 2: // center left case 3: // top left startCol = 1 break case 5: // top right case 6: // center right case 7: // bottom right startCol = Math.max(1, 78 - getVisualLength(line) - 2) break case 0: // bottom center case 4: // top center case 8: // dead center default: startCol = Math.max(1, Math.floor((80 - longestLineLength - 2) / 2) + 1) break } con.move(row, startCol) // Parse and display line with formatting tag support displayFormattedLine(line) } con.color_pair(oldFgColour, oldBgColour) } exports = { clearSubtitleArea, displaySubtitle }