From 937d3e27edbb9e48077ae305e1d42569b78ecb64 Mon Sep 17 00:00:00 2001 From: minjaesong Date: Wed, 6 May 2026 21:48:11 +0900 Subject: [PATCH] taut: help message panel --- assets/disk0/tvdos/bin/taut.js | 187 +++++++++++-- assets/disk0/tvdos/bin/taut_helpmsg.js | 339 +++++++++++++++++++++--- assets/disk0/tvdos/bin/tautfont.kra | 4 +- assets/disk0/tvdos/bin/tautfont_low.chr | Bin 1920 -> 1920 bytes terranmon.txt | 4 +- 5 files changed, 464 insertions(+), 70 deletions(-) diff --git a/assets/disk0/tvdos/bin/taut.js b/assets/disk0/tvdos/bin/taut.js index d33d46b..f6f0bc5 100644 --- a/assets/disk0/tvdos/bin/taut.js +++ b/assets/disk0/tvdos/bin/taut.js @@ -146,11 +146,11 @@ Z:"UNIMPLEMENTED", // IT: MIDI macro } const panFxNames = { 0:"Set to", -1:"Slide L", -2:"Slide R", +1:"Slide R", +2:"Slide L", 3:"Fine slide", -30:"Fine slide L", -31:"Fine slide R", +30:"Fine slide R", +31:"Fine slide L", 999:"---", } const volFxNames = { @@ -209,7 +209,7 @@ sym:[` \u00E0\u00E1`,` \u00E2\u00E3`,` \u00E4\u00E5`,` \u00E6\u00E7`,` \u00E8\u0 const volEffSym = [sym.volset, sym.volup, sym.voldn, sym.volfineup, sym.volfinedn] -const panEffSym = [sym.panset, sym.panle, sym.panri, sym.panfinele, sym.panfineri] +const panEffSym = [sym.panset, sym.panri, sym.panle, sym.panfineri, sym.panfinele] const colNote = 239 const colInst = 114 @@ -521,7 +521,7 @@ function loadTaud(filePath, songIndex) { ptns[i*2] = ((hi >> 4) << 8) | ((mi >> 4) << 4) | (lo >> 4) ptns[i*2+1] = ((hi & 0xF) << 8) | ((mi & 0xF) << 4) | (lo & 0xF) } - const instr = sys.peek(cueSheetPtr + c * CUE_SIZE + 30) & 0xFF + const instr = (sys.peek(cueSheetPtr + c * CUE_SIZE + 30) << 8) | sys.peek(cueSheetPtr + c * CUE_SIZE + 31) cues[c] = { ptns, instr } for (let v = 0; v < NUM_VOICES; v++) { @@ -557,7 +557,7 @@ let VOCSIZE_TIMELINE_FULL = Math.floor((SCRW - 3) / COLSIZE_TIMELINE_FULL) const ORDERS_CMD_X = 5 const ORDERS_VOICE_X = 9 -const VOCSIZE_ORDERS = Math.floor((SCRW - 8) / 4) +const VOCSIZE_ORDERS = Math.floor((SCRW - 10) / 4) const VIEW_TIMELINE = 0 const VIEW_CUES = 1 @@ -863,7 +863,9 @@ function drawControlHint() { ['n','Solo'], ['m','Mute'], ['sep'], - ['tab','Panel'] + ['tab','Panel'], + ['sep'], + ['!','Help'], // ['sep'], // ['q','Quit'], ] @@ -874,6 +876,8 @@ function drawControlHint() { ['sp','Edit'], ['sep'], ['tab','Panel'], + ['sep'], + ['!','Help'], // ['sep'], // ['q','Quit'], ] @@ -885,6 +889,8 @@ function drawControlHint() { ['sp','Edit'], ['sep'], ['tab','Panel'], + ['sep'], + ['!','Help'], // ['sep'], // ['q','Quit'], ] @@ -902,6 +908,8 @@ function drawControlHint() { ['sep'], ['=','KOff'], ['^','KCut'], + ['sep'], + ['!','Help'], // ['sep'], // ['Sp','ExitEdit'], ] @@ -911,18 +919,22 @@ function drawControlHint() { ['sep'], [`0${sym.doubledot}9 A${sym.doubledot}F`,'Instrument'], ['sep'], - ['sp','ExitEdit'], + ['!','Help'], + // ['sep'], + // ['sp','ExitEdit'], ] let hintElemEditVolEff = [ [`\u008428u\u008429u`,'Nav'], [`pg\u008418u`,'Cue'], ['sep'], - ['h','Set'], - ['j','SlideDn'], - ['k','SlideUp'], - ['u','FineDn'], - ['i','FineUp'], + ['.','Set'], + ['v','SlideUp'], + ['^','SlideDn'], + ['-','FineDn'], + ['=','FineUp'], [`0${sym.doubledot}9 A${sym.doubledot}F`,'Val'], + ['sep'], + ['!','Help'], // ['sep'], // ['Sp','ExitEdit'], ] @@ -930,11 +942,11 @@ function drawControlHint() { [`\u008428u\u008429u`,'Nav'], [`pg\u008418u`,'Cue'], ['sep'], - ['h','Set'], - ['j','SlideL'], - ['k','SlideR'], - ['u','FineL'], - ['i','FineR'], + ['.','Set'], + ['<','SlideL'], + ['>','SlideR'], + ['-','FineL'], + ['=','FineR'], [`0${sym.doubledot}9 A${sym.doubledot}F`,'Val'], // ['sep'], // ['Sp','ExitEdit'], @@ -945,7 +957,9 @@ function drawControlHint() { ['sep'], [`0${sym.doubledot}9 A${sym.doubledot}F`,`FxSym`], ['sep'], - ['sp','ExitEdit'], + ['!','Help'], + // ['sep'], + // ['sp','ExitEdit'], ] let hintElemEditFxVal = [ [`\u008428u\u008429u`,'Nav'], @@ -953,10 +967,12 @@ function drawControlHint() { ['sep'], [`0${sym.doubledot}9 A${sym.doubledot}F`,`FxVal`], ['sep'], - ['sp','ExitEdit'], + ['!','Help'], + // ['sep'], + // ['sp','ExitEdit'], ] - const hintElemExternal = [['Tab','Panel']] + const hintElemExternal = [['Tab','Panel'],['sep'],['!','Help']] let hintElems = [hintElemTimeline, hintElemOrders, hintElemPatterns, hintElemExternal, hintElemExternal, hintElemExternal, hintElemExternal] let hintElemPat = [hintElemEditNoteValue, hintElemEditInstValue, hintElemEditVolEff, hintElemEditPanEff, hintElemEditFxSym, hintElemEditFxVal] @@ -1383,7 +1399,7 @@ function drawOrdersHeader() { con.color_pair(colVoiceHdr, 255) print(' ') con.color_pair(colVoiceHdr, ordersColCursor === 0 ? colHighlight : 255) - print('Cmd ') + print('Comand ') for (let c = 0; c < VOCSIZE_ORDERS; c++) { const v = ordersVoiceOff + c con.color_pair(colVoiceHdr, ordersColCursor === v + 1 ? colHighlight : 255) @@ -1416,9 +1432,9 @@ function drawOrdersContents(wo) { // CMD column — crosshair highlight at (ordersCursor, col 0) const cmdBack = (isSel && ordersColCursor === 0) ? colPlayback : back con.color_pair(cue.instr ? colStatus : colSep, cmdBack) - print(cue.instr ? cue.instr.hex02() : '--') + print(cue.instr ? cueInstToStr(cue.instr) : '------') con.color_pair(colBackPtn, back) - print(' ') + print(' ') // Voice columns for (let c = 0; c < VOCSIZE_ORDERS; c++) { const v = ordersVoiceOff + c @@ -1435,6 +1451,35 @@ function drawOrdersContents(wo) { } } +function cueInstToStr(inst) { + let foreword = (inst >>> 12) & 15 + let preamble = (inst >>> 8) & 15 + let arg12 = inst & 0xFFF + let arg8 = inst & 0xFF + let fallback = `?${inst.hex04()}?` + switch (foreword) { + case 0b1000: + return "BAK" + arg12.hex03() + case 0b1001: + return "FWD" + arg12.hex03() + case 0b1111: + return "JMP" + arg12.hex03() + case 0b0000: + switch (preamble) { + case 0b0010: + return "LEN " + arg8.dec02() + case 0b0001: + return arg8 ? ("FADE" + arg8.dec02()) : "HALT " + case 0b0000: + return "NO-OP " + default: + return fallback + } + default: + return fallback + } +} + function timelineInput(wo, event) { const keysym = event[1] const keyJustHit = (1 == event[2]) @@ -2404,6 +2449,93 @@ function clampOrdersHoriz() { } } +///////////////////////////////////////////////////////////////////////////////////////////////////////////// +// HELP POPUP +///////////////////////////////////////////////////////////////////////////////////////////////////////////// + +const HELP_POPUP_W = SCRW - 8 +const HELP_POPUP_X = ((SCRW - HELP_POPUP_W) / 2 | 0) + 1 +const HELP_POPUP_Y = 5 +const HELP_POPUP_H = SCRH - HELP_POPUP_Y - 1 +const HELP_CONTENT_X = HELP_POPUP_X + 2 +const HELP_CONTENT_Y = HELP_POPUP_Y + 2 +const HELP_CONTENT_W = HELP_POPUP_W - 6 +const HELP_CONTENT_H = HELP_POPUP_H - 3 + +// Pre-typeset every panel's help text. taut_helpmsg.js reads HELPMSG_WIDTH for +// the wrap width and stores ready-to-print display strings into MSG_BY_TABS. +_G.TAUT.HELPMSG_WIDTH = HELP_CONTENT_W +_G.shell.execute("taut_helpmsg") + +function openHelpPopup() { + const helpmsg = _G.TAUT.HELPMSG || {} + const lines = (helpmsg.MSG_BY_TABS && helpmsg.MSG_BY_TABS[currentPanel]) || [''] + const colText = helpmsg.COL_TEXT || colWHITE + + const popup = new win.WindowObject( + HELP_POPUP_X, HELP_POPUP_Y, HELP_POPUP_W, HELP_POPUP_H, + ()=>{}, ()=>{}, `Help: ${PANEL_NAMES[currentPanel]}`, popupDrawFrame + ) + popup.isHighlighted = true + popup.titleBack = colPopupBack + + let scroll = 0 + const maxScroll = Math.max(0, lines.length - HELP_CONTENT_H) + + const repaint = () => { + con.color_pair(230, colPopupBack) + popup.drawFrame() + + // popupDrawFrame leaves the bottom row unpainted; fill it ourselves. + con.color_pair(colText, colPopupBack) + con.move(HELP_POPUP_Y + HELP_POPUP_H - 1, HELP_POPUP_X) + print(' '.repeat(HELP_POPUP_W)) + + for (let r = 0; r < HELP_CONTENT_H; r++) { + con.move(HELP_CONTENT_Y + r, HELP_CONTENT_X) + con.color_pair(colText, colPopupBack) + const line = lines[scroll + r] + print((line === undefined) ? ' '.repeat(HELP_CONTENT_W) : line) + } + + // scroll indicator on the right inner edge + if (lines.length > HELP_CONTENT_H) { + const trackH = HELP_CONTENT_H + const indPos = (maxScroll === 0) ? 0 : ((scroll * (trackH - 1) / maxScroll) | 0) + for (let r = 0; r < trackH; r++) { + con.move(HELP_CONTENT_Y + r, HELP_POPUP_X + HELP_POPUP_W - 2) + con.color_pair(colPushBtnBack, colPopupBack) + print(r === indPos ? '\u00DB' : '\u00B3') + } + } + + con.color_pair(colStatus, 255) + } + + repaint() + + let done = false + let eventJustReceived = true + while (!done) { + input.withEvent(ev => { + if (ev[0] !== 'key_down') return + //if (1 !== ev[2]) return // allow continuous scroll by key repeat + if (eventJustReceived) { eventJustReceived = false; return } + const ks = ev[1] + + if (ks === '' || ks === '!' || ks === 'q' || ks === '\n') { done = true } + else if (ks === '') { if (scroll > 0) { scroll -= 1; repaint() } } + else if (ks === '') { if (scroll < maxScroll) { scroll += 1; repaint() } } + else if (ks === '') { scroll = Math.max(0, scroll - HELP_CONTENT_H); repaint() } + else if (ks === '') { scroll = Math.min(maxScroll, scroll + HELP_CONTENT_H); repaint() } + else if (ks === '') { scroll = 0; repaint() } + else if (ks === '') { scroll = maxScroll; repaint() } + }) + } + + drawAll() +} + ///////////////////////////////////////////////////////////////////////////////////////////////////////////// // GOTO POPUP ///////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -2613,6 +2745,11 @@ while (!exitFlag) { return } + if (keyJustHit && keysym === '!') { + openHelpPopup() + return + } + panels[currentPanel].processInput(event) }) diff --git a/assets/disk0/tvdos/bin/taut_helpmsg.js b/assets/disk0/tvdos/bin/taut_helpmsg.js index 7d9c2b0..e8f58e3 100644 --- a/assets/disk0/tvdos/bin/taut_helpmsg.js +++ b/assets/disk0/tvdos/bin/taut_helpmsg.js @@ -9,10 +9,11 @@ Tags: - centre the line. If the line spans multiple lines, centre each line - align right - align left -µtone; - replace with the brand string -&bul; - replace with bullet (\u00847u) + - create virtual typesetting box. Left anchor: where the text cursor is. Right anchor: end of the line +µtone; - replace with the brand string (Microtone) +&bul; - replace with bullet (\u00F9) &ddot; - replace with double-dot (\u008419u) -&mdot; - replace with BIGDOT (\u00F9) +&mdot; - replace with BIGDOT (\u00FA) &updn; - up-down arrow (\u008418u) &udlr; - four direction arrow (\u008428u\u008429u) &keyoffsym; - pattern view key-off symbol (\u00A0\u00CD\u00CD\u00A1) @@ -22,63 +23,319 @@ Tags: default alignment: fully justified */ -help.notation = `CONTROL NOTATON +let helpNotation = `CONTROL NOTATON -µtone; shortcuts differentiate normal and shifted shortcuts. -&bul;a&ddot;z : alphabet without shift-in -&bul;A&ddot;Z : alphabet with shift-in -&bul;^ : control key` +µtone; shortcuts differentiate normal and shifted shortcuts. +&bul;a&ddot;z : alphabet without shift-in +&bul;A&ddot;Z : alphabet with shift-in +&bul;^q : hit 'q' with control key +&bul;^Q : hit 'q' with control and shift key` //////////////////////////////////////////////////////////////////////////////////////////////////// -help.jam = `NOTE JAMMING +let helpJam = `NOTE JAMMING Push keys to play or insert notes. - w e   t y u i + w e   t y u a s d f g h j k` //////////////////////////////////////////////////////////////////////////////////////////////////// -help.common = `COMMON CONTROLS +let helpCommon = `COMMON CONTROLS -&bul;Y : play the entire song from the current cue -&bul;U : play the current cue then stop -&bul;I : play the current row -&bul;O : stop the playback -&bul;tab : switch forward a tab -&bul;TAB : switch backward a tab -&bul;q : close µtone;` +&bul;! : show this help message +&bul;Y : play the entire song from the current cue +&bul;U : play the current cue then stop +&bul;I : play the current row +&bul;O : stop the playback +&bul;tab : switch forward a tab +&bul;TAB : switch backward a tab +&bul;q : close µtone;` //////////////////////////////////////////////////////////////////////////////////////////////////// -help.timeline = `TIMELINE VIEW +let helpTimeline = `TIMELINE VIEW Timeline has two distinct modes: view and edit mode. Two modes are toggled using the space bar. -View mode -&bul;Note jamming : plays the note -&bul;&udlr; : move the viewing cursor by voices and rows -&bul;pg&updn; : go to previous/next cue -&bul;W&mdot;E&mdot;R : toggle timeline view mode. W-most detailed, R-most abridged -&bul;n : toggle soloing of the selected voice -&bul;m : toggle muting of the selected voice +VIEW MODE +&bul;Note jamming : plays the note +&bul;&udlr; : move the viewing cursor by voices and rows +&bul;pg&updn; : go to previous/next cue +&bul;W&mdot;E&mdot;R : toggle timeline view mode. W-most detailed, R-most abridged +&bul;n : toggle soloing of the selected voice +&bul;m : toggle muting of the selected voice -Edit mode -&bul;Note jammping : (note column) inserts the note -&bul;{&mdot;} : (note column) lower/raise a note by one octave (or period) -&bul;[&mdot;] : (note column) lower/raise a note by one unit -&bul;= : (note column) insert a key-off &keyoffsym; -&bul;^ : (note column) insert a note-cut ¬ecutsym; -&bul;. : remove a symbol on the selected column -&bul;bksp : delete one character on the selected column -&bul;0&ddot;9 a&ddot;f : inserts a (hexa)decimal number -&bul;^&mdot;v : (volume column) slide up/down -&bul;<&mdot;> : (panning column) slide left/right -&bul;-&mdot;= : (vol/pan col) fine slide down/up -&bul;&udlr; : move the viewing cursor by columns and rows -&bul;pg&updn; : go to previous/next cue` +EDIT MODE +&bul;Note jamming : (note column) inserts the note +&bul;{&mdot;} : (note column) lower/raise a note by one octave (or period) +&bul;[&mdot;] : (note column) lower/raise a note by one unit +&bul;= : (note column) insert a key-off &keyoffsym; +&bul;^ : (note column) insert a note-cut ¬ecutsym; +&bul;. : remove a symbol on the selected column +&bul;bksp : delete one character on the selected column +&bul;0&ddot;9 a&ddot;f : inserts a (hexa)decimal number +&bul;^&mdot;v : (volume column) slide up/down +&bul;<&mdot;>: (panning column) slide left/right +&bul;-&mdot;= : (vol/pan col) fine slide down/up +&bul;&udlr; : move the viewing cursor by columns and rows +&bul;pg&updn; : go to previous/next cue` //////////////////////////////////////////////////////////////////////////////////////////////////// -if (!_G.TAUT.HELPMSG) _G.TAUT.HELPMSG=help; +// assemble help text pieces to complete help message +const SCRW = con.getmaxyx()[1] + +// Display-command palette. taut.js's popup uses (HELP_COL_TEXT on background) as the +// default colour pair, so embedded `\x1B[38;5;Nm` codes switch foreground only. +const HELP_COL_TEXT = 239 // popup body default (== colWHITE) +const HELP_COL_EMPH = 230 // ... highlight (== colVoiceHdr) +const HELP_COL_BRAND = 211 // first half of "Microtone" +const HELP_COL_BRAND_DIM = 239 // second half of "Microtone" + +const fgEsc = (n) => `\x1B[38;5;${n}m` +const ESC_DEFAULT = fgEsc(HELP_COL_TEXT) +const ESC_EMPH = fgEsc(HELP_COL_EMPH) +const MICROTONE = `${fgEsc(HELP_COL_BRAND)}Micro${fgEsc(HELP_COL_BRAND_DIM)}tone${ESC_DEFAULT}` + +// Replace &xxx; entities with their final printable representations. +function expandEntities(s) { + return s + .replaceAll('µtone;', MICROTONE) + .replaceAll('&bul;', '\u00F9') + .replaceAll('&ddot;', '\u008419u') + .replaceAll('&mdot;', '\u00FA') + .replaceAll('&updn;', '\u008418u') + .replaceAll('&udlr;', '\u008428u\u008429u') + .replaceAll('&keyoffsym;', '\u00A0\u00CD\u00CD\u00A1') + .replaceAll('¬ecutsym;', '\u00A4\u00A4\u00A4\u00A4') + .replaceAll(' ', '\u007F') + .replaceAll('­', '') + .replaceAll('<', '<') + .replaceAll('>', '>') +} + +// Tokenise a (post-entity-expansion) line. Returns an array of: +// {type:'word', text:String, w:int} - non-breakable run of visible chars (may carry ANSI escapes) +// {type:'sp'} - a single soft space (eligible for break/expansion) +// {type:'anchor', open:Boolean} - / markers (zero width) +// +// Width accounting: +// - ANSI escapes (`\x1B[...m`) : 0 visible chars +// - TSVM unicode escapes (`„..u`) : 1 visible char +// - non-breaking space ( ) : 1 visible char (consumed as part of a word) +// - soft hyphen (­) : dropped (not implemented as a break point) +// - everything else : 1 visible char +function tokenise(line) { + const tokens = [] + let buf = '' + let bufW = 0 + let i = 0 + + const flushWord = () => { + if (buf.length > 0) { + tokens.push({type: 'word', text: buf, w: bufW}) + buf = '' + bufW = 0 + } + } + + while (i < line.length) { + // inline tags (case-sensitive for , case-insensitive for ) + if (line.slice(i, i + 3) === '') { buf += ESC_EMPH; i += 3; continue } + if (line.slice(i, i + 4) === '') { buf += ESC_DEFAULT; i += 4; continue } + const head3 = line.slice(i, i + 3).toLowerCase() + const head4 = line.slice(i, i + 4).toLowerCase() + if (head3 === '') { flushWord(); tokens.push({type: 'anchor', open: true}); i += 3; continue } + if (head4 === '') { flushWord(); tokens.push({type: 'anchor', open: false}); i += 4; continue } + + const c = line[i] + const cc = line.charCodeAt(i) + + if (cc === 0x1B) { + // pre-existing ANSI escape - copy verbatim, zero visible width + const m = line.indexOf('m', i) + const end = (m < 0) ? line.length : m + 1 + buf += line.slice(i, end) + i = end + } + else if (cc === 0x84) { + // TSVM „u escape - copy verbatim, one visible char + const u = line.indexOf('u', i) + const end = (u < 0) ? line.length : u + 1 + buf += line.slice(i, end) + bufW += 1 + i = end + } + else if (c === ' ') { + flushWord() + tokens.push({type: 'sp'}) + i += 1 + } + else if (cc === 0x00AD) { + // soft hyphen: drop (no break-point handling for now) + i += 1 + } + else { + buf += c + bufW += 1 + i += 1 + } + } + flushWord() + return tokens +} + +// Build wrapped lines from a token stream then format each one according to alignment. +// Returns an array of strings, each exactly `width` visible chars wide (padded with +// trailing spaces) so the caller can blit them without further math. +function wrapAndAlign(tokens, width, alignment) { + const lines = [] // each: {tokens, indent, contentW} + let curTokens = [] + let curW = 0 + let curIndent = 0 + let nextIndent = 0 // indent the *next* flushed line should use + + const flushLine = () => { + // strip trailing soft spaces + while (curTokens.length > 0 && curTokens[curTokens.length - 1].type === 'sp') { + curTokens.pop() + curW -= 1 + } + lines.push({tokens: curTokens, indent: curIndent, contentW: curW}) + curTokens = [] + curW = 0 + curIndent = nextIndent + } + + for (const tok of tokens) { + if (tok.type === 'anchor') { + // anchor opens at the current visible column (accounting for indent) + if (tok.open) nextIndent = curIndent + curW + else nextIndent = 0 + continue + } + + if (tok.type === 'sp') { + // ignore leading soft spaces on a fresh line + if (curW === 0) continue + // hard wrap if the line is already at the right edge + if (curIndent + curW + 1 > width) { flushLine(); continue } + curTokens.push(tok) + curW += 1 + continue + } + + // word + const tw = tok.w + if (curIndent + curW + tw > width) { + flushLine() + // word too wide for the wrapped line: emit it on its own row (possibly clipped by terminal) + if (curIndent + tw > width) { + curTokens.push(tok) + curW += tw + flushLine() + continue + } + } + curTokens.push(tok) + curW += tw + } + + if (curTokens.length > 0 || lines.length === 0) flushLine() + + return lines.map((line, i) => formatLine(line, width, alignment, i === lines.length - 1)) +} + +function formatLine(line, totalWidth, alignment, isLast) { + if (line.tokens.length === 0) return ' '.repeat(totalWidth) + + const indent = ' '.repeat(line.indent) + const remaining = totalWidth - line.indent - line.contentW + const pad = (n) => (n > 0) ? ' '.repeat(n) : '' + const flatText = () => line.tokens.map(t => (t.type === 'sp') ? ' ' : t.text).join('') + + if (alignment === 'c') { + const left = remaining >> 1 + return indent + pad(left) + flatText() + pad(remaining - left) + } + if (alignment === 'r') return indent + pad(remaining) + flatText() + if (alignment === 'l') return indent + flatText() + pad(remaining) + + // justified: only expand spaces when there's slack and we're not on the + // last (or single) wrapped line + if (isLast || remaining <= 0) return indent + flatText() + pad(remaining) + + const spaceCount = line.tokens.reduce((n, t) => n + (t.type === 'sp' ? 1 : 0), 0) + if (spaceCount === 0) return indent + flatText() + pad(remaining) + + const baseExtra = (remaining / spaceCount) | 0 + let leftover = remaining - baseExtra * spaceCount + + let out = indent + for (const tok of line.tokens) { + if (tok.type === 'sp') { + const extra = baseExtra + (leftover > 0 ? 1 : 0) + if (leftover > 0) leftover -= 1 + out += ' '.repeat(1 + extra) + } else { + out += tok.text + } + } + return out +} + +// Process a single source line: peel a leading // alignment tag (if present), +// strip its matching close tag, then tokenise + wrap. +function typesetSourceLine(line, width) { + if (line.length === 0) return [' '.repeat(width)] + + let alignment = 'j' // justified default + const startMatch = line.match(/^<([crl])>/i) + if (startMatch) { + alignment = startMatch[1].toLowerCase() + line = line.slice(startMatch[0].length) + const closeRe = new RegExp(`$`, 'i') + line = line.replace(closeRe, '') + } + + const tokens = tokenise(line) + return wrapAndAlign(tokens, width, alignment) +} + +function typesetText(text, width) { + text = expandEntities(text) + const out = [] + for (const srcLine of text.split('\n')) { + for (const outLine of typesetSourceLine(srcLine, width)) out.push(outLine) + } + return out +} + +function typeset(text, customWidth) { + let typesetWidth = customWidth + if (typesetWidth === undefined) typesetWidth = _G.TAUT.HELPMSG_WIDTH + if (typesetWidth === undefined) { + const currentPosX = con.getyx()[1] // 1-indexed + typesetWidth = SCRW - currentPosX + 1 + } + return typesetText(text, typesetWidth) +} + +let helpMessages = [ // index: taut.js PANEL_NAMES + [helpJam, helpTimeline, helpCommon, helpNotation].join('\n\n'), + [helpCommon, helpNotation].join('\n\n'), // placeholder + [helpCommon, helpNotation].join('\n\n'), // placeholder + [helpCommon, helpNotation].join('\n\n'), // placeholder + [helpCommon, helpNotation].join('\n\n'), // placeholder + [helpCommon, helpNotation].join('\n\n'), // placeholder + [helpCommon, helpNotation].join('\n\n'), // placeholder +] + +help.MSG_BY_TABS = helpMessages.map(it => typeset(it)) +help.typeset = typeset +help.COL_TEXT = HELP_COL_TEXT +help.COL_EMPH = HELP_COL_EMPH + +if (!_G.TAUT.HELPMSG) _G.TAUT.HELPMSG=help; diff --git a/assets/disk0/tvdos/bin/tautfont.kra b/assets/disk0/tvdos/bin/tautfont.kra index 9f5ae6e..da2b0d5 100644 --- a/assets/disk0/tvdos/bin/tautfont.kra +++ b/assets/disk0/tvdos/bin/tautfont.kra @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7c84ddf490b0b39ba77cd6fb2dd066c5c468ddf45d7ae9d9553900c271c00c8b -size 139108 +oid sha256:63b3dd03d8be5cb761229421a2293d85e370ea535d04bc9b0795ab1effbef8e1 +size 138940 diff --git a/assets/disk0/tvdos/bin/tautfont_low.chr b/assets/disk0/tvdos/bin/tautfont_low.chr index be1e2fc5e680271da0b46a7e652d6353854ff1e5..2cf13ba03d07195fd7cf6663e9555784f64e017e 100644 GIT binary patch delta 15 WcmZqRZ{XkXjcqa)d)Z_Gb`bz7>ja|! delta 18 ZcmZqRZ{XkXjg5^%#w