commit
78d32f7fdc
@ -1,397 +1,497 @@ |
|||||||
const { mk } = require('../utils') |
const { getColor } = require('./themes') |
||||||
|
const { |
||||||
|
ATTR_FG, |
||||||
|
ATTR_BG, |
||||||
|
ATTR_BOLD, |
||||||
|
ATTR_UNDERLINE, |
||||||
|
ATTR_BLINK, |
||||||
|
ATTR_ITALIC, |
||||||
|
ATTR_STRIKE, |
||||||
|
ATTR_OVERLINE, |
||||||
|
ATTR_FAINT, |
||||||
|
ATTR_FRAKTUR |
||||||
|
} = require('./screen_attr_bits') |
||||||
|
|
||||||
module.exports = function attachDebugger (screen, connection) { |
module.exports = function attachDebugger (screen, connection) { |
||||||
const debugCanvas = mk('canvas') |
const debugCanvas = document.createElement('canvas') |
||||||
|
debugCanvas.classList.add('debug-canvas') |
||||||
const ctx = debugCanvas.getContext('2d') |
const ctx = debugCanvas.getContext('2d') |
||||||
|
|
||||||
debugCanvas.classList.add('debug-canvas') |
const toolbar = document.createElement('div') |
||||||
|
toolbar.classList.add('debug-toolbar') |
||||||
|
|
||||||
|
const tooltip = document.createElement('div') |
||||||
|
tooltip.classList.add('debug-tooltip') |
||||||
|
tooltip.classList.add('hidden') |
||||||
|
|
||||||
let mouseHoverCell = null |
let updateTooltip |
||||||
let updateToolbar |
let updateToolbar |
||||||
|
|
||||||
let onMouseMove = e => { |
let selectedCell = null |
||||||
mouseHoverCell = screen.layout.screenToGrid(e.offsetX, e.offsetY) |
let mousePosition = null |
||||||
startDrawing() |
const onMouseMove = (e) => { |
||||||
updateToolbar() |
if (e.target !== screen.layout.canvas) { |
||||||
|
selectedCell = null |
||||||
|
return |
||||||
} |
} |
||||||
let onMouseOut = () => (mouseHoverCell = null) |
selectedCell = screen.layout.screenToGrid(e.offsetX, e.offsetY) |
||||||
|
mousePosition = [e.offsetX, e.offsetY] |
||||||
let addCanvas = function () { |
updateTooltip() |
||||||
if (!debugCanvas.parentNode) { |
|
||||||
screen.layout.canvas.parentNode.appendChild(debugCanvas) |
|
||||||
screen.layout.canvas.addEventListener('mousemove', onMouseMove) |
|
||||||
screen.layout.canvas.addEventListener('mouseout', onMouseOut) |
|
||||||
} |
} |
||||||
|
const onMouseOut = (e) => { |
||||||
|
selectedCell = null |
||||||
|
tooltip.classList.add('hidden') |
||||||
} |
} |
||||||
let removeCanvas = function () { |
|
||||||
if (debugCanvas.parentNode) { |
const updateCanvasSize = function () { |
||||||
|
let { width, height, devicePixelRatio } = screen.layout.window |
||||||
|
let cellSize = screen.layout.getCellSize() |
||||||
|
let padding = Math.round(screen.layout._padding) |
||||||
|
debugCanvas.width = (width * cellSize.width + 2 * padding) * devicePixelRatio |
||||||
|
debugCanvas.height = (height * cellSize.height + 2 * padding) * devicePixelRatio |
||||||
|
debugCanvas.style.width = `${width * cellSize.width + 2 * screen.layout._padding}px` |
||||||
|
debugCanvas.style.height = `${height * cellSize.height + 2 * screen.layout._padding}px` |
||||||
|
} |
||||||
|
|
||||||
|
let startDrawLoop |
||||||
|
let screenAttached = false |
||||||
|
let eventNode |
||||||
|
const setScreenAttached = function (attached) { |
||||||
|
if (attached && !debugCanvas.parentNode) { |
||||||
|
screen.layout.canvas.parentNode.appendChild(debugCanvas) |
||||||
|
eventNode = debugCanvas.parentNode |
||||||
|
eventNode.addEventListener('mousemove', onMouseMove) |
||||||
|
eventNode.addEventListener('mouseout', onMouseOut) |
||||||
|
screen.layout.on('size-update', updateCanvasSize) |
||||||
|
updateCanvasSize() |
||||||
|
screenAttached = true |
||||||
|
startDrawLoop() |
||||||
|
} else if (!attached && debugCanvas.parentNode) { |
||||||
debugCanvas.parentNode.removeChild(debugCanvas) |
debugCanvas.parentNode.removeChild(debugCanvas) |
||||||
screen.layout.canvas.removeEventListener('mousemove', onMouseMove) |
eventNode.removeEventListener('mousemove', onMouseMove) |
||||||
screen.layout.canvas.removeEventListener('mouseout', onMouseOut) |
eventNode.removeEventListener('mouseout', onMouseOut) |
||||||
onMouseOut() |
screen.layout.removeListener('size-update', updateCanvasSize) |
||||||
|
screenAttached = false |
||||||
} |
} |
||||||
} |
} |
||||||
let updateCanvasSize = function () { |
|
||||||
let { width, height, devicePixelRatio } = screen.layout.window |
const setToolbarAttached = function (attached) { |
||||||
let cellSize = screen.layout.getCellSize() |
if (attached && !toolbar.parentNode) { |
||||||
debugCanvas.width = width * cellSize.width * devicePixelRatio |
screen.layout.canvas.parentNode.appendChild(toolbar) |
||||||
debugCanvas.height = height * cellSize.height * devicePixelRatio |
screen.layout.canvas.parentNode.appendChild(tooltip) |
||||||
debugCanvas.style.width = `${width * cellSize.width}px` |
updateToolbar() |
||||||
debugCanvas.style.height = `${height * cellSize.height}px` |
} else if (!attached && toolbar.parentNode) { |
||||||
|
screen.layout.canvas.parentNode.removeChild(toolbar) |
||||||
|
screen.layout.canvas.parentNode.removeChild(tooltip) |
||||||
|
} |
||||||
} |
} |
||||||
|
|
||||||
let drawInfo = mk('div') |
screen.on('update-window:debug', enabled => { |
||||||
drawInfo.classList.add('draw-info') |
setToolbarAttached(enabled) |
||||||
|
}) |
||||||
|
|
||||||
let startTime, endTime, lastReason |
screen.layout.on('update-window:debug', enabled => { |
||||||
let cells = new Map() |
setScreenAttached(enabled) |
||||||
let clippedRects = [] |
}) |
||||||
let updateFrames = [] |
|
||||||
|
|
||||||
let startDrawing |
let drawData = { |
||||||
|
reason: '', |
||||||
|
showUpdates: false, |
||||||
|
startTime: 0, |
||||||
|
endTime: 0, |
||||||
|
clipped: [], |
||||||
|
frames: [], |
||||||
|
cells: new Map(), |
||||||
|
scrollRegion: null |
||||||
|
} |
||||||
|
|
||||||
screen._debug = screen.layout.renderer._debug = { |
screen._debug = screen.layout.renderer._debug = { |
||||||
drawStart (reason) { |
drawStart (reason) { |
||||||
lastReason = reason |
drawData.reason = reason |
||||||
startTime = Date.now() |
drawData.startTime = window.performance.now() |
||||||
clippedRects = [] |
|
||||||
}, |
}, |
||||||
drawEnd () { |
drawEnd () { |
||||||
endTime = Date.now() |
drawData.endTime = window.performance.now() |
||||||
drawInfo.textContent = `Draw: ${lastReason} (${(endTime - startTime)} ms), fancy gfx=${screen.layout.renderer.graphics}` |
|
||||||
startDrawing() |
|
||||||
}, |
}, |
||||||
setCell (cell, flags) { |
setCell (cell, flags) { |
||||||
cells.set(cell, [flags, Date.now()]) |
drawData.cells.set(cell, [flags, window.performance.now()]) |
||||||
}, |
|
||||||
clipRect (...args) { |
|
||||||
clippedRects.push(args) |
|
||||||
}, |
}, |
||||||
pushFrame (frame) { |
pushFrame (frame) { |
||||||
frame.push(Date.now()) |
drawData.frames.push([...frame, window.performance.now()]) |
||||||
updateFrames.push(frame) |
} |
||||||
startDrawing() |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
let clipPattern |
|
||||||
{ |
|
||||||
let patternCanvas = document.createElement('canvas') |
|
||||||
patternCanvas.width = patternCanvas.height = 12 |
|
||||||
let pctx = patternCanvas.getContext('2d') |
|
||||||
pctx.lineWidth = 1 |
|
||||||
pctx.strokeStyle = '#00f' |
|
||||||
pctx.beginPath() |
|
||||||
pctx.moveTo(0, 0) |
|
||||||
pctx.lineTo(0 - 4, 12) |
|
||||||
pctx.moveTo(4, 0) |
|
||||||
pctx.lineTo(4 - 4, 12) |
|
||||||
pctx.moveTo(8, 0) |
|
||||||
pctx.lineTo(8 - 4, 12) |
|
||||||
pctx.moveTo(12, 0) |
|
||||||
pctx.lineTo(12 - 4, 12) |
|
||||||
pctx.moveTo(16, 0) |
|
||||||
pctx.lineTo(16 - 4, 12) |
|
||||||
pctx.stroke() |
|
||||||
clipPattern = ctx.createPattern(patternCanvas, 'repeat') |
|
||||||
} |
} |
||||||
|
|
||||||
let isDrawing = false |
let isDrawing = false |
||||||
let lastDrawTime = 0 |
|
||||||
let t = 0 |
|
||||||
|
|
||||||
let drawLoop = function () { |
let drawLoop = function () { |
||||||
if (isDrawing) window.requestAnimationFrame(drawLoop) |
if (screenAttached) window.requestAnimationFrame(drawLoop) |
||||||
|
else isDrawing = false |
||||||
|
|
||||||
let dt = (Date.now() - lastDrawTime) / 1000 |
let now = window.performance.now() |
||||||
lastDrawTime = Date.now() |
|
||||||
t += dt |
|
||||||
|
|
||||||
let { devicePixelRatio, width, height } = screen.layout.window |
let { width, height, devicePixelRatio } = screen.layout.window |
||||||
let { width: cellWidth, height: cellHeight } = screen.layout.getCellSize() |
let padding = Math.round(screen.layout._padding) |
||||||
let screenLength = width * height |
let cellSize = screen.layout.getCellSize() |
||||||
let now = Date.now() |
|
||||||
|
|
||||||
ctx.setTransform(devicePixelRatio, 0, 0, devicePixelRatio, 0, 0) |
ctx.setTransform(devicePixelRatio, 0, 0, devicePixelRatio, 0, 0) |
||||||
ctx.clearRect(0, 0, width * cellWidth, height * cellHeight) |
ctx.clearRect(0, 0, width * cellSize.width + 2 * padding, height * cellSize.height + 2 * padding) |
||||||
|
ctx.translate(padding, padding) |
||||||
|
|
||||||
let activeCells = 0 |
ctx.lineWidth = 2 |
||||||
for (let cell = 0; cell < screenLength; cell++) { |
ctx.lineJoin = 'round' |
||||||
|
|
||||||
|
if (drawData.showUpdates) { |
||||||
|
const cells = drawData.cells |
||||||
|
for (let cell = 0; cell < width * height; cell++) { |
||||||
|
// cell does not exist or has no flags set
|
||||||
if (!cells.has(cell) || cells.get(cell)[0] === 0) continue |
if (!cells.has(cell) || cells.get(cell)[0] === 0) continue |
||||||
|
|
||||||
let [flags, timestamp] = cells.get(cell) |
const [flags, timestamp] = cells.get(cell) |
||||||
let elapsedTime = (now - timestamp) / 1000 |
let elapsedTime = (now - timestamp) / 1000 |
||||||
|
|
||||||
if (elapsedTime > 1) continue |
if (elapsedTime > 1) { |
||||||
|
cells.delete(cell) |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
activeCells++ |
|
||||||
ctx.globalAlpha = 0.5 * Math.max(0, 1 - elapsedTime) |
ctx.globalAlpha = 0.5 * Math.max(0, 1 - elapsedTime) |
||||||
|
|
||||||
let x = cell % width |
let x = cell % width |
||||||
let y = Math.floor(cell / width) |
let y = Math.floor(cell / width) |
||||||
|
|
||||||
if (flags & 1) { |
|
||||||
// redrawn
|
|
||||||
ctx.fillStyle = '#f0f' |
|
||||||
} |
|
||||||
if (flags & 2) { |
if (flags & 2) { |
||||||
// updated
|
// updated
|
||||||
ctx.fillStyle = '#0f0' |
ctx.fillStyle = '#0f0' |
||||||
|
} else if (flags & 1) { |
||||||
|
// redrawn
|
||||||
|
ctx.fillStyle = '#f0f' |
||||||
} |
} |
||||||
|
|
||||||
ctx.fillRect(x * cellWidth, y * cellHeight, cellWidth, cellHeight) |
if (!(flags & 4)) { |
||||||
|
// outside a clipped region
|
||||||
|
ctx.fillStyle = '#0ff' |
||||||
|
} |
||||||
|
|
||||||
|
ctx.fillRect(x * cellSize.width, y * cellSize.height, cellSize.width, cellSize.height) |
||||||
|
|
||||||
if (flags & 4) { |
if (flags & 8) { |
||||||
// wide cell
|
// wide cell
|
||||||
ctx.lineWidth = 2 |
|
||||||
ctx.strokeStyle = '#f00' |
ctx.strokeStyle = '#f00' |
||||||
ctx.strokeRect(x * cellWidth, y * cellHeight, cellWidth, cellHeight) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
if (clippedRects.length) { |
|
||||||
ctx.globalAlpha = 0.5 |
|
||||||
ctx.beginPath() |
ctx.beginPath() |
||||||
|
ctx.moveTo(x * cellSize.width, (y + 1) * cellSize.height) |
||||||
for (let rect of clippedRects) { |
ctx.lineTo((x + 1) * cellSize.width, (y + 1) * cellSize.height) |
||||||
ctx.rect(...rect) |
ctx.stroke() |
||||||
} |
} |
||||||
|
|
||||||
ctx.fillStyle = clipPattern |
|
||||||
ctx.fill() |
|
||||||
} |
} |
||||||
|
|
||||||
let didDrawUpdateFrames = false |
|
||||||
if (updateFrames.length) { |
|
||||||
let framesToDelete = [] |
let framesToDelete = [] |
||||||
for (let frame of updateFrames) { |
for (let frame of drawData.frames) { |
||||||
let time = frame[4] |
let timestamp = frame[4] |
||||||
let elapsed = Date.now() - time |
let elapsedTime = (now - timestamp) / 1000 |
||||||
if (elapsed > 1000) framesToDelete.push(frame) |
if (elapsedTime > 1) framesToDelete.push(frame) |
||||||
else { |
else { |
||||||
didDrawUpdateFrames = true |
ctx.globalAlpha = 1 - elapsedTime |
||||||
ctx.globalAlpha = 1 - elapsed / 1000 |
|
||||||
ctx.strokeStyle = '#ff0' |
ctx.strokeStyle = '#ff0' |
||||||
ctx.lineWidth = 2 |
ctx.strokeRect(frame[0] * cellSize.width, frame[1] * cellSize.height, |
||||||
ctx.strokeRect(frame[0] * cellWidth, frame[1] * cellHeight, frame[2] * cellWidth, frame[3] * cellHeight) |
frame[2] * cellSize.width, frame[3] * cellSize.height) |
||||||
} |
} |
||||||
} |
} |
||||||
for (let frame of framesToDelete) { |
for (let frame of framesToDelete) { |
||||||
updateFrames.splice(updateFrames.indexOf(frame), 1) |
drawData.frames.splice(drawData.frames.indexOf(frame), 1) |
||||||
} |
} |
||||||
} |
} |
||||||
|
|
||||||
if (mouseHoverCell) { |
if (selectedCell !== null) { |
||||||
|
let [x, y] = selectedCell |
||||||
|
|
||||||
ctx.save() |
ctx.save() |
||||||
|
ctx.globalAlpha = 0.5 |
||||||
|
ctx.lineWidth = 1 |
||||||
|
|
||||||
|
// draw X line
|
||||||
|
ctx.beginPath() |
||||||
|
ctx.moveTo(0, y * cellSize.height) |
||||||
|
ctx.lineTo(x * cellSize.width, y * cellSize.height) |
||||||
|
ctx.strokeStyle = '#f00' |
||||||
|
ctx.setLineDash([cellSize.width]) |
||||||
|
ctx.stroke() |
||||||
|
|
||||||
|
// draw Y line
|
||||||
|
ctx.beginPath() |
||||||
|
ctx.moveTo(x * cellSize.width, 0) |
||||||
|
ctx.lineTo(x * cellSize.width, y * cellSize.height) |
||||||
|
ctx.strokeStyle = '#0f0' |
||||||
|
ctx.setLineDash([cellSize.height]) |
||||||
|
ctx.stroke() |
||||||
|
|
||||||
ctx.globalAlpha = 1 |
ctx.globalAlpha = 1 |
||||||
ctx.lineWidth = 1 + 0.5 * Math.sin(t * 10) |
ctx.lineWidth = 1 + 0.5 * Math.sin((now / 1000) * 10) |
||||||
ctx.strokeStyle = '#fff' |
ctx.strokeStyle = '#fff' |
||||||
ctx.lineJoin = 'round' |
ctx.lineJoin = 'round' |
||||||
ctx.setLineDash([2, 2]) |
ctx.setLineDash([2, 2]) |
||||||
ctx.lineDashOffset = t * 10 |
ctx.lineDashOffset = (now / 1000) * 10 |
||||||
ctx.strokeRect(mouseHoverCell[0] * cellWidth, mouseHoverCell[1] * cellHeight, cellWidth, cellHeight) |
ctx.strokeRect(x * cellSize.width, y * cellSize.height, cellSize.width, cellSize.height) |
||||||
ctx.lineDashOffset += 2 |
ctx.lineDashOffset += 2 |
||||||
ctx.strokeStyle = '#000' |
ctx.strokeStyle = '#000' |
||||||
ctx.strokeRect(mouseHoverCell[0] * cellWidth, mouseHoverCell[1] * cellHeight, cellWidth, cellHeight) |
ctx.strokeRect(x * cellSize.width, y * cellSize.height, cellSize.width, cellSize.height) |
||||||
ctx.restore() |
ctx.restore() |
||||||
} |
} |
||||||
|
|
||||||
if (activeCells === 0 && !mouseHoverCell && !didDrawUpdateFrames) { |
if (drawData.scrollRegion !== null) { |
||||||
isDrawing = false |
let [start, end] = drawData.scrollRegion |
||||||
removeCanvas() |
|
||||||
|
ctx.save() |
||||||
|
ctx.globalAlpha = 1 |
||||||
|
ctx.strokeStyle = '#00f' |
||||||
|
ctx.lineWidth = 2 |
||||||
|
ctx.setLineDash([2, 2]) |
||||||
|
|
||||||
|
ctx.beginPath() |
||||||
|
ctx.moveTo(0, start * cellSize.height) |
||||||
|
ctx.lineTo(width * cellSize.width, start * cellSize.height) |
||||||
|
ctx.stroke() |
||||||
|
|
||||||
|
ctx.beginPath() |
||||||
|
ctx.moveTo(0, (end + 1) * cellSize.height) |
||||||
|
ctx.lineTo(width * cellSize.width, (end + 1) * cellSize.height) |
||||||
|
ctx.stroke() |
||||||
|
|
||||||
|
ctx.restore() |
||||||
} |
} |
||||||
} |
} |
||||||
|
startDrawLoop = function () { |
||||||
startDrawing = function () { |
|
||||||
if (isDrawing) return |
if (isDrawing) return |
||||||
addCanvas() |
|
||||||
updateCanvasSize() |
|
||||||
isDrawing = true |
isDrawing = true |
||||||
lastDrawTime = Date.now() |
|
||||||
drawLoop() |
drawLoop() |
||||||
} |
} |
||||||
|
|
||||||
// debug toolbar
|
let pad2 = i => ('00' + i.toString()).substr(-2) |
||||||
const toolbar = mk('div') |
let formatColor = color => color < 256 |
||||||
toolbar.classList.add('debug-toolbar') |
? color.toString() |
||||||
let toolbarAttached = false |
: '#' + pad2(color >> 16) + pad2((color >> 8) & 0xFF) + pad2(color & 0xFF) |
||||||
|
|
||||||
|
let makeSpan = (text, styles) => { |
||||||
|
let span = document.createElement('span') |
||||||
|
span.textContent = text |
||||||
|
Object.assign(span.style, styles || {}) |
||||||
|
return span |
||||||
|
} |
||||||
|
let formatAttributes = (target, attrs) => { |
||||||
|
if (attrs & ATTR_FG) target.appendChild(makeSpan('HasFG')) |
||||||
|
if (attrs & ATTR_BG) target.appendChild(makeSpan('HasBG')) |
||||||
|
if (attrs & ATTR_BOLD) target.appendChild(makeSpan('Bold', { fontWeight: 'bold' })) |
||||||
|
if (attrs & ATTR_UNDERLINE) target.appendChild(makeSpan('Uline', { textDecoration: 'underline' })) |
||||||
|
if (attrs & ATTR_BLINK) target.appendChild(makeSpan('Blink')) |
||||||
|
if (attrs & ATTR_ITALIC) target.appendChild(makeSpan('Italic', { fontStyle: 'italic' })) |
||||||
|
if (attrs & ATTR_STRIKE) target.appendChild(makeSpan('Strike', { textDecoration: 'line-through' })) |
||||||
|
if (attrs & ATTR_OVERLINE) target.appendChild(makeSpan('Oline', { textDecoration: 'overline' })) |
||||||
|
if (attrs & ATTR_FAINT) target.appendChild(makeSpan('Faint', { opacity: 0.5 })) |
||||||
|
if (attrs & ATTR_FRAKTUR) target.appendChild(makeSpan('Fraktur')) |
||||||
|
} |
||||||
|
|
||||||
|
// tooltip
|
||||||
|
updateTooltip = function () { |
||||||
|
tooltip.classList.remove('hidden') |
||||||
|
tooltip.innerHTML = '' |
||||||
|
let cell = selectedCell[1] * screen.window.width + selectedCell[0] |
||||||
|
if (!screen.screen[cell]) return |
||||||
|
|
||||||
|
let foreground = document.createElement('span') |
||||||
|
foreground.textContent = formatColor(screen.screenFG[cell]) |
||||||
|
let preview = document.createElement('span') |
||||||
|
preview.textContent = ' ●' |
||||||
|
preview.style.color = getColor(screen.screenFG[cell], screen.layout.renderer.palette) |
||||||
|
foreground.appendChild(preview) |
||||||
|
|
||||||
|
let background = document.createElement('span') |
||||||
|
background.textContent = formatColor(screen.screenBG[cell]) |
||||||
|
let bgPreview = document.createElement('span') |
||||||
|
bgPreview.textContent = ' ●' |
||||||
|
bgPreview.style.color = getColor(screen.screenBG[cell], screen.layout.renderer.palette) |
||||||
|
background.appendChild(bgPreview) |
||||||
|
|
||||||
|
let character = screen.screen[cell] |
||||||
|
let codePoint = character.codePointAt(0) |
||||||
|
let formattedCodePoint = codePoint.toString(16).length <= 4 |
||||||
|
? `0000${codePoint.toString(16)}`.substr(-4) |
||||||
|
: codePoint.toString(16) |
||||||
|
|
||||||
|
let attributes = document.createElement('span') |
||||||
|
attributes.classList.add('attributes') |
||||||
|
formatAttributes(attributes, screen.screenAttrs[cell]) |
||||||
|
|
||||||
|
let data = { |
||||||
|
Cell: `col ${selectedCell[0] + 1}, ln ${selectedCell[1] + 1} (${cell})`, |
||||||
|
Foreground: foreground, |
||||||
|
Background: background, |
||||||
|
Character: `U+${formattedCodePoint}`, |
||||||
|
Attributes: attributes |
||||||
|
} |
||||||
|
|
||||||
|
let table = document.createElement('table') |
||||||
|
|
||||||
|
for (let name in data) { |
||||||
|
let row = document.createElement('tr') |
||||||
|
let label = document.createElement('td') |
||||||
|
label.appendChild(new window.Text(name)) |
||||||
|
label.classList.add('label') |
||||||
|
|
||||||
|
let value = document.createElement('td') |
||||||
|
value.appendChild(typeof data[name] === 'string' ? new window.Text(data[name]) : data[name]) |
||||||
|
value.classList.add('value') |
||||||
|
|
||||||
|
row.appendChild(label) |
||||||
|
row.appendChild(value) |
||||||
|
table.appendChild(row) |
||||||
|
} |
||||||
|
|
||||||
|
tooltip.appendChild(table) |
||||||
|
|
||||||
const heartbeat = mk('div') |
let cellSize = screen.layout.getCellSize() |
||||||
heartbeat.classList.add('heartbeat') |
// add 3 to the position because for some reason the corner is off
|
||||||
heartbeat.textContent = '❤' |
let posX = (selectedCell[0] + 1) * cellSize.width + 3 |
||||||
toolbar.appendChild(heartbeat) |
let posY = (selectedCell[1] + 1) * cellSize.height + 3 |
||||||
|
tooltip.style.transform = `translate(${posX}px, ${posY}px)` |
||||||
const dataDisplay = mk('div') |
} |
||||||
dataDisplay.classList.add('data-display') |
|
||||||
toolbar.appendChild(dataDisplay) |
|
||||||
|
|
||||||
const internalDisplay = mk('div') |
|
||||||
internalDisplay.classList.add('internal-display') |
|
||||||
toolbar.appendChild(internalDisplay) |
|
||||||
|
|
||||||
toolbar.appendChild(drawInfo) |
|
||||||
|
|
||||||
const buttons = mk('div') |
let toolbarData = null |
||||||
buttons.classList.add('toolbar-buttons') |
let toolbarNodes = {} |
||||||
toolbar.appendChild(buttons) |
const initToolbar = function () { |
||||||
|
if (toolbarData) return |
||||||
|
|
||||||
// heartbeat
|
let showUpdates = document.createElement('input') |
||||||
connection.on('heartbeat', () => { |
showUpdates.type = 'checkbox' |
||||||
heartbeat.classList.remove('beat') |
showUpdates.addEventListener('change', e => { |
||||||
window.requestAnimationFrame(() => { |
drawData.showUpdates = showUpdates.checked |
||||||
heartbeat.classList.add('beat') |
|
||||||
}) |
}) |
||||||
|
|
||||||
|
let fancyGraphics = document.createElement('input') |
||||||
|
fancyGraphics.type = 'checkbox' |
||||||
|
fancyGraphics.value = !!screen.layout.window.graphics |
||||||
|
fancyGraphics.addEventListener('change', e => { |
||||||
|
screen.layout.window.graphics = +fancyGraphics.checked |
||||||
}) |
}) |
||||||
|
|
||||||
{ |
toolbarData = { |
||||||
const redraw = mk('button') |
cursor: { |
||||||
redraw.textContent = 'Redraw' |
title: 'Cursor', |
||||||
redraw.addEventListener('click', e => { |
Position: '', |
||||||
|
Style: '', |
||||||
|
Visible: true, |
||||||
|
Hanging: false |
||||||
|
}, |
||||||
|
internal: { |
||||||
|
Flags: '', |
||||||
|
'Cursor Attributes': '', |
||||||
|
'Code Page': '', |
||||||
|
Heap: 0, |
||||||
|
Clients: 0 |
||||||
|
}, |
||||||
|
drawing: { |
||||||
|
title: 'Drawing', |
||||||
|
'Last Update': '', |
||||||
|
'Show Updates': showUpdates, |
||||||
|
'Fancy Graphics': fancyGraphics, |
||||||
|
'Redraw Screen': () => { |
||||||
screen.layout.renderer.resetDrawn() |
screen.layout.renderer.resetDrawn() |
||||||
screen.layout.renderer.draw('debug-redraw') |
screen.layout.renderer.draw('debug-redraw') |
||||||
}) |
|
||||||
buttons.appendChild(redraw) |
|
||||||
|
|
||||||
const fancyGraphics = mk('button') |
|
||||||
fancyGraphics.textContent = 'Toggle Fancy Graphics' |
|
||||||
fancyGraphics.addEventListener('click', e => { |
|
||||||
screen.layout.renderer.graphics = +!screen.layout.renderer.graphics |
|
||||||
screen.layout.renderer.draw('set-graphics') |
|
||||||
}) |
|
||||||
buttons.appendChild(fancyGraphics) |
|
||||||
} |
} |
||||||
|
|
||||||
const attachToolbar = function () { |
|
||||||
screen.layout.canvas.parentNode.appendChild(toolbar) |
|
||||||
} |
} |
||||||
const detachToolbar = function () { |
|
||||||
toolbar.parentNode.removeChild(toolbar) |
|
||||||
} |
} |
||||||
|
|
||||||
screen.on('update-window:debug', debug => { |
for (let i in toolbarData) { |
||||||
if (debug !== toolbarAttached) { |
let group = toolbarData[i] |
||||||
toolbarAttached = debug |
let table = document.createElement('table') |
||||||
if (debug) attachToolbar() |
table.classList.add('toolbar-group') |
||||||
else { |
|
||||||
detachToolbar() |
toolbarNodes[i] = {} |
||||||
removeCanvas() |
|
||||||
} |
for (let key in group) { |
||||||
} |
let item = document.createElement('tr') |
||||||
}) |
let name = document.createElement('td') |
||||||
|
name.classList.add('name') |
||||||
|
let value = document.createElement('td') |
||||||
|
value.classList.add('value') |
||||||
|
|
||||||
|
toolbarNodes[i][key] = { name, value } |
||||||
|
|
||||||
const displayCellAttrs = attrs => { |
if (key === 'title') { |
||||||
let result = attrs.toString(16) |
name.textContent = group[key] |
||||||
if (attrs & 1 || attrs & 2) { |
name.classList.add('title') |
||||||
result += ':has(' |
|
||||||
if (attrs & 1) result += 'fg' |
|
||||||
if (attrs & 2) result += (attrs & 1 ? ',' : '') + 'bg' |
|
||||||
result += ')' |
|
||||||
} |
|
||||||
let attributes = [] |
|
||||||
if (attrs & (1 << 2)) attributes.push('\\[bold]bold\\()') |
|
||||||
if (attrs & (1 << 3)) attributes.push('\\[underline]underln\\()') |
|
||||||
if (attrs & (1 << 4)) attributes.push('\\[invert]invert\\()') |
|
||||||
if (attrs & (1 << 5)) attributes.push('blink') |
|
||||||
if (attrs & (1 << 6)) attributes.push('\\[italic]italic\\()') |
|
||||||
if (attrs & (1 << 7)) attributes.push('\\[strike]strike\\()') |
|
||||||
if (attrs & (1 << 8)) attributes.push('\\[overline]overln\\()') |
|
||||||
if (attrs & (1 << 9)) attributes.push('\\[faint]faint\\()') |
|
||||||
if (attrs & (1 << 10)) attributes.push('fraktur') |
|
||||||
if (attributes.length) result += ':' + attributes.join() |
|
||||||
return result.trim() |
|
||||||
} |
|
||||||
|
|
||||||
const formatColor = color => color < 256 ? color : `#${`000000${(color - 256).toString(16)}`.substr(-6)}` |
|
||||||
const getCellData = cell => { |
|
||||||
if (cell < 0 || cell > screen.screen.length) return '(-)' |
|
||||||
let cellAttrs = screen.layout.renderer.drawnScreenAttrs[cell] | 0 |
|
||||||
let cellFG = screen.layout.renderer.drawnScreenFG[cell] | 0 |
|
||||||
let cellBG = screen.layout.renderer.drawnScreenBG[cell] | 0 |
|
||||||
let fgText = formatColor(cellFG) |
|
||||||
let bgText = formatColor(cellBG) |
|
||||||
fgText += `\\[color=${screen.layout.renderer.getColor(cellFG).replace(/ /g, '')}]●\\[]` |
|
||||||
bgText += `\\[color=${screen.layout.renderer.getColor(cellBG).replace(/ /g, '')}]●\\[]` |
|
||||||
let cellCode = (screen.layout.renderer.drawnScreen[cell] || '').codePointAt(0) | 0 |
|
||||||
let hexcode = cellCode.toString(16).toUpperCase() |
|
||||||
if (hexcode.length < 4) hexcode = `0000${hexcode}`.substr(-4) |
|
||||||
hexcode = `U+${hexcode}` |
|
||||||
let x = cell % screen.window.width |
|
||||||
let y = Math.floor(cell / screen.window.width) |
|
||||||
return `((${y},${x})=${cell}:\\[bold]${hexcode}\\[]:F${fgText}:B${bgText}:A(${displayCellAttrs(cellAttrs)}))` |
|
||||||
} |
|
||||||
|
|
||||||
const setFormattedText = (node, text) => { |
|
||||||
node.innerHTML = '' |
|
||||||
|
|
||||||
let match |
|
||||||
let attrs = {} |
|
||||||
|
|
||||||
let pushSpan = content => { |
|
||||||
let span = mk('span') |
|
||||||
node.appendChild(span) |
|
||||||
span.textContent = content |
|
||||||
for (let key in attrs) span[key] = attrs[key] |
|
||||||
} |
|
||||||
|
|
||||||
while ((match = text.match(/\\\[(.*?)\]/))) { |
|
||||||
if (match.index > 0) pushSpan(text.substr(0, match.index)) |
|
||||||
|
|
||||||
attrs = { style: '' } |
|
||||||
let data = match[1].split(' ') |
|
||||||
for (let attr of data) { |
|
||||||
if (!attr) continue |
|
||||||
let key, value |
|
||||||
if (attr.indexOf('=') > -1) { |
|
||||||
key = attr.substr(0, attr.indexOf('=')) |
|
||||||
value = attr.substr(attr.indexOf('=') + 1) |
|
||||||
} else { |
} else { |
||||||
key = attr |
name.textContent = key |
||||||
value = true |
if (group[key] instanceof Function) { |
||||||
|
name.textContent = '' |
||||||
|
let button = document.createElement('button') |
||||||
|
name.classList.add('has-button') |
||||||
|
name.appendChild(button) |
||||||
|
button.textContent = key |
||||||
|
button.addEventListener('click', e => group[key](e)) |
||||||
|
} else if (group[key] instanceof window.Node) value.appendChild(group[key]) |
||||||
|
else value.textContent = group[key] |
||||||
} |
} |
||||||
|
|
||||||
if (key === 'bold') attrs.style += 'font-weight:bold;' |
item.appendChild(name) |
||||||
if (key === 'italic') attrs.style += 'font-style:italic;' |
item.appendChild(value) |
||||||
if (key === 'underline') attrs.style += 'text-decoration:underline;' |
table.appendChild(item) |
||||||
if (key === 'invert') attrs.style += 'background:#000;filter:invert(1);' |
|
||||||
if (key === 'strike') attrs.style += 'text-decoration:line-through;' |
|
||||||
if (key === 'overline') attrs.style += 'text-decoration:overline;' |
|
||||||
if (key === 'faint') attrs.style += 'opacity:0.5;' |
|
||||||
else if (key === 'color') attrs.style += `color:${value};` |
|
||||||
else attrs[key] = value |
|
||||||
} |
} |
||||||
|
|
||||||
text = text.substr(match.index + match[0].length) |
toolbar.appendChild(table) |
||||||
} |
} |
||||||
|
|
||||||
if (text) pushSpan(text) |
let heartbeat = toolbarNodes.heartbeat = document.createElement('div') |
||||||
|
heartbeat.classList.add('heartbeat') |
||||||
|
heartbeat.textContent = '❤' |
||||||
|
toolbar.appendChild(heartbeat) |
||||||
} |
} |
||||||
|
|
||||||
let internalInfo = {} |
connection.on('heartbeat', () => { |
||||||
|
if (screenAttached && toolbarNodes.heartbeat) { |
||||||
updateToolbar = () => { |
toolbarNodes.heartbeat.classList.remove('beat') |
||||||
if (!toolbarAttached) return |
window.requestAnimationFrame(() => { |
||||||
let text = `C((${screen.cursor.y},${screen.cursor.x}),hang:${screen.cursor.hanging},vis:${screen.cursor.visible})` |
toolbarNodes.heartbeat.classList.add('beat') |
||||||
if (mouseHoverCell) { |
}) |
||||||
text += ' m' + getCellData(mouseHoverCell[1] * screen.window.width + mouseHoverCell[0]) |
|
||||||
} |
} |
||||||
setFormattedText(dataDisplay, text) |
}) |
||||||
|
|
||||||
|
updateToolbar = function () { |
||||||
|
initToolbar() |
||||||
|
|
||||||
if ('flags' in internalInfo) { |
Object.assign(toolbarData.cursor, { |
||||||
// we got ourselves some internal data
|
Position: `col ${screen.cursor.x + 1}, ln ${screen.cursor.y + 1}`, |
||||||
let text = ' ' |
Style: screen.cursor.style + (screen.cursor.blinking ? ', blink' : ''), |
||||||
text += ` flags:${internalInfo.flags.toString(2)}` |
Visible: screen.cursor.visible, |
||||||
text += ` curAttrs:${internalInfo.cursorAttrs.toString(2)}` |
Hanging: screen.cursor.hanging |
||||||
text += ` Region:${internalInfo.regionStart}->${internalInfo.regionEnd}` |
}) |
||||||
text += ` Charset:${internalInfo.charsetGx} (0:${internalInfo.charsetG0},1:${internalInfo.charsetG1})` |
|
||||||
text += ` Heap:${internalInfo.freeHeap}` |
let drawTime = Math.round((drawData.endTime - drawData.startTime) * 100) / 100 |
||||||
text += ` Clients:${internalInfo.clientCount}` |
toolbarData.drawing['Last Update'] = `${drawData.reason} (${drawTime}ms)` |
||||||
setFormattedText(internalDisplay, text) |
toolbarData.drawing['Fancy Graphics'].checked = !!screen.layout.window.graphics |
||||||
|
|
||||||
|
for (let i in toolbarData) { |
||||||
|
let group = toolbarData[i] |
||||||
|
let nodes = toolbarNodes[i] |
||||||
|
for (let key in group) { |
||||||
|
if (key === 'title') continue |
||||||
|
let value = nodes[key].value |
||||||
|
if (!(group[key] instanceof window.Node) && !(group[key] instanceof Function)) { |
||||||
|
value.textContent = group[key] |
||||||
|
} |
||||||
|
} |
||||||
} |
} |
||||||
} |
} |
||||||
|
|
||||||
screen.on('draw', updateToolbar) |
screen.on('update', updateToolbar) |
||||||
screen.on('internal', data => { |
screen.on('internal', data => { |
||||||
internalInfo = data |
if (screenAttached && toolbarData) { |
||||||
|
Object.assign(toolbarData.internal, { |
||||||
|
Flags: data.flags.toString(2), |
||||||
|
'Cursor Attributes': data.cursorAttrs.toString(2), |
||||||
|
'Code Page': `${data.charsetGx} (${data.charsetG0}, ${data.charsetG1})`, |
||||||
|
Heap: data.freeHeap, |
||||||
|
Clients: data.clientCount |
||||||
|
}) |
||||||
|
drawData.scrollRegion = [data.regionStart, data.regionEnd] |
||||||
updateToolbar() |
updateToolbar() |
||||||
|
} |
||||||
}) |
}) |
||||||
} |
} |
||||||
|
Loading…
Reference in new issue