Add fancy debug toolbar

webgl-renderer
cpsdqs 7 years ago
parent e81b2d409c
commit ef4c867007
Signed by untrusted user: cpsdqs
GPG Key ID: 3F59586BB7448DD1
  1. 311
      js/term/debug.js
  2. 2
      js/term/screen.js
  3. 53
      sass/pages/_term.scss

@ -1,4 +1,16 @@
const { getColor } = require('./themes') 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 = document.createElement('canvas') const debugCanvas = document.createElement('canvas')
@ -10,8 +22,10 @@ module.exports = function attachDebugger (screen, connection) {
const tooltip = document.createElement('div') const tooltip = document.createElement('div')
tooltip.classList.add('debug-tooltip') tooltip.classList.add('debug-tooltip')
tooltip.classList.add('hidden')
let updateTooltip let updateTooltip
let updateToolbar
let selectedCell = null let selectedCell = null
let mousePosition = null let mousePosition = null
@ -26,6 +40,7 @@ module.exports = function attachDebugger (screen, connection) {
} }
const onMouseOut = (e) => { const onMouseOut = (e) => {
selectedCell = null selectedCell = null
tooltip.classList.add('hidden')
} }
const updateCanvasSize = function () { const updateCanvasSize = function () {
@ -64,6 +79,7 @@ module.exports = function attachDebugger (screen, connection) {
if (attached && !toolbar.parentNode) { if (attached && !toolbar.parentNode) {
screen.layout.canvas.parentNode.appendChild(toolbar) screen.layout.canvas.parentNode.appendChild(toolbar)
screen.layout.canvas.parentNode.appendChild(tooltip) screen.layout.canvas.parentNode.appendChild(tooltip)
updateToolbar()
} else if (!attached && toolbar.parentNode) { } else if (!attached && toolbar.parentNode) {
screen.layout.canvas.parentNode.removeChild(toolbar) screen.layout.canvas.parentNode.removeChild(toolbar)
screen.layout.canvas.parentNode.removeChild(tooltip) screen.layout.canvas.parentNode.removeChild(tooltip)
@ -80,11 +96,13 @@ module.exports = function attachDebugger (screen, connection) {
let drawData = { let drawData = {
reason: '', reason: '',
showUpdates: false,
startTime: 0, startTime: 0,
endTime: 0, endTime: 0,
clipped: [], clipped: [],
frames: [], frames: [],
cells: new Map() cells: new Map(),
scrollRegion: null
} }
screen._debug = screen.layout.renderer._debug = { screen._debug = screen.layout.renderer._debug = {
@ -121,64 +139,66 @@ module.exports = function attachDebugger (screen, connection) {
ctx.lineWidth = 2 ctx.lineWidth = 2
ctx.lineJoin = 'round' ctx.lineJoin = 'round'
const cells = drawData.cells if (drawData.showUpdates) {
for (let cell = 0; cell < width * height; cell++) { const cells = drawData.cells
// cell does not exist or has no flags set for (let cell = 0; cell < width * height; cell++) {
if (!cells.has(cell) || cells.get(cell)[0] === 0) continue // cell does not exist or has no flags set
if (!cells.has(cell) || cells.get(cell)[0] === 0) continue
const [flags, timestamp] = cells.get(cell)
let elapsedTime = (now - timestamp) / 1000 const [flags, timestamp] = cells.get(cell)
let elapsedTime = (now - timestamp) / 1000
if (elapsedTime > 1) {
cells.delete(cell) if (elapsedTime > 1) {
continue cells.delete(cell)
} continue
}
ctx.globalAlpha = 0.5 * Math.max(0, 1 - elapsedTime)
ctx.globalAlpha = 0.5 * Math.max(0, 1 - elapsedTime)
let x = cell % width
let y = Math.floor(cell / width) let x = cell % width
let y = Math.floor(cell / width)
if (flags & 2) {
// updated if (flags & 2) {
ctx.fillStyle = '#0f0' // updated
} else if (flags & 1) { ctx.fillStyle = '#0f0'
// redrawn } else if (flags & 1) {
ctx.fillStyle = '#f0f' // redrawn
ctx.fillStyle = '#f0f'
}
if (!(flags & 4)) {
// outside a clipped region
ctx.fillStyle = '#0ff'
}
ctx.fillRect(x * cellSize.width, y * cellSize.height, cellSize.width, cellSize.height)
if (flags & 8) {
// 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()
}
} }
if (!(flags & 4)) { let framesToDelete = []
// outside a clipped region for (let frame of drawData.frames) {
ctx.fillStyle = '#0ff' let timestamp = frame[4]
let elapsedTime = (now - timestamp) / 1000
if (elapsedTime > 1) framesToDelete.push(frame)
else {
ctx.globalAlpha = 1 - elapsedTime
ctx.strokeStyle = '#ff0'
ctx.strokeRect(frame[0] * cellSize.width, frame[1] * cellSize.height,
frame[2] * cellSize.width, frame[3] * cellSize.height)
}
} }
for (let frame of framesToDelete) {
ctx.fillRect(x * cellSize.width, y * cellSize.height, cellSize.width, cellSize.height) drawData.frames.splice(drawData.frames.indexOf(frame), 1)
if (flags & 8) {
// 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()
}
}
let framesToDelete = []
for (let frame of drawData.frames) {
let timestamp = frame[4]
let elapsedTime = (now - timestamp) / 1000
if (elapsedTime > 1) framesToDelete.push(frame)
else {
ctx.globalAlpha = 1 - elapsedTime
ctx.strokeStyle = '#ff0'
ctx.strokeRect(frame[0] * cellSize.width, frame[1] * cellSize.height,
frame[2] * cellSize.width, frame[3] * cellSize.height)
} }
} }
for (let frame of framesToDelete) {
drawData.frames.splice(drawData.frames.indexOf(frame), 1)
}
if (selectedCell !== null) { if (selectedCell !== null) {
let [x, y] = selectedCell let [x, y] = selectedCell
@ -215,6 +235,28 @@ module.exports = function attachDebugger (screen, connection) {
ctx.strokeRect(x * cellSize.width, y * cellSize.height, cellSize.width, cellSize.height) ctx.strokeRect(x * cellSize.width, y * cellSize.height, cellSize.width, cellSize.height)
ctx.restore() ctx.restore()
} }
if (drawData.scrollRegion !== null) {
let [start, end] = drawData.scrollRegion
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 * cellSize.height)
ctx.lineTo(width * cellSize.width, end * cellSize.height)
ctx.stroke()
ctx.restore()
}
} }
startDrawLoop = function () { startDrawLoop = function () {
if (isDrawing) return if (isDrawing) return
@ -222,21 +264,46 @@ module.exports = function attachDebugger (screen, connection) {
drawLoop() drawLoop()
} }
let pad2 = i => ('00' + i.toString()).substr(-2)
let formatColor = color => color < 256
? color.toString()
: '#' + 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 // tooltip
updateTooltip = function () { updateTooltip = function () {
tooltip.classList.remove('hidden')
tooltip.innerHTML = '' tooltip.innerHTML = ''
let cell = selectedCell[1] * screen.window.width + selectedCell[0] let cell = selectedCell[1] * screen.window.width + selectedCell[0]
if (!screen.screen[cell]) return if (!screen.screen[cell]) return
let foreground = document.createElement('span') let foreground = document.createElement('span')
foreground.textContent = screen.screenFG[cell] foreground.textContent = formatColor(screen.screenFG[cell])
let preview = document.createElement('span') let preview = document.createElement('span')
preview.textContent = ' ●' preview.textContent = ' ●'
preview.style.color = getColor(screen.screenFG[cell], screen.layout.renderer.palette) preview.style.color = getColor(screen.screenFG[cell], screen.layout.renderer.palette)
foreground.appendChild(preview) foreground.appendChild(preview)
let background = document.createElement('span') let background = document.createElement('span')
background.textContent = screen.screenBG[cell] background.textContent = formatColor(screen.screenBG[cell])
let bgPreview = document.createElement('span') let bgPreview = document.createElement('span')
bgPreview.textContent = ' ●' bgPreview.textContent = ' ●'
bgPreview.style.color = getColor(screen.screenBG[cell], screen.layout.renderer.palette) bgPreview.style.color = getColor(screen.screenBG[cell], screen.layout.renderer.palette)
@ -248,10 +315,15 @@ module.exports = function attachDebugger (screen, connection) {
? `0000${codePoint.toString(16)}`.substr(-4) ? `0000${codePoint.toString(16)}`.substr(-4)
: codePoint.toString(16) : codePoint.toString(16)
let attributes = document.createElement('span')
attributes.classList.add('attributes')
formatAttributes(attributes, screen.screenAttrs[cell])
let data = { let data = {
Foreground: foreground, Foreground: foreground,
Background: background, Background: background,
Character: `U+${formattedCodePoint}` Character: `U+${formattedCodePoint}`,
Attributes: attributes
} }
let table = document.createElement('table') let table = document.createElement('table')
@ -275,4 +347,129 @@ module.exports = function attachDebugger (screen, connection) {
tooltip.style.transform = `translate(${mousePosition.map(x => x + 'px').join(',')})` tooltip.style.transform = `translate(${mousePosition.map(x => x + 'px').join(',')})`
} }
let toolbarData = null
let toolbarNodes = {}
const initToolbar = function () {
if (toolbarData) return
let showUpdates = document.createElement('input')
showUpdates.type = 'checkbox'
showUpdates.addEventListener('change', e => {
drawData.showUpdates = showUpdates.checked
})
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 = {
cursor: {
title: 'Cursor',
Position: '',
Style: '',
Visible: true,
Hanging: false
},
internal: {
Flags: '',
'Cursor Attributes': '',
'Code Page': '',
Heap: 0,
Clients: 0
},
drawing: {
title: 'Drawing',
'Show Updates': showUpdates,
'Fancy Graphics': fancyGraphics,
'Redraw Screen': () => {
screen.layout.renderer.resetDrawn()
screen.layout.renderer.draw('debug-redraw')
}
}
}
for (let i in toolbarData) {
let group = toolbarData[i]
let table = document.createElement('table')
table.classList.add('toolbar-group')
toolbarNodes[i] = {}
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 }
if (key === 'title') {
name.textContent = group[key]
name.classList.add('title')
} else {
name.textContent = key
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]
}
item.appendChild(name)
item.appendChild(value)
table.appendChild(item)
}
toolbar.appendChild(table)
}
}
updateToolbar = function () {
initToolbar()
Object.assign(toolbarData.cursor, {
Position: `col ${screen.cursor.x}, ln ${screen.cursor.y}`,
Style: screen.cursor.style + (screen.cursor.blinking ? ', blink' : ''),
Visible: screen.cursor.visible,
Hanging: screen.cursor.hanging
})
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('update', updateToolbar)
screen.on('internal', 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()
}
})
} }

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

@ -117,9 +117,11 @@ body.term {
left: 0; left: 0;
pointer-events: none; pointer-events: none;
background: #fff; background: #fff;
color: #000;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3); box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
border-radius: 6px; border-radius: 6px;
padding: 10px 15px; padding: 6px 10px;
font-size: 12px;
line-height: 1; line-height: 1;
table { table {
@ -132,19 +134,62 @@ body.term {
.value { .value {
text-align: left; 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-group {
display: inline-block;
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;
}
}
}
}
.toolbar-buttons { .toolbar-buttons {
button { button {
margin-right: 5px; margin-right: 5px;

Loading…
Cancel
Save