diff --git a/zss/gadget/components/tape/common.ts b/zss/gadget/components/tape/common.ts index ab6a82d4..faf0bd57 100644 --- a/zss/gadget/components/tape/common.ts +++ b/zss/gadget/components/tape/common.ts @@ -3,8 +3,8 @@ import { createContext } from 'react' import { MODEM_SHARED_VALUE } from 'zss/device/modem' import { DRAW_CHAR_HEIGHT, DRAW_CHAR_WIDTH } from 'zss/gadget/data/types' import { MAYBE, ispresent } from 'zss/mapping/types' -import { COLOR } from 'zss/words/types' import { WRITE_TEXT_CONTEXT, textformatreadedges } from 'zss/words/textformat' +import { COLOR } from 'zss/words/types' // deco export const BKG_PTRN = 250 @@ -112,6 +112,13 @@ export function splitcoderows(code: string): EDITOR_CODE_ROW[] { }) } +export function findmaxwidthinrows(rows: EDITOR_CODE_ROW[]): number { + return rows.reduce((maxwidth, row) => { + const width = row.code.length + return width > maxwidth ? width : maxwidth + }, 0) +} + export function findcursorinrows(cursor: number, rows: EDITOR_CODE_ROW[]) { for (let i = 0; i < rows.length; ++i) { if (cursor <= rows[i].end) { diff --git a/zss/gadget/components/tape/editor.tsx b/zss/gadget/components/tape/editor.tsx index fcdf8536..b66a25bc 100644 --- a/zss/gadget/components/tape/editor.tsx +++ b/zss/gadget/components/tape/editor.tsx @@ -8,7 +8,12 @@ import { ispresent } from 'zss/mapping/types' import { textformatreadedges } from 'zss/words/textformat' import { useShallow } from 'zustand/react/shallow' -import { findcursorinrows, sharedtosynced, splitcoderows } from './common' +import { + findcursorinrows, + findmaxwidthinrows, + sharedtosynced, + splitcoderows, +} from './common' import { BackPlate } from './elements/backplate' import { EditorFrame } from './elements/editorframe' import { EditorInput } from './elements/editorinput' @@ -36,40 +41,75 @@ export function TapeEditor() { const strvalue = ispresent(value) ? value.toJSON() : '' const rows = splitcoderows(strvalue) const ycursor = findcursorinrows(tapeeditor.cursor, rows) + const xcursor = tapeeditor.cursor - rows[ycursor].start + // figure out longest line of code + const maxwidth = findmaxwidthinrows(rows) // measure edges once const props = { + rows, + xcursor, + ycursor, + codepage, xoffset: tapeeditor.xscroll, yoffset: tapeeditor.yscroll, - codepage, - ycursor, - rows, } - const maxscroll = rows.length - 4 + const chunkstep = 32 + const xmaxscroll = (Math.round(maxwidth / chunkstep) + 1) * chunkstep + const ymaxscroll = rows.length + useEffect(() => { + let xscroll = tapeeditor.xscroll + const xdelta = xcursor - xscroll + const xwidth = edge.width - 3 + + let xstep = Math.round(clamp(Math.abs(xdelta) * 0.5, 1, chunkstep)) + if (xstep < 8) { + xstep = 1 + } + if (xdelta > xwidth - 8) { + xscroll += xstep + } + if (xdelta < 8) { + xscroll -= xstep + } + let yscroll = tapeeditor.yscroll - const delta = ycursor - tapeeditor.yscroll - const bottom = edge.height - 8 - const maxstep = Math.round(bottom * 0.75) - let step = clamp(Math.round(Math.abs(delta) * 0.25), 1, maxstep) - if (step < 8) { - step = 1 + const ydelta = ycursor - yscroll + const yheight = edge.height - 4 + const ymaxstep = Math.round(yheight * 0.5) + + let ystep = Math.round(clamp(Math.abs(ydelta) * 0.25, 1, ymaxstep)) + if (ystep < 8) { + ystep = 1 } - if (delta > bottom) { - yscroll += step + if (ydelta > yheight - 4) { + yscroll += ystep } - if (delta < 4) { - yscroll -= step + if (ydelta < 4) { + yscroll -= ystep } + + // update setTimeout( () => useTapeEditor.setState({ - yscroll: Math.round(clamp(yscroll, 0, maxscroll)), + xscroll: Math.round(clamp(xscroll, 0, xmaxscroll)), + yscroll: Math.round(clamp(yscroll, 0, ymaxscroll)), }), 16, ) - }, [ycursor, tapeeditor.yscroll, maxscroll, edge.height]) + }, [ + xcursor, + xmaxscroll, + tapeeditor.xscroll, + ycursor, + ymaxscroll, + tapeeditor.yscroll, + edge.width, + edge.height, + ]) return ( <> diff --git a/zss/gadget/components/tape/elements/editorinput.tsx b/zss/gadget/components/tape/elements/editorinput.tsx index 3e6a289c..293d60e4 100644 --- a/zss/gadget/components/tape/elements/editorinput.tsx +++ b/zss/gadget/components/tape/elements/editorinput.tsx @@ -18,14 +18,18 @@ import { UserInput, modsfromevent } from '../../userinput' import { EDITOR_CODE_ROW, sharedtosynced } from '../common' type TextinputProps = { + xcursor: number ycursor: number + xoffset: number yoffset: number rows: EDITOR_CODE_ROW[] codepage: MAYBE } export function EditorInput({ + xcursor, ycursor, + xoffset, yoffset, rows, codepage, @@ -41,11 +45,8 @@ export function EditorInput({ const strvalue = ispresent(value) ? value.toJSON() : '' const rowsend = rows.length - 1 - // translate index to x, y - const xcursor = tapeeditor.cursor - rows[ycursor].start - // draw cursor - const xblink = xcursor + 1 + const xblink = xcursor + 1 - xoffset const yblink = ycursor + 2 - yoffset if (ispresent(codepage)) { const moving = @@ -53,7 +54,19 @@ export function EditorInput({ if (blink || moving) { const x = edge.left + xblink const y = edge.top + yblink - applystrtoindex(x + y * context.width, String.fromCharCode(221), context) + // visibility clip + if ( + y > edge.top + 1 && + y < edge.bottom && + x > edge.left && + x < edge.right + ) { + applystrtoindex( + x + y * context.width, + String.fromCharCode(221), + context, + ) + } } } blinkdelta.current = { x: xblink, y: yblink } diff --git a/zss/gadget/components/tape/elements/editorrows.tsx b/zss/gadget/components/tape/elements/editorrows.tsx index 82a67c8e..b2777d07 100644 --- a/zss/gadget/components/tape/elements/editorrows.tsx +++ b/zss/gadget/components/tape/elements/editorrows.tsx @@ -61,8 +61,8 @@ export function EditorRows({ } // render lines - const left = edge.left + 1 - xoffset - setupeditoritem(false, false, 0, -yoffset, context, 1, 2, 1) + const left = edge.left + 1 + setupeditoritem(false, false, -xoffset, -yoffset, context, 1, 2, 1) for (let i = 0; i < rows.length; ++i) { if (context.y <= edge.top + 1) { ++context.y @@ -75,24 +75,32 @@ export function EditorRows({ const text = row.code.replaceAll('\n', '') // render - context.x = left + context.x = left - xoffset context.iseven = context.y % 2 === 0 context.active.bg = active ? BG_ACTIVE : BG context.disablewrap = true - writeplaintext(`${text}`, context, false) + writeplaintext(`${text} `, context, false) // render selection if (hasselection && row.start <= ii2 && row.end >= ii1) { - const index = left + context.y * context.width - const start = Math.max(0, ii1 - row.start) - const end = Math.min(row.end - row.start, ii2 - row.start) - applycolortoindexes( - index + start, - index + end, - FG_SELECTED, - BG_SELECTED, - context, - ) + const maybestart = Math.max(row.start, ii1) - row.start - xoffset + const maybeend = Math.min(row.end, ii2) - row.start - xoffset + + // start of drawn line + const right = edge.width - 3 + const index = 1 + context.y * context.width + const start = Math.max(0, maybestart) + const end = Math.min(right, maybeend) + + if (start <= right && end >= left) { + applycolortoindexes( + index + start, + index + end, + FG_SELECTED, + BG_SELECTED, + context, + ) + } } // next line