disbrowser/src/main/java/com/smallhacker/disbrowser/Grid.kt

200 lines
6.8 KiB
Kotlin

package com.smallhacker.disbrowser
import com.smallhacker.disbrowser.asm.*
class Grid {
private val arrowCells = HashMap<Pair<Int, Int>, HtmlNode?>()
private val arrowClasses = HashMap<Pair<Int, Int>, String>()
private var arrowWidth = 0
private val content = HashMap<Pair<Int, Int>, HtmlNode?>()
private val cellClasses = HashMap<Pair<Int, Int>, String>()
private val addresses = HashMap<SnesAddress, Int>()
private val rowClasses = HashMap<Int, String>()
private val rowId = HashMap<Int, String>()
private var height = 0
private var nextAddress: SnesAddress? = null
fun arrow(from: SnesAddress, to: SnesAddress) {
val yStart = addresses[from]
val yEnd = addresses[to]
if (yStart == null || yEnd == null) {
return
}
val y1: Int
val y2: Int
val dir: String
if (yStart > yEnd) {
dir = "up"
y1 = yEnd
y2 = yStart
} else {
dir = "down"
y1 = yStart
y2 = yEnd
}
val x = nextArrowX(y1, y2)
if ((x + 1) > arrowWidth) {
arrowWidth = x + 1
}
arrowClasses[x to y1] = "arrow arrow-$dir-start"
for (y in (y1 + 1)..(y2 - 1)) {
arrowClasses[x to y] = "arrow arrow-$dir-middle"
}
arrowClasses[x to y2] = "arrow arrow-$dir-end"
//arrowCells[x to yStart] = a().addClass("arrow-link").attr("href", "#${to.toSimpleString()}")
arrowCells[x to yEnd] = div().addClass("arrow-head")
}
private fun nextArrowX(y1: Int, y2: Int): Int {
return generateSequence(0) { it + 1 }
.filter { x ->
(y1..y2).asSequence()
.map { y -> arrowClasses[x to y] }
.all { it == null }
}
.first()
}
fun add(ins: CodeUnit, metadata: Metadata, disassembly: Disassembly) {
val presentedAddress = ins.presentedAddress
if (nextAddress != null) {
if (presentedAddress != nextAddress) {
addDummy()
}
}
nextAddress = ins.nextPresentedAddress
val y = (height++)
addresses[presentedAddress] = y
val (address, bytes, label, primaryMnemonic, secondaryMnemonic, suffix, operands, state, comment, labelAddress)
= ins.print(metadata)
add(y, ins.address,
text(address ?: ""),
text(bytes),
editableField(presentedAddress, "label", label),
fragment {
if (secondaryMnemonic == null) {
text(primaryMnemonic)
} else {
span {
text(primaryMnemonic)
}.attr("title", secondaryMnemonic).addClass("opcode-info")
}
text(suffix ?: "")
text(" ")
val link = ins.linkedState
if (link == null) {
if (labelAddress == null) {
text(operands ?: "")
} else {
val currentLabel = metadata[labelAddress]?.label
editablePopupField(labelAddress, "label", operands, currentLabel)
}
} else {
val local = link.address in disassembly
val url = when {
local -> "#${link.address.toSimpleString()}"
else -> "/${link.address.toSimpleString()}/${link.urlString}"
}
//operands = metadata[link.address]?.label ?: operands
a {
text(operands ?: "")
}.attr("href", url)
}
},
text(state ?: ""),
editableField(presentedAddress, "comment", comment)
)
if (ins.opcode.continuation == Continuation.NO) {
rowClasses[y] = "routine-end"
}
}
private fun editableField(address: SnesAddress, type: String, value: String?): HtmlNode {
return input(value = value ?: "")
.addClass("field-$type")
.addClass("field-editable")
.attr("data-field", type)
.attr("data-address", address.toSimpleString())
}
private fun HtmlArea.editablePopupField(address: SnesAddress, type: String, displayValue: String?, editValue: String?) {
span {
text(displayValue ?: "")
span {}.addClass("field-editable-popup-icon")
}
.addClass("field-$type")
.addClass("field-editable-popup")
.attr("data-field", type)
.attr("data-value", editValue ?: "")
.attr("data-address", address.toSimpleString())
}
private fun addDummy() {
val y = (height++)
add(y, null, null, null, null, text("..."), null, null)
}
private fun add(y: Int, address: SnesAddress?,
cAddress: HtmlNode?,
cBytes: HtmlNode?,
cLabel: HtmlNode?,
cCode: HtmlNode?,
cState: HtmlNode?,
cComment: HtmlNode?
) {
if (address != null) {
rowId[y] = address.toSimpleString()
}
content[0 to y] = cAddress
content[1 to y] = cBytes
content[2 to y] = cLabel
content[3 to y] = cCode
content[4 to y] = cState
content[5 to y] = cComment
}
fun output(): HtmlNode {
val contentMaxX = content.keys.asSequence()
.map { it.first }
.max()
?: -1
return table {
for (y in 0 until height) {
tr {
for (x in 0..3) {
val cssClass = cellClasses[x to y]
td {
content[x to y]?.appendTo(parent)
}.addClass(cssClass)
}
for (x in 0 until arrowWidth) {
val cssClass = arrowClasses[x to y]
td {
arrowCells[x to y]?.appendTo(parent)
}.addClass(cssClass)
}
for (x in 4..contentMaxX) {
val cssClass = cellClasses[x to y]
td {
content[x to y]?.appendTo(parent)
}.addClass(cssClass)
}
}.addClass(rowClasses[y]).attr("id", rowId[y])
}
}
}
}