Merge branch 'fancy-debug' into work

webgl-renderer
cpsdqs 7 years ago
commit 78d32f7fdc
  1. 680
      js/term/debug.js
  2. 2
      js/term/screen.js
  3. 2
      js/term/screen_layout.js
  4. 4
      js/term/screen_renderer.js
  5. 81
      sass/pages/_term.scss

@ -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')
let mouseHoverCell = null const tooltip = document.createElement('div')
tooltip.classList.add('debug-tooltip')
tooltip.classList.add('hidden')
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
}
selectedCell = screen.layout.screenToGrid(e.offsetX, e.offsetY)
mousePosition = [e.offsetX, e.offsetY]
updateTooltip()
}
const onMouseOut = (e) => {
selectedCell = null
tooltip.classList.add('hidden')
}
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 onMouseOut = () => (mouseHoverCell = null)
let addCanvas = function () { let startDrawLoop
if (!debugCanvas.parentNode) { let screenAttached = false
let eventNode
const setScreenAttached = function (attached) {
if (attached && !debugCanvas.parentNode) {
screen.layout.canvas.parentNode.appendChild(debugCanvas) screen.layout.canvas.parentNode.appendChild(debugCanvas)
screen.layout.canvas.addEventListener('mousemove', onMouseMove) eventNode = debugCanvas.parentNode
screen.layout.canvas.addEventListener('mouseout', onMouseOut) eventNode.addEventListener('mousemove', onMouseMove)
} eventNode.addEventListener('mouseout', onMouseOut)
} screen.layout.on('size-update', updateCanvasSize)
let removeCanvas = function () { updateCanvasSize()
if (debugCanvas.parentNode) { 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 (!cells.has(cell) || cells.get(cell)[0] === 0) continue
let [flags, timestamp] = cells.get(cell) if (drawData.showUpdates) {
let elapsedTime = (now - timestamp) / 1000 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 (elapsedTime > 1) continue const [flags, timestamp] = cells.get(cell)
let elapsedTime = (now - timestamp) / 1000
activeCells++ if (elapsedTime > 1) {
ctx.globalAlpha = 0.5 * Math.max(0, 1 - elapsedTime) cells.delete(cell)
continue
}
let x = cell % width ctx.globalAlpha = 0.5 * Math.max(0, 1 - elapsedTime)
let y = Math.floor(cell / width)
if (flags & 1) { let x = cell % width
// redrawn let y = Math.floor(cell / width)
ctx.fillStyle = '#f0f'
}
if (flags & 2) {
// updated
ctx.fillStyle = '#0f0'
}
ctx.fillRect(x * cellWidth, y * cellHeight, cellWidth, cellHeight) if (flags & 2) {
// updated
ctx.fillStyle = '#0f0'
} else if (flags & 1) {
// redrawn
ctx.fillStyle = '#f0f'
}
if (flags & 4) { if (!(flags & 4)) {
// wide cell // outside a clipped region
ctx.lineWidth = 2 ctx.fillStyle = '#0ff'
ctx.strokeStyle = '#f00' }
ctx.strokeRect(x * cellWidth, y * cellHeight, cellWidth, cellHeight)
}
}
if (clippedRects.length) { ctx.fillRect(x * cellSize.width, y * cellSize.height, cellSize.width, cellSize.height)
ctx.globalAlpha = 0.5
ctx.beginPath()
for (let rect of clippedRects) { if (flags & 8) {
ctx.rect(...rect) // wide cell
ctx.strokeStyle = '#f00'
ctx.beginPath()
ctx.moveTo(x * cellSize.width, (y + 1) * cellSize.height)
ctx.lineTo((x + 1) * cellSize.width, (y + 1) * cellSize.height)
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)
const heartbeat = mk('div') let makeSpan = (text, styles) => {
heartbeat.classList.add('heartbeat') let span = document.createElement('span')
heartbeat.textContent = '❤' span.textContent = text
toolbar.appendChild(heartbeat) 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'))
}
const dataDisplay = mk('div') // tooltip
dataDisplay.classList.add('data-display') updateTooltip = function () {
toolbar.appendChild(dataDisplay) 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
}
const internalDisplay = mk('div') let table = document.createElement('table')
internalDisplay.classList.add('internal-display')
toolbar.appendChild(internalDisplay)
toolbar.appendChild(drawInfo) 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')
const buttons = mk('div') let value = document.createElement('td')
buttons.classList.add('toolbar-buttons') value.appendChild(typeof data[name] === 'string' ? new window.Text(data[name]) : data[name])
toolbar.appendChild(buttons) value.classList.add('value')
// heartbeat row.appendChild(label)
connection.on('heartbeat', () => { row.appendChild(value)
heartbeat.classList.remove('beat') table.appendChild(row)
window.requestAnimationFrame(() => { }
heartbeat.classList.add('beat')
})
})
{ tooltip.appendChild(table)
const redraw = mk('button')
redraw.textContent = 'Redraw'
redraw.addEventListener('click', e => {
screen.layout.renderer.resetDrawn()
screen.layout.renderer.draw('debug-redraw')
})
buttons.appendChild(redraw)
const fancyGraphics = mk('button') let cellSize = screen.layout.getCellSize()
fancyGraphics.textContent = 'Toggle Fancy Graphics' // add 3 to the position because for some reason the corner is off
fancyGraphics.addEventListener('click', e => { let posX = (selectedCell[0] + 1) * cellSize.width + 3
screen.layout.renderer.graphics = +!screen.layout.renderer.graphics let posY = (selectedCell[1] + 1) * cellSize.height + 3
screen.layout.renderer.draw('set-graphics') tooltip.style.transform = `translate(${posX}px, ${posY}px)`
})
buttons.appendChild(fancyGraphics)
} }
const attachToolbar = function () { let toolbarData = null
screen.layout.canvas.parentNode.appendChild(toolbar) let toolbarNodes = {}
} const initToolbar = function () {
const detachToolbar = function () { if (toolbarData) return
toolbar.parentNode.removeChild(toolbar)
}
screen.on('update-window:debug', debug => { let showUpdates = document.createElement('input')
if (debug !== toolbarAttached) { showUpdates.type = 'checkbox'
toolbarAttached = debug showUpdates.addEventListener('change', e => {
if (debug) attachToolbar() drawData.showUpdates = showUpdates.checked
else { })
detachToolbar()
removeCanvas()
}
}
})
const displayCellAttrs = attrs => { let fancyGraphics = document.createElement('input')
let result = attrs.toString(16) fancyGraphics.type = 'checkbox'
if (attrs & 1 || attrs & 2) { fancyGraphics.value = !!screen.layout.window.graphics
result += ':has(' fancyGraphics.addEventListener('change', e => {
if (attrs & 1) result += 'fg' screen.layout.window.graphics = +fancyGraphics.checked
if (attrs & 2) result += (attrs & 1 ? ',' : '') + 'bg' })
result += ')'
toolbarData = {
cursor: {
title: 'Cursor',
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.draw('debug-redraw')
}
}
} }
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)}` for (let i in toolbarData) {
const getCellData = cell => { let group = toolbarData[i]
if (cell < 0 || cell > screen.screen.length) return '(-)' let table = document.createElement('table')
let cellAttrs = screen.layout.renderer.drawnScreenAttrs[cell] | 0 table.classList.add('toolbar-group')
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) => { toolbarNodes[i] = {}
node.innerHTML = ''
let match for (let key in group) {
let attrs = {} let item = document.createElement('tr')
let name = document.createElement('td')
name.classList.add('name')
let value = document.createElement('td')
value.classList.add('value')
let pushSpan = content => { toolbarNodes[i][key] = { name, value }
let span = mk('span')
node.appendChild(span)
span.textContent = content
for (let key in attrs) span[key] = attrs[key]
}
while ((match = text.match(/\\\[(.*?)\]/))) { if (key === 'title') {
if (match.index > 0) pushSpan(text.substr(0, match.index)) name.textContent = group[key]
name.classList.add('title')
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) })
if ('flags' in internalInfo) { updateToolbar = function () {
// we got ourselves some internal data initToolbar()
let text = ' '
text += ` flags:${internalInfo.flags.toString(2)}` Object.assign(toolbarData.cursor, {
text += ` curAttrs:${internalInfo.cursorAttrs.toString(2)}` Position: `col ${screen.cursor.x + 1}, ln ${screen.cursor.y + 1}`,
text += ` Region:${internalInfo.regionStart}->${internalInfo.regionEnd}` Style: screen.cursor.style + (screen.cursor.blinking ? ', blink' : ''),
text += ` Charset:${internalInfo.charsetGx} (0:${internalInfo.charsetG0},1:${internalInfo.charsetG1})` Visible: screen.cursor.visible,
text += ` Heap:${internalInfo.freeHeap}` Hanging: screen.cursor.hanging
text += ` Clients:${internalInfo.clientCount}` })
setFormattedText(internalDisplay, text)
let drawTime = Math.round((drawData.endTime - drawData.startTime) * 100) / 100
toolbarData.drawing['Last Update'] = `${drawData.reason} (${drawTime}ms)`
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) {
updateToolbar() 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()
}
}) })
} }

@ -549,5 +549,7 @@ module.exports = class TermScreen extends EventEmitter {
console.warn('Unhandled update', update) console.warn('Unhandled update', update)
} }
} }
this.emit('update')
} }
} }

@ -247,6 +247,8 @@ module.exports = class ScreenLayout extends EventEmitter {
this.renderer.resetDrawn() this.renderer.resetDrawn()
this.renderer.render('update-size', this.serializeRenderData()) this.renderer.render('update-size', this.serializeRenderData())
this.emit('size-update')
} }
} }

@ -639,7 +639,6 @@ module.exports = class CanvasRenderer extends EventEmitter {
if (y === height - 1) rectHeight += padding if (y === height - 1) rectHeight += padding
ctx.rect(rectX, rectY, rectWidth, rectHeight) ctx.rect(rectX, rectY, rectWidth, rectHeight)
if (this.debug && this._debug) this._debug.clipRect(rectX, rectY, rectWidth, rectHeight)
} }
ctx.save() ctx.save()
@ -674,7 +673,8 @@ module.exports = class CanvasRenderer extends EventEmitter {
// set cell flags // set cell flags
let flags = (+redrawMap.get(cell)) let flags = (+redrawMap.get(cell))
flags |= (+updateMap.get(cell)) << 1 flags |= (+updateMap.get(cell)) << 1
flags |= (+isTextWide(text)) << 2 flags |= (+maskedCells.get(cell)) << 2
flags |= (+isTextWide(text)) << 3
this._debug.setCell(cell, flags) this._debug.setCell(cell, flags)
} }
} }

@ -106,22 +106,87 @@ body.term {
.debug-canvas { .debug-canvas {
position: absolute; position: absolute;
top: 6px; top: 0;
left: 6px; left: 0;
pointer-events: none; pointer-events: none;
} }
.debug-tooltip {
position: absolute;
top: 0;
left: 0;
pointer-events: none;
background: #fff;
color: #000;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
border-radius: 6px;
padding: 6px 10px;
font-size: 12px;
line-height: 1;
table {
tr {
.label {
font-weight: bold;
text-align: right;
opacity: 0.5;
}
.value {
text-align: left;
.attributes {
&:empty::before {
content: 'None'
}
span:not(:last-of-type)::after {
content: ', '
}
}
}
}
}
}
.debug-toolbar { .debug-toolbar {
line-height: 1.5; line-height: 1.2;
text-align: left; text-align: left;
padding: 6px 12px 12px 12px; margin: 6px 12px 12px 12px;
font-family: $screen-stack; padding: 6px;
background: #fff;
color: #000;
border-radius: 6px;
font-size: 12px; font-size: 12px;
white-space: normal; white-space: normal;
.toolbar-buttons { .toolbar-group {
button { display: inline-block;
margin-right: 5px; vertical-align: top;
margin: 0 1em;
tr {
.name {
font-weight: bold;
text-align: right;
opacity: 0.5;
&.title, &.has-button {
opacity: 1;
}
button {
background: none;
font: inherit;
text-shadow: none;
box-shadow: none;
color: #2ea1f9;
font-weight: bold;
text-align: right;
padding: 0;
margin: 0;
}
}
} }
} }

Loading…
Cancel
Save