mirror of
https://github.com/DaanVandenBosch/phantasmal-world.git
synced 2025-04-05 07:18:29 +08:00
Added script editor widget.
This commit is contained in:
parent
0983be905d
commit
cd70d22da2
@ -1,3 +1,5 @@
|
|||||||
package world.phantasmal.core
|
package world.phantasmal.core
|
||||||
|
|
||||||
fun Char.isDigit(): Boolean = this in '0'..'9'
|
fun Char.isDigit(): Boolean = this in '0'..'9'
|
||||||
|
|
||||||
|
expect fun Int.reinterpretAsFloat(): Float
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
package world.phantasmal.core
|
||||||
|
|
||||||
|
import org.khronos.webgl.ArrayBuffer
|
||||||
|
import org.khronos.webgl.DataView
|
||||||
|
|
||||||
|
private val dataView = DataView(ArrayBuffer(4))
|
||||||
|
|
||||||
|
actual fun Int.reinterpretAsFloat(): Float {
|
||||||
|
dataView.setInt32(0, this)
|
||||||
|
return dataView.getFloat32(0)
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
package world.phantasmal.core
|
||||||
|
|
||||||
|
import java.lang.Float.intBitsToFloat
|
||||||
|
|
||||||
|
actual fun Int.reinterpretAsFloat(): Float = intBitsToFloat(this)
|
@ -20,11 +20,15 @@ class AssemblyProblem(
|
|||||||
|
|
||||||
fun assemble(
|
fun assemble(
|
||||||
assembly: List<String>,
|
assembly: List<String>,
|
||||||
manualStack: Boolean = false,
|
inlineStackArgs: Boolean = true,
|
||||||
): PwResult<List<Segment>> {
|
): PwResult<List<Segment>> {
|
||||||
logger.trace { "Assembly start." }
|
logger.trace {
|
||||||
|
"Assembling ${assembly.size} lines with ${
|
||||||
|
if (inlineStackArgs) "inline stack arguments" else "stack push instructions"
|
||||||
|
}."
|
||||||
|
}
|
||||||
|
|
||||||
val result = Assembler(assembly, manualStack).assemble()
|
val result = Assembler(assembly, inlineStackArgs).assemble()
|
||||||
|
|
||||||
logger.trace {
|
logger.trace {
|
||||||
val warnings = result.problems.count { it.severity == Severity.Warning }
|
val warnings = result.problems.count { it.severity == Severity.Warning }
|
||||||
@ -36,7 +40,7 @@ fun assemble(
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Assembler(private val assembly: List<String>, private val manualStack: Boolean) {
|
private class Assembler(private val assembly: List<String>, private val inlineStackArgs: Boolean) {
|
||||||
private var lineNo = 1
|
private var lineNo = 1
|
||||||
private lateinit var tokens: MutableList<Token>
|
private lateinit var tokens: MutableList<Token>
|
||||||
private var ir: MutableList<Segment> = mutableListOf()
|
private var ir: MutableList<Segment> = mutableListOf()
|
||||||
@ -355,7 +359,7 @@ private class Assembler(private val assembly: List<String>, private val manualSt
|
|||||||
}
|
}
|
||||||
|
|
||||||
val paramCount =
|
val paramCount =
|
||||||
if (manualStack && opcode.stack == StackInteraction.Pop) 0
|
if (!inlineStackArgs && opcode.stack == StackInteraction.Pop) 0
|
||||||
else opcode.params.size
|
else opcode.params.size
|
||||||
|
|
||||||
val argCount = tokens.count { it !is ArgSeparatorToken }
|
val argCount = tokens.count { it !is ArgSeparatorToken }
|
||||||
|
@ -0,0 +1,310 @@
|
|||||||
|
package world.phantasmal.lib.assembly
|
||||||
|
|
||||||
|
import mu.KotlinLogging
|
||||||
|
import world.phantasmal.core.reinterpretAsFloat
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
|
private val logger = KotlinLogging.logger {}
|
||||||
|
|
||||||
|
private const val INDENT_WIDTH = 4
|
||||||
|
private val INDENT = " ".repeat(INDENT_WIDTH)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param inlineStackArgs If true, will output stack arguments inline instead of outputting stack
|
||||||
|
* management instructions (argpush variants).
|
||||||
|
*/
|
||||||
|
fun disassemble(byteCodeIr: List<Segment>, inlineStackArgs: Boolean = true): List<String> {
|
||||||
|
logger.trace {
|
||||||
|
"Disassembling ${byteCodeIr.size} segments with ${
|
||||||
|
if (inlineStackArgs) "inline stack arguments" else "stack push instructions"
|
||||||
|
}."
|
||||||
|
}
|
||||||
|
|
||||||
|
val lines = mutableListOf<String>()
|
||||||
|
val stack = mutableListOf<ArgWithType>()
|
||||||
|
var sectionType: SegmentType? = null
|
||||||
|
|
||||||
|
for (segment in byteCodeIr) {
|
||||||
|
// Section marker (.code, .data or .string).
|
||||||
|
if (sectionType != segment.type) {
|
||||||
|
sectionType = segment.type
|
||||||
|
|
||||||
|
if (lines.isNotEmpty()) {
|
||||||
|
lines.add("")
|
||||||
|
}
|
||||||
|
|
||||||
|
val sectionMarker = when (segment) {
|
||||||
|
is InstructionSegment -> ".code"
|
||||||
|
is DataSegment -> ".data"
|
||||||
|
is StringSegment -> ".string"
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.add(sectionMarker)
|
||||||
|
lines.add("")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Labels.
|
||||||
|
for (label in segment.labels) {
|
||||||
|
lines.add("$label:")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Code or data lines.
|
||||||
|
when (segment) {
|
||||||
|
is InstructionSegment -> {
|
||||||
|
var inVaList = false
|
||||||
|
|
||||||
|
segment.instructions.forEachIndexed { i, instruction ->
|
||||||
|
val opcode = instruction.opcode
|
||||||
|
|
||||||
|
if (opcode.code == OP_VA_START.code) {
|
||||||
|
inVaList = true
|
||||||
|
} else if (opcode.code == OP_VA_END.code) {
|
||||||
|
inVaList = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inlineStackArgs &&
|
||||||
|
!inVaList &&
|
||||||
|
opcode.stack == StackInteraction.Push &&
|
||||||
|
canInlinePushedArg(segment, i)
|
||||||
|
) {
|
||||||
|
stack.addAll(addTypeToArgs(opcode.params, instruction.args))
|
||||||
|
} else {
|
||||||
|
val sb = StringBuilder(INDENT)
|
||||||
|
sb.append(opcode.mnemonic)
|
||||||
|
|
||||||
|
if (opcode.stack == StackInteraction.Pop) {
|
||||||
|
if (inlineStackArgs) {
|
||||||
|
sb.appendArgs(
|
||||||
|
opcode.params,
|
||||||
|
stack.takeLast(opcode.params.size),
|
||||||
|
stack = true,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sb.appendArgs(
|
||||||
|
opcode.params,
|
||||||
|
addTypeToArgs(opcode.params, instruction.args),
|
||||||
|
stack = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opcode.stack != StackInteraction.Push) {
|
||||||
|
stack.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.add(sb.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is DataSegment -> {
|
||||||
|
val sb = StringBuilder(INDENT)
|
||||||
|
|
||||||
|
for (i in 0 until segment.data.size) {
|
||||||
|
sb.append("0x")
|
||||||
|
sb.append(segment.data.getUByte(i).toString(16).padStart(2, '0'))
|
||||||
|
|
||||||
|
when {
|
||||||
|
// Last line.
|
||||||
|
i == segment.data.size - 1 -> {
|
||||||
|
lines.add(sb.toString())
|
||||||
|
}
|
||||||
|
// Start a new line after every 16 bytes.
|
||||||
|
i % 16 == 15 -> {
|
||||||
|
lines.add(sb.toString())
|
||||||
|
sb.setLength(0)
|
||||||
|
sb.append(INDENT)
|
||||||
|
}
|
||||||
|
// Add a space between each byte.
|
||||||
|
else -> {
|
||||||
|
sb.append(" ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is StringSegment -> {
|
||||||
|
lines.add(StringBuilder(INDENT).appendStringSegment(segment.value).toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure newline at the end.
|
||||||
|
lines.add("")
|
||||||
|
|
||||||
|
logger.trace { "Disassembly finished, line count: ${lines.size}." }
|
||||||
|
|
||||||
|
return lines
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class ArgWithType(val arg: Arg, val type: AnyType)
|
||||||
|
|
||||||
|
private fun canInlinePushedArg(segment: InstructionSegment, index: Int): Boolean {
|
||||||
|
var pushedArgCount = 0
|
||||||
|
|
||||||
|
for (i in index until segment.instructions.size) {
|
||||||
|
val opcode = segment.instructions[i].opcode
|
||||||
|
|
||||||
|
when (opcode.stack) {
|
||||||
|
StackInteraction.Push -> pushedArgCount++
|
||||||
|
|
||||||
|
StackInteraction.Pop -> {
|
||||||
|
var paramCount = 0
|
||||||
|
var varArgs = false
|
||||||
|
|
||||||
|
for (param in opcode.params) {
|
||||||
|
when (param.type) {
|
||||||
|
is ILabelVarType -> varArgs = true
|
||||||
|
is RegRefVarType -> varArgs = true
|
||||||
|
else -> paramCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pushedArgCount <= paramCount || (pushedArgCount > paramCount && varArgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
null -> return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addTypeToArgs(params: List<Param>, args: List<Arg>): List<ArgWithType> {
|
||||||
|
val argsWithType = mutableListOf<ArgWithType>()
|
||||||
|
|
||||||
|
for (i in 0 until min(params.size, args.size)) {
|
||||||
|
argsWithType.add(ArgWithType(args[i], params[i].type))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deal with varargs.
|
||||||
|
val lastParam = params.lastOrNull()
|
||||||
|
|
||||||
|
if (
|
||||||
|
lastParam != null &&
|
||||||
|
(lastParam.type == ILabelVarType || lastParam.type == RegRefVarType)
|
||||||
|
) {
|
||||||
|
for (i in argsWithType.size until args.size) {
|
||||||
|
argsWithType.add(ArgWithType(args[i], lastParam.type))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return argsWithType
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun StringBuilder.appendArgs(params: List<Param>, args: List<ArgWithType>, stack: Boolean) {
|
||||||
|
var i = 0
|
||||||
|
|
||||||
|
while (i < params.size) {
|
||||||
|
val paramType = params[i].type
|
||||||
|
|
||||||
|
if (i == 0) {
|
||||||
|
append(" ")
|
||||||
|
} else {
|
||||||
|
append(", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i < args.size) {
|
||||||
|
val (arg, argType) = args[i]
|
||||||
|
|
||||||
|
if (argType is RegTupRefType) {
|
||||||
|
append("r")
|
||||||
|
append(arg.value)
|
||||||
|
} else {
|
||||||
|
when (paramType) {
|
||||||
|
FloatType -> {
|
||||||
|
// Floats are pushed onto the stack as integers with arg_pushl.
|
||||||
|
if (stack) {
|
||||||
|
append((arg.value as Int).reinterpretAsFloat())
|
||||||
|
} else {
|
||||||
|
append(arg.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ILabelVarType -> {
|
||||||
|
while (i < args.size) {
|
||||||
|
append(args[i].arg.value)
|
||||||
|
if (i < args.lastIndex) append(", ")
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RegRefVarType -> {
|
||||||
|
while (i < args.size) {
|
||||||
|
append("r")
|
||||||
|
append(args[i].arg.value)
|
||||||
|
if (i < args.lastIndex) append(", ")
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RegRefType,
|
||||||
|
is RegTupRefType,
|
||||||
|
-> {
|
||||||
|
append("r")
|
||||||
|
append(arg.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
StringType -> {
|
||||||
|
appendStringArg(arg.value as String)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
append(arg.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun StringBuilder.appendStringArg(value: String) {
|
||||||
|
append("\"")
|
||||||
|
|
||||||
|
for (char in value) {
|
||||||
|
when (char) {
|
||||||
|
'\r' -> append("\\r")
|
||||||
|
'\n' -> append("\\n")
|
||||||
|
'\t' -> append("\\t")
|
||||||
|
'"' -> append("\\\"")
|
||||||
|
else -> append(char)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
append("\"")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun StringBuilder.appendStringSegment(value: String) {
|
||||||
|
append("\"")
|
||||||
|
|
||||||
|
var i = 0
|
||||||
|
|
||||||
|
while (i < value.length) {
|
||||||
|
when (val char = value[i]) {
|
||||||
|
// Replace <cr> with \n.
|
||||||
|
'<' -> {
|
||||||
|
if (i + 3 < value.length &&
|
||||||
|
value[i + 1] == 'c' &&
|
||||||
|
value[i + 2] == 'r' &&
|
||||||
|
value[i + 3] == '>'
|
||||||
|
) {
|
||||||
|
append("\\n")
|
||||||
|
i += 3
|
||||||
|
} else {
|
||||||
|
append(char)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'\r' -> append("\\r")
|
||||||
|
'\n' -> append("\\n")
|
||||||
|
'\t' -> append("\\t")
|
||||||
|
'"' -> append("\\\"")
|
||||||
|
else -> append(char)
|
||||||
|
}
|
||||||
|
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
append("\"")
|
||||||
|
}
|
@ -58,11 +58,12 @@ fun instructionSize(instruction: Instruction, dcGcFormat: Boolean): Int {
|
|||||||
is RegTupRefType,
|
is RegTupRefType,
|
||||||
-> 1
|
-> 1
|
||||||
|
|
||||||
|
// Ensure this case is before the LabelType case because ILabelVarType extends
|
||||||
|
// LabelType.
|
||||||
|
is ILabelVarType -> 1 + 2 * args.size
|
||||||
|
|
||||||
is ShortType,
|
is ShortType,
|
||||||
is LabelType,
|
is LabelType,
|
||||||
is ILabelType,
|
|
||||||
is DLabelType,
|
|
||||||
is SLabelType,
|
|
||||||
-> 2
|
-> 2
|
||||||
|
|
||||||
is IntType,
|
is IntType,
|
||||||
@ -77,8 +78,6 @@ fun instructionSize(instruction: Instruction, dcGcFormat: Boolean): Int {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is ILabelVarType -> 1 + 2 * args.size
|
|
||||||
|
|
||||||
is RegRefVarType -> 1 + args.size
|
is RegRefVarType -> 1 + args.size
|
||||||
|
|
||||||
else -> error("Parameter type ${type::class} not implemented.")
|
else -> error("Parameter type ${type::class} not implemented.")
|
||||||
|
@ -62,7 +62,7 @@ object DLabelType : LabelType()
|
|||||||
object SLabelType : LabelType()
|
object SLabelType : LabelType()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Arbitrary amount of instruction labels.
|
* Arbitrary amount of instruction labels (variadic arguments).
|
||||||
*/
|
*/
|
||||||
object ILabelVarType : LabelType()
|
object ILabelVarType : LabelType()
|
||||||
|
|
||||||
@ -88,7 +88,7 @@ object RegRefType : RefType()
|
|||||||
class RegTupRefType(val registerTuple: List<Param>) : RefType()
|
class RegTupRefType(val registerTuple: List<Param>) : RefType()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Arbitrary amount of register references.
|
* Arbitrary amount of register references (variadic arguments).
|
||||||
*/
|
*/
|
||||||
object RegRefVarType : RefType()
|
object RegRefVarType : RefType()
|
||||||
|
|
||||||
|
@ -5,10 +5,30 @@ import world.phantasmal.lib.assembly.*
|
|||||||
// See https://en.wikipedia.org/wiki/Control-flow_graph.
|
// See https://en.wikipedia.org/wiki/Control-flow_graph.
|
||||||
|
|
||||||
enum class BranchType {
|
enum class BranchType {
|
||||||
|
/**
|
||||||
|
* Only encountered when the last segment of a script has no jump or return.
|
||||||
|
*/
|
||||||
None,
|
None,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ret
|
||||||
|
*/
|
||||||
Return,
|
Return,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* jmp or switch_jmp. switch_jmp is a non-conditional jump because it always jumps even though
|
||||||
|
* the jump location is dynamic.
|
||||||
|
*/
|
||||||
Jump,
|
Jump,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Every other jump instruction.
|
||||||
|
*/
|
||||||
ConditionalJump,
|
ConditionalJump,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* call, switch_call or va_call.
|
||||||
|
*/
|
||||||
Call,
|
Call,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,7 +200,7 @@ private fun createBasicBlocks(cfg: ControlFlowGraphBuilder, segment: Instruction
|
|||||||
branchLabels = listOf(inst.args[2].value as Int)
|
branchLabels = listOf(inst.args[2].value as Int)
|
||||||
}
|
}
|
||||||
OP_SWITCH_JMP.code -> {
|
OP_SWITCH_JMP.code -> {
|
||||||
branchType = BranchType.ConditionalJump
|
branchType = BranchType.Jump
|
||||||
branchLabels = inst.args.drop(1).map { it.value as Int }
|
branchLabels = inst.args.drop(1).map { it.value as Int }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -248,7 +268,7 @@ private fun linkBlocks(cfg: ControlFlowGraphBuilder) {
|
|||||||
BranchType.ConditionalJump,
|
BranchType.ConditionalJump,
|
||||||
-> nextBlock?.let(block::linkTo)
|
-> nextBlock?.let(block::linkTo)
|
||||||
|
|
||||||
else -> {
|
BranchType.Jump -> {
|
||||||
// Ignore.
|
// Ignore.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -411,7 +411,7 @@ private fun parseInstructionsSegment(
|
|||||||
for (i in instructions.size - 1 downTo 0) {
|
for (i in instructions.size - 1 downTo 0) {
|
||||||
val opcode = instructions[i].opcode.code
|
val opcode = instructions[i].opcode.code
|
||||||
|
|
||||||
if (opcode == OP_RET.code || opcode == OP_JMP.code) {
|
if (opcode == OP_RET.code || opcode == OP_JMP.code || opcode == OP_SWITCH_JMP.code) {
|
||||||
dropThrough = false
|
dropThrough = false
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -506,11 +506,7 @@ private fun parseInstructionArguments(
|
|||||||
args.addAll(cursor.uShortArray(argSize.toInt()).map { Arg(it.toInt()) })
|
args.addAll(cursor.uShortArray(argSize.toInt()).map { Arg(it.toInt()) })
|
||||||
}
|
}
|
||||||
|
|
||||||
is LabelType,
|
is LabelType -> {
|
||||||
is ILabelType,
|
|
||||||
is DLabelType,
|
|
||||||
is SLabelType,
|
|
||||||
-> {
|
|
||||||
args.add(Arg(cursor.uShort().toInt()))
|
args.add(Arg(cursor.uShort().toInt()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,47 @@
|
|||||||
|
package world.phantasmal.lib.assembly
|
||||||
|
|
||||||
|
import world.phantasmal.lib.test.LibTestSuite
|
||||||
|
import world.phantasmal.testUtils.assertCloseTo
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
class AssemblyTokenizationTests : LibTestSuite() {
|
||||||
|
@Test
|
||||||
|
fun valid_floats_are_parsed_as_FloatTokens() {
|
||||||
|
assertCloseTo(808.9f, (tokenizeLine("808.9")[0] as FloatToken).value)
|
||||||
|
assertCloseTo(-0.9f, (tokenizeLine("-0.9")[0] as FloatToken).value)
|
||||||
|
assertCloseTo(0.001f, (tokenizeLine("1e-3")[0] as FloatToken).value)
|
||||||
|
assertCloseTo(-600.0f, (tokenizeLine("-6e2")[0] as FloatToken).value)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun invalid_floats_area_parsed_as_InvalidNumberTokens_or_InvalidSectionTokens() {
|
||||||
|
val tokens1 = tokenizeLine(" 808.9a ")
|
||||||
|
|
||||||
|
assertEquals(1, tokens1.size)
|
||||||
|
assertEquals(InvalidNumberToken::class, tokens1[0]::class)
|
||||||
|
assertEquals(2, tokens1[0].col)
|
||||||
|
assertEquals(6, tokens1[0].len)
|
||||||
|
|
||||||
|
val tokens2 = tokenizeLine(" -55e ")
|
||||||
|
|
||||||
|
assertEquals(1, tokens2.size)
|
||||||
|
assertEquals(InvalidNumberToken::class, tokens2[0]::class)
|
||||||
|
assertEquals(3, tokens2[0].col)
|
||||||
|
assertEquals(4, tokens2[0].len)
|
||||||
|
|
||||||
|
val tokens3 = tokenizeLine(".7429")
|
||||||
|
|
||||||
|
assertEquals(1, tokens3.size)
|
||||||
|
assertEquals(InvalidSectionToken::class, tokens3[0]::class)
|
||||||
|
assertEquals(1, tokens3[0].col)
|
||||||
|
assertEquals(5, tokens3[0].len)
|
||||||
|
|
||||||
|
val tokens4 = tokenizeLine("\t\t\t4. test")
|
||||||
|
|
||||||
|
assertEquals(2, tokens4.size)
|
||||||
|
assertEquals(InvalidNumberToken::class, tokens4[0]::class)
|
||||||
|
assertEquals(4, tokens4[0].col)
|
||||||
|
assertEquals(2, tokens4[0].len)
|
||||||
|
}
|
||||||
|
}
|
@ -18,6 +18,9 @@ fun Val<Any?>.isNull(): Val<Boolean> =
|
|||||||
fun Val<Any?>.isNotNull(): Val<Boolean> =
|
fun Val<Any?>.isNotNull(): Val<Boolean> =
|
||||||
map { it != null }
|
map { it != null }
|
||||||
|
|
||||||
|
fun <T> Val<T?>.orElse(defaultValue: () -> T): Val<T> =
|
||||||
|
map { it ?: defaultValue() }
|
||||||
|
|
||||||
infix fun <T : Comparable<T>> Val<T>.gt(value: T): Val<Boolean> =
|
infix fun <T : Comparable<T>> Val<T>.gt(value: T): Val<Boolean> =
|
||||||
map { it > value }
|
map { it > value }
|
||||||
|
|
||||||
|
@ -25,6 +25,9 @@ abstract class RegularValTests : ValTests() {
|
|||||||
|
|
||||||
// Test `isNotNull`.
|
// Test `isNotNull`.
|
||||||
assertEquals(any != null, value.isNotNull().value)
|
assertEquals(any != null, value.isNotNull().value)
|
||||||
|
|
||||||
|
// Test `orElse`.
|
||||||
|
assertEquals(any ?: "default", value.orElse { "default" }.value)
|
||||||
}
|
}
|
||||||
listOf(10 to 10, 5 to 99, "a" to "a", "x" to "y").forEach { (a, b) ->
|
listOf(10 to 10, 5 to 99, "a" to "a", "x" to "y").forEach { (a, b) ->
|
||||||
val aVal = createWithValue(a)
|
val aVal = createWithValue(a)
|
||||||
|
@ -39,8 +39,11 @@ dependencies {
|
|||||||
implementation("io.ktor:ktor-client-core-js:$ktorVersion")
|
implementation("io.ktor:ktor-client-core-js:$ktorVersion")
|
||||||
implementation("io.ktor:ktor-client-serialization-js:$ktorVersion")
|
implementation("io.ktor:ktor-client-serialization-js:$ktorVersion")
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-core-js:1.0.0")
|
implementation("org.jetbrains.kotlinx:kotlinx-serialization-core-js:1.0.0")
|
||||||
implementation(npm("golden-layout", "1.5.9"))
|
implementation(npm("@babylonjs/core", "^4.2.0-rc.5"))
|
||||||
implementation(npm("@babylonjs/core", "4.2.0-rc.5"))
|
implementation(npm("golden-layout", "^1.5.9"))
|
||||||
|
implementation(npm("monaco-editor", "^0.21.2"))
|
||||||
|
|
||||||
|
implementation(devNpm("file-loader", "^6.0.0"))
|
||||||
|
|
||||||
testImplementation(kotlin("test-js"))
|
testImplementation(kotlin("test-js"))
|
||||||
testImplementation(project(":test-utils"))
|
testImplementation(project(":test-utils"))
|
||||||
|
@ -51,8 +51,6 @@ class DockWidget(
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
js("""require("golden-layout/src/css/goldenlayout-base.css");""")
|
js("""require("golden-layout/src/css/goldenlayout-base.css");""")
|
||||||
|
|
||||||
observeResize()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun Node.createElement() =
|
override fun Node.createElement() =
|
||||||
@ -94,11 +92,11 @@ class DockWidget(
|
|||||||
|
|
||||||
style.width = ""
|
style.width = ""
|
||||||
style.height = ""
|
style.height = ""
|
||||||
}
|
|
||||||
|
|
||||||
override fun resized(width: Double, height: Double) {
|
addDisposable(size.observe { (size) ->
|
||||||
goldenLayout.updateSize(width, height)
|
goldenLayout.updateSize(size.width, size.height)
|
||||||
}
|
})
|
||||||
|
}
|
||||||
|
|
||||||
override fun internalDispose() {
|
override fun internalDispose() {
|
||||||
goldenLayout.destroy()
|
goldenLayout.destroy()
|
||||||
@ -155,6 +153,7 @@ class DockWidget(
|
|||||||
.pw-core-dock {
|
.pw-core-dock {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
#pw-root .lm_header {
|
#pw-root .lm_header {
|
||||||
|
@ -19,8 +19,6 @@ class RendererWidget(
|
|||||||
className = "pw-core-renderer"
|
className = "pw-core-renderer"
|
||||||
tabIndex = -1
|
tabIndex = -1
|
||||||
|
|
||||||
observeResize()
|
|
||||||
|
|
||||||
observe(selfOrAncestorVisible) { visible ->
|
observe(selfOrAncestorVisible) { visible ->
|
||||||
if (visible) {
|
if (visible) {
|
||||||
renderer.startRendering()
|
renderer.startRendering()
|
||||||
@ -29,14 +27,14 @@ class RendererWidget(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addDisposable(size.observe { (size) ->
|
||||||
|
canvas.width = floor(size.width).toInt()
|
||||||
|
canvas.height = floor(size.height).toInt()
|
||||||
|
})
|
||||||
|
|
||||||
append(canvas)
|
append(canvas)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun resized(width: Double, height: Double) {
|
|
||||||
canvas.width = floor(width).toInt()
|
|
||||||
canvas.height = floor(height).toInt()
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
init {
|
init {
|
||||||
@Suppress("CssUnusedSymbol")
|
@Suppress("CssUnusedSymbol")
|
||||||
|
@ -495,4 +495,11 @@ external class Color4(
|
|||||||
var g: Double
|
var g: Double
|
||||||
var b: Double
|
var b: Double
|
||||||
var a: Double
|
var a: Double
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* Creates a new Color4 from integer values (< 256)
|
||||||
|
*/
|
||||||
|
fun FromInts(r: Int, g: Int, b: Int, a: Int): Color4
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
769
web/src/main/kotlin/world/phantasmal/web/externals/monacoEditor/editor.kt
vendored
Normal file
769
web/src/main/kotlin/world/phantasmal/web/externals/monacoEditor/editor.kt
vendored
Normal file
@ -0,0 +1,769 @@
|
|||||||
|
@file:JsModule("monaco-editor")
|
||||||
|
@file:JsNonModule
|
||||||
|
@file:JsQualifier("editor")
|
||||||
|
@file:Suppress("unused", "PropertyName")
|
||||||
|
|
||||||
|
package world.phantasmal.web.externals.monacoEditor
|
||||||
|
|
||||||
|
import org.w3c.dom.HTMLElement
|
||||||
|
import org.w3c.dom.Range
|
||||||
|
|
||||||
|
external fun create(
|
||||||
|
domElement: HTMLElement,
|
||||||
|
options: IStandaloneEditorConstructionOptions = definedExternally,
|
||||||
|
): IStandaloneCodeEditor
|
||||||
|
|
||||||
|
external fun createModel(
|
||||||
|
value: String,
|
||||||
|
language: String = definedExternally,
|
||||||
|
uri: Uri = definedExternally,
|
||||||
|
): ITextModel
|
||||||
|
|
||||||
|
external fun defineTheme(themeName: String, themeData: IStandaloneThemeData)
|
||||||
|
|
||||||
|
external interface IStandaloneThemeData {
|
||||||
|
var base: String /* 'vs' | 'vs-dark' | 'hc-black' */
|
||||||
|
var inherit: Boolean
|
||||||
|
var rules: Array<ITokenThemeRule>
|
||||||
|
var encodedTokensColors: Array<String>?
|
||||||
|
var colors: IColors
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface IColors
|
||||||
|
|
||||||
|
external interface ITokenThemeRule {
|
||||||
|
var token: String
|
||||||
|
var foreground: String?
|
||||||
|
var background: String?
|
||||||
|
var fontStyle: String?
|
||||||
|
}
|
||||||
|
|
||||||
|
external enum class ScrollType {
|
||||||
|
Smooth /* = 0 */,
|
||||||
|
Immediate /* = 1 */
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface IDimension {
|
||||||
|
var width: Number
|
||||||
|
var height: Number
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface IEditor {
|
||||||
|
fun onDidDispose(listener: () -> Unit): IDisposable
|
||||||
|
fun dispose()
|
||||||
|
fun getId(): String
|
||||||
|
fun getEditorType(): String
|
||||||
|
fun updateOptions(newOptions: IEditorOptions)
|
||||||
|
fun layout(dimension: IDimension = definedExternally)
|
||||||
|
fun focus()
|
||||||
|
fun hasTextFocus(): Boolean
|
||||||
|
fun saveViewState(): dynamic /* ICodeEditorViewState? | IDiffEditorViewState? */
|
||||||
|
fun getVisibleColumnFromPosition(position: IPosition): Number
|
||||||
|
fun getPosition(): Position?
|
||||||
|
fun setPosition(position: IPosition)
|
||||||
|
fun revealLine(lineNumber: Number, scrollType: ScrollType = definedExternally)
|
||||||
|
fun revealLineInCenter(lineNumber: Number, scrollType: ScrollType = definedExternally)
|
||||||
|
fun revealLineInCenterIfOutsideViewport(
|
||||||
|
lineNumber: Number,
|
||||||
|
scrollType: ScrollType = definedExternally,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun revealLineNearTop(lineNumber: Number, scrollType: ScrollType = definedExternally)
|
||||||
|
fun revealPosition(position: IPosition, scrollType: ScrollType = definedExternally)
|
||||||
|
fun revealPositionInCenter(position: IPosition, scrollType: ScrollType = definedExternally)
|
||||||
|
fun revealPositionInCenterIfOutsideViewport(
|
||||||
|
position: IPosition,
|
||||||
|
scrollType: ScrollType = definedExternally,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun revealPositionNearTop(position: IPosition, scrollType: ScrollType = definedExternally)
|
||||||
|
fun getSelection(): Selection?
|
||||||
|
fun getSelections(): Array<Selection>?
|
||||||
|
fun setSelection(selection: IRange)
|
||||||
|
fun setSelection(selection: Range)
|
||||||
|
fun setSelection(selection: ISelection)
|
||||||
|
fun setSelection(selection: Selection)
|
||||||
|
fun setSelections(selections: Any)
|
||||||
|
fun revealLines(
|
||||||
|
startLineNumber: Number,
|
||||||
|
endLineNumber: Number,
|
||||||
|
scrollType: ScrollType = definedExternally,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun revealLinesInCenter(
|
||||||
|
lineNumber: Number,
|
||||||
|
endLineNumber: Number,
|
||||||
|
scrollType: ScrollType = definedExternally,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun revealLinesInCenterIfOutsideViewport(
|
||||||
|
lineNumber: Number,
|
||||||
|
endLineNumber: Number,
|
||||||
|
scrollType: ScrollType = definedExternally,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun revealLinesNearTop(
|
||||||
|
lineNumber: Number,
|
||||||
|
endLineNumber: Number,
|
||||||
|
scrollType: ScrollType = definedExternally,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun revealRange(range: IRange, scrollType: ScrollType = definedExternally)
|
||||||
|
fun revealRangeInCenter(range: IRange, scrollType: ScrollType = definedExternally)
|
||||||
|
fun revealRangeAtTop(range: IRange, scrollType: ScrollType = definedExternally)
|
||||||
|
fun revealRangeInCenterIfOutsideViewport(
|
||||||
|
range: IRange,
|
||||||
|
scrollType: ScrollType = definedExternally,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun revealRangeNearTop(range: IRange, scrollType: ScrollType = definedExternally)
|
||||||
|
fun revealRangeNearTopIfOutsideViewport(
|
||||||
|
range: IRange,
|
||||||
|
scrollType: ScrollType = definedExternally,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun trigger(source: String?, handlerId: String, payload: Any)
|
||||||
|
fun getModel(): dynamic /* ITextModel? | IDiffEditorModel? */
|
||||||
|
fun setModel(model: ITextModel?)
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface ICodeEditor : IEditor {
|
||||||
|
fun onDidChangeModelContent(listener: (e: IModelContentChangedEvent) -> Unit): IDisposable
|
||||||
|
fun onDidChangeModelLanguage(listener: (e: IModelLanguageChangedEvent) -> Unit): IDisposable
|
||||||
|
fun onDidChangeModelLanguageConfiguration(listener: (e: IModelLanguageConfigurationChangedEvent) -> Unit): IDisposable
|
||||||
|
fun onDidChangeModelOptions(listener: (e: IModelOptionsChangedEvent) -> Unit): IDisposable
|
||||||
|
fun onDidChangeCursorPosition(listener: (e: ICursorPositionChangedEvent) -> Unit): IDisposable
|
||||||
|
fun onDidChangeCursorSelection(listener: (e: ICursorSelectionChangedEvent) -> Unit): IDisposable
|
||||||
|
fun onDidChangeModelDecorations(listener: (e: IModelDecorationsChangedEvent) -> Unit): IDisposable
|
||||||
|
fun onDidFocusEditorText(listener: () -> Unit): IDisposable
|
||||||
|
fun onDidBlurEditorText(listener: () -> Unit): IDisposable
|
||||||
|
fun onDidFocusEditorWidget(listener: () -> Unit): IDisposable
|
||||||
|
fun onDidBlurEditorWidget(listener: () -> Unit): IDisposable
|
||||||
|
fun onDidCompositionStart(listener: () -> Unit): IDisposable
|
||||||
|
fun onDidCompositionEnd(listener: () -> Unit): IDisposable
|
||||||
|
fun onDidAttemptReadOnlyEdit(listener: () -> Unit): IDisposable
|
||||||
|
fun hasWidgetFocus(): Boolean
|
||||||
|
override fun getModel(): ITextModel?
|
||||||
|
override fun setModel(model: ITextModel?)
|
||||||
|
fun getRawOptions(): IEditorOptions
|
||||||
|
fun setValue(newValue: String)
|
||||||
|
fun getContentWidth(): Number
|
||||||
|
fun getScrollWidth(): Number
|
||||||
|
fun getScrollLeft(): Number
|
||||||
|
fun getContentHeight(): Number
|
||||||
|
fun getScrollHeight(): Number
|
||||||
|
fun getScrollTop(): Number
|
||||||
|
fun pushUndoStop(): Boolean
|
||||||
|
fun executeEdits(
|
||||||
|
source: String?,
|
||||||
|
edits: Array<IIdentifiedSingleEditOperation>,
|
||||||
|
endCursorState: ICursorStateComputer = definedExternally,
|
||||||
|
): Boolean
|
||||||
|
|
||||||
|
fun executeEdits(
|
||||||
|
source: String?,
|
||||||
|
edits: Array<IIdentifiedSingleEditOperation>,
|
||||||
|
endCursorState: Array<Selection> = definedExternally,
|
||||||
|
): Boolean
|
||||||
|
|
||||||
|
fun getLineDecorations(lineNumber: Number): Array<IModelDecoration>?
|
||||||
|
fun deltaDecorations(
|
||||||
|
oldDecorations: Array<String>,
|
||||||
|
newDecorations: Array<IModelDeltaDecoration>,
|
||||||
|
): Array<String>
|
||||||
|
|
||||||
|
fun getVisibleRanges(): Array<Range>
|
||||||
|
fun getTopForLineNumber(lineNumber: Number): Number
|
||||||
|
fun getTopForPosition(lineNumber: Number, column: Number): Number
|
||||||
|
fun getContainerDomNode(): HTMLElement
|
||||||
|
fun getDomNode(): HTMLElement?
|
||||||
|
fun getOffsetForColumn(lineNumber: Number, column: Number): Number
|
||||||
|
fun render(forceRedraw: Boolean = definedExternally)
|
||||||
|
fun applyFontInfo(target: HTMLElement)
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface IStandaloneCodeEditor : ICodeEditor {
|
||||||
|
override fun updateOptions(newOptions: IEditorOptions /* IEditorOptions & IGlobalEditorOptions */)
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface IGlobalEditorOptions {
|
||||||
|
var tabSize: Number?
|
||||||
|
var insertSpaces: Boolean?
|
||||||
|
var detectIndentation: Boolean?
|
||||||
|
var trimAutoWhitespace: Boolean?
|
||||||
|
var largeFileOptimizations: Boolean?
|
||||||
|
var wordBasedSuggestions: Boolean?
|
||||||
|
var stablePeek: Boolean?
|
||||||
|
var maxTokenizationLineLength: Number?
|
||||||
|
var theme: String?
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface IEditorScrollbarOptions {
|
||||||
|
var arrowSize: Number?
|
||||||
|
var vertical: String? /* 'auto' | 'visible' | 'hidden' */
|
||||||
|
var horizontal: String? /* 'auto' | 'visible' | 'hidden' */
|
||||||
|
var useShadows: Boolean?
|
||||||
|
var verticalHasArrows: Boolean?
|
||||||
|
var horizontalHasArrows: Boolean?
|
||||||
|
var handleMouseWheel: Boolean?
|
||||||
|
var alwaysConsumeMouseWheel: Boolean?
|
||||||
|
var horizontalScrollbarSize: Number?
|
||||||
|
var verticalScrollbarSize: Number?
|
||||||
|
var verticalSliderSize: Number?
|
||||||
|
var horizontalSliderSize: Number?
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface IEditorMinimapOptions {
|
||||||
|
var enabled: Boolean?
|
||||||
|
var side: String? /* 'right' | 'left' */
|
||||||
|
var size: String? /* 'proportional' | 'fill' | 'fit' */
|
||||||
|
var showSlider: String? /* 'always' | 'mouseover' */
|
||||||
|
var renderCharacters: Boolean?
|
||||||
|
var maxColumn: Number?
|
||||||
|
var scale: Number?
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface IEditorFindOptions {
|
||||||
|
var cursorMoveOnType: Boolean?
|
||||||
|
var seedSearchStringFromSelection: Boolean?
|
||||||
|
var autoFindInSelection: String? /* 'never' | 'always' | 'multiline' */
|
||||||
|
var addExtraSpaceOnTop: Boolean?
|
||||||
|
var loop: Boolean?
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface IEditorOptions {
|
||||||
|
var inDiffEditor: Boolean?
|
||||||
|
var ariaLabel: String?
|
||||||
|
var tabIndex: Number?
|
||||||
|
var rulers: Array<dynamic /* Number | IRulerOption */>?
|
||||||
|
var wordSeparators: String?
|
||||||
|
var selectionClipboard: Boolean?
|
||||||
|
var lineNumbers: dynamic /* String | String | String | String | ((lineNumber: Number) -> String)? */
|
||||||
|
var cursorSurroundingLines: Number?
|
||||||
|
var cursorSurroundingLinesStyle: String? /* 'default' | 'all' */
|
||||||
|
var renderFinalNewline: Boolean?
|
||||||
|
var unusualLineTerminators: String? /* 'off' | 'prompt' | 'auto' */
|
||||||
|
var selectOnLineNumbers: Boolean?
|
||||||
|
var lineNumbersMinChars: Number?
|
||||||
|
var glyphMargin: Boolean?
|
||||||
|
var lineDecorationsWidth: dynamic /* Number? | String? */
|
||||||
|
var revealHorizontalRightPadding: Number?
|
||||||
|
var roundedSelection: Boolean?
|
||||||
|
var extraEditorClassName: String?
|
||||||
|
var readOnly: Boolean?
|
||||||
|
var renameOnType: Boolean?
|
||||||
|
var renderValidationDecorations: String? /* 'editable' | 'on' | 'off' */
|
||||||
|
var scrollbar: IEditorScrollbarOptions?
|
||||||
|
var minimap: IEditorMinimapOptions?
|
||||||
|
var find: IEditorFindOptions?
|
||||||
|
var fixedOverflowWidgets: Boolean?
|
||||||
|
var overviewRulerLanes: Number?
|
||||||
|
var overviewRulerBorder: Boolean?
|
||||||
|
var cursorBlinking: String? /* 'blink' | 'smooth' | 'phase' | 'expand' | 'solid' */
|
||||||
|
var mouseWheelZoom: Boolean?
|
||||||
|
var mouseStyle: String? /* 'text' | 'default' | 'copy' */
|
||||||
|
var cursorSmoothCaretAnimation: Boolean?
|
||||||
|
var cursorStyle: String? /* 'line' | 'block' | 'underline' | 'line-thin' | 'block-outline' | 'underline-thin' */
|
||||||
|
var cursorWidth: Number?
|
||||||
|
var fontLigatures: dynamic /* Boolean? | String? */
|
||||||
|
var disableLayerHinting: Boolean?
|
||||||
|
var disableMonospaceOptimizations: Boolean?
|
||||||
|
var hideCursorInOverviewRuler: Boolean?
|
||||||
|
var scrollBeyondLastLine: Boolean?
|
||||||
|
var scrollBeyondLastColumn: Number?
|
||||||
|
var smoothScrolling: Boolean?
|
||||||
|
var automaticLayout: Boolean?
|
||||||
|
var wordWrap: String? /* 'off' | 'on' | 'wordWrapColumn' | 'bounded' */
|
||||||
|
var wordWrapColumn: Number?
|
||||||
|
var wordWrapMinified: Boolean?
|
||||||
|
var wrappingIndent: String? /* 'none' | 'same' | 'indent' | 'deepIndent' */
|
||||||
|
var wrappingStrategy: String? /* 'simple' | 'advanced' */
|
||||||
|
var wordWrapBreakBeforeCharacters: String?
|
||||||
|
var wordWrapBreakAfterCharacters: String?
|
||||||
|
var stopRenderingLineAfter: Number?
|
||||||
|
var links: Boolean?
|
||||||
|
var colorDecorators: Boolean?
|
||||||
|
var contextmenu: Boolean?
|
||||||
|
var mouseWheelScrollSensitivity: Number?
|
||||||
|
var fastScrollSensitivity: Number?
|
||||||
|
var scrollPredominantAxis: Boolean?
|
||||||
|
var columnSelection: Boolean?
|
||||||
|
var multiCursorModifier: String? /* 'ctrlCmd' | 'alt' */
|
||||||
|
var multiCursorMergeOverlapping: Boolean?
|
||||||
|
var multiCursorPaste: String? /* 'spread' | 'full' */
|
||||||
|
var accessibilitySupport: String? /* 'auto' | 'off' | 'on' */
|
||||||
|
var accessibilityPageSize: Number?
|
||||||
|
var quickSuggestions: dynamic /* Boolean? | IQuickSuggestionsOptions? */
|
||||||
|
var quickSuggestionsDelay: Number?
|
||||||
|
var autoClosingBrackets: String? /* 'always' | 'languageDefined' | 'beforeWhitespace' | 'never' */
|
||||||
|
var autoClosingQuotes: String? /* 'always' | 'languageDefined' | 'beforeWhitespace' | 'never' */
|
||||||
|
var autoClosingOvertype: String? /* 'always' | 'auto' | 'never' */
|
||||||
|
var autoSurround: String? /* 'languageDefined' | 'quotes' | 'brackets' | 'never' */
|
||||||
|
var autoIndent: String? /* 'none' | 'keep' | 'brackets' | 'advanced' | 'full' */
|
||||||
|
var formatOnType: Boolean?
|
||||||
|
var formatOnPaste: Boolean?
|
||||||
|
var dragAndDrop: Boolean?
|
||||||
|
var suggestOnTriggerCharacters: Boolean?
|
||||||
|
var acceptSuggestionOnEnter: String? /* 'on' | 'smart' | 'off' */
|
||||||
|
var acceptSuggestionOnCommitCharacter: Boolean?
|
||||||
|
var snippetSuggestions: String? /* 'top' | 'bottom' | 'inline' | 'none' */
|
||||||
|
var emptySelectionClipboard: Boolean?
|
||||||
|
var copyWithSyntaxHighlighting: Boolean?
|
||||||
|
var suggestSelection: String? /* 'first' | 'recentlyUsed' | 'recentlyUsedByPrefix' */
|
||||||
|
var suggestFontSize: Number?
|
||||||
|
var suggestLineHeight: Number?
|
||||||
|
var tabCompletion: String? /* 'on' | 'off' | 'onlySnippets' */
|
||||||
|
var selectionHighlight: Boolean?
|
||||||
|
var occurrencesHighlight: Boolean?
|
||||||
|
var codeLens: Boolean?
|
||||||
|
var codeActionsOnSaveTimeout: Number?
|
||||||
|
var folding: Boolean?
|
||||||
|
var foldingStrategy: String? /* 'auto' | 'indentation' */
|
||||||
|
var foldingHighlight: Boolean?
|
||||||
|
var showFoldingControls: String? /* 'always' | 'mouseover' */
|
||||||
|
var unfoldOnClickAfterEndOfLine: Boolean?
|
||||||
|
var matchBrackets: String? /* 'never' | 'near' | 'always' */
|
||||||
|
var renderWhitespace: String? /* 'none' | 'boundary' | 'selection' | 'trailing' | 'all' */
|
||||||
|
var renderControlCharacters: Boolean?
|
||||||
|
var renderIndentGuides: Boolean?
|
||||||
|
var highlightActiveIndentGuide: Boolean?
|
||||||
|
var renderLineHighlight: String? /* 'none' | 'gutter' | 'line' | 'all' */
|
||||||
|
var renderLineHighlightOnlyWhenFocus: Boolean?
|
||||||
|
var useTabStops: Boolean?
|
||||||
|
var fontFamily: String?
|
||||||
|
var fontWeight: String?
|
||||||
|
var fontSize: Number?
|
||||||
|
var lineHeight: Number?
|
||||||
|
var letterSpacing: Number?
|
||||||
|
var showUnused: Boolean?
|
||||||
|
var peekWidgetDefaultFocus: String? /* 'tree' | 'editor' */
|
||||||
|
var definitionLinkOpensInPeek: Boolean?
|
||||||
|
var showDeprecated: Boolean?
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface IEditorConstructionOptions : IEditorOptions {
|
||||||
|
var overflowWidgetsDomNode: HTMLElement?
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface IStandaloneEditorConstructionOptions : IEditorConstructionOptions,
|
||||||
|
IGlobalEditorOptions {
|
||||||
|
var model: ITextModel?
|
||||||
|
var value: String?
|
||||||
|
var language: String?
|
||||||
|
override var theme: String?
|
||||||
|
var accessibilityHelpUrl: String?
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface IMarker {
|
||||||
|
var owner: String
|
||||||
|
var resource: Uri
|
||||||
|
var severity: MarkerSeverity
|
||||||
|
var code: dynamic /* String? | `T$5`? */
|
||||||
|
var message: String
|
||||||
|
var source: String?
|
||||||
|
var startLineNumber: Number
|
||||||
|
var startColumn: Number
|
||||||
|
var endLineNumber: Number
|
||||||
|
var endColumn: Number
|
||||||
|
var relatedInformation: Array<IRelatedInformation>?
|
||||||
|
var tags: Array<MarkerTag>?
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface IMarkerData {
|
||||||
|
var code: dynamic /* String? | `T$5`? */
|
||||||
|
var severity: MarkerSeverity
|
||||||
|
var message: String
|
||||||
|
var source: String?
|
||||||
|
var startLineNumber: Number
|
||||||
|
var startColumn: Number
|
||||||
|
var endLineNumber: Number
|
||||||
|
var endColumn: Number
|
||||||
|
var relatedInformation: Array<IRelatedInformation>?
|
||||||
|
var tags: Array<MarkerTag>?
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface IRelatedInformation {
|
||||||
|
var resource: Uri
|
||||||
|
var message: String
|
||||||
|
var startLineNumber: Number
|
||||||
|
var startColumn: Number
|
||||||
|
var endLineNumber: Number
|
||||||
|
var endColumn: Number
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface IColorizerOptions {
|
||||||
|
var tabSize: Number?
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface IColorizerElementOptions : IColorizerOptions {
|
||||||
|
var theme: String?
|
||||||
|
var mimeType: String?
|
||||||
|
}
|
||||||
|
|
||||||
|
external enum class ScrollbarVisibility {
|
||||||
|
Auto /* = 1 */,
|
||||||
|
Hidden /* = 2 */,
|
||||||
|
Visible /* = 3 */
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface ThemeColor {
|
||||||
|
var id: String
|
||||||
|
}
|
||||||
|
|
||||||
|
external enum class OverviewRulerLane {
|
||||||
|
Left /* = 1 */,
|
||||||
|
Center /* = 2 */,
|
||||||
|
Right /* = 4 */,
|
||||||
|
Full /* = 7 */
|
||||||
|
}
|
||||||
|
|
||||||
|
external enum class MinimapPosition {
|
||||||
|
Inline /* = 1 */,
|
||||||
|
Gutter /* = 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface IDecorationOptions {
|
||||||
|
var color: dynamic /* String? | ThemeColor? */
|
||||||
|
var darkColor: dynamic /* String? | ThemeColor? */
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface IModelDecorationOverviewRulerOptions : IDecorationOptions {
|
||||||
|
var position: OverviewRulerLane
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface IModelDecorationMinimapOptions : IDecorationOptions {
|
||||||
|
var position: MinimapPosition
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface IModelDecorationOptions {
|
||||||
|
var stickiness: TrackedRangeStickiness?
|
||||||
|
var className: String?
|
||||||
|
var glyphMarginHoverMessage: dynamic /* IMarkdownString? | Array<IMarkdownString>? */
|
||||||
|
var hoverMessage: dynamic /* IMarkdownString? | Array<IMarkdownString>? */
|
||||||
|
var isWholeLine: Boolean?
|
||||||
|
var zIndex: Number?
|
||||||
|
var overviewRuler: IModelDecorationOverviewRulerOptions?
|
||||||
|
var minimap: IModelDecorationMinimapOptions?
|
||||||
|
var glyphMarginClassName: String?
|
||||||
|
var linesDecorationsClassName: String?
|
||||||
|
var firstLineDecorationClassName: String?
|
||||||
|
var marginClassName: String?
|
||||||
|
var inlineClassName: String?
|
||||||
|
var inlineClassNameAffectsLetterSpacing: Boolean?
|
||||||
|
var beforeContentClassName: String?
|
||||||
|
var afterContentClassName: String
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface IModelDeltaDecoration {
|
||||||
|
var range: IRange
|
||||||
|
var options: IModelDecorationOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface IModelDecoration {
|
||||||
|
var id: String
|
||||||
|
var ownerId: Number
|
||||||
|
var range: Range
|
||||||
|
var options: IModelDecorationOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface IWordAtPosition {
|
||||||
|
var word: String
|
||||||
|
var startColumn: Number
|
||||||
|
var endColumn: Number
|
||||||
|
}
|
||||||
|
|
||||||
|
external enum class EndOfLinePreference {
|
||||||
|
TextDefined /* = 0 */,
|
||||||
|
LF /* = 1 */,
|
||||||
|
CRLF /* = 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
external enum class DefaultEndOfLine {
|
||||||
|
LF /* = 1 */,
|
||||||
|
CRLF /* = 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
external enum class EndOfLineSequence {
|
||||||
|
LF /* = 0 */,
|
||||||
|
CRLF /* = 1 */
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface ISingleEditOperation {
|
||||||
|
var range: IRange
|
||||||
|
var text: String?
|
||||||
|
var forceMoveMarkers: Boolean?
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface IIdentifiedSingleEditOperation {
|
||||||
|
var range: IRange
|
||||||
|
var text: String?
|
||||||
|
var forceMoveMarkers: Boolean?
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface IValidEditOperation {
|
||||||
|
var range: Range
|
||||||
|
var text: String
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface ICursorStateComputer
|
||||||
|
|
||||||
|
open external class TextModelResolvedOptions {
|
||||||
|
open var _textModelResolvedOptionsBrand: Unit
|
||||||
|
open var tabSize: Number
|
||||||
|
open var indentSize: Number
|
||||||
|
open var insertSpaces: Boolean
|
||||||
|
open var defaultEOL: DefaultEndOfLine
|
||||||
|
open var trimAutoWhitespace: Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface ITextModelUpdateOptions {
|
||||||
|
var tabSize: Number?
|
||||||
|
var indentSize: Number?
|
||||||
|
var insertSpaces: Boolean?
|
||||||
|
var trimAutoWhitespace: Boolean?
|
||||||
|
}
|
||||||
|
|
||||||
|
open external class FindMatch {
|
||||||
|
open var _findMatchBrand: Unit
|
||||||
|
open var range: Range
|
||||||
|
open var matches: Array<String>?
|
||||||
|
}
|
||||||
|
|
||||||
|
external enum class TrackedRangeStickiness {
|
||||||
|
AlwaysGrowsWhenTypingAtEdges /* = 0 */,
|
||||||
|
NeverGrowsWhenTypingAtEdges /* = 1 */,
|
||||||
|
GrowsOnlyWhenTypingBefore /* = 2 */,
|
||||||
|
GrowsOnlyWhenTypingAfter /* = 3 */
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface ITextModel {
|
||||||
|
var uri: Uri
|
||||||
|
var id: String
|
||||||
|
fun getOptions(): TextModelResolvedOptions
|
||||||
|
fun getVersionId(): Number
|
||||||
|
fun getAlternativeVersionId(): Number
|
||||||
|
fun setValue(newValue: String)
|
||||||
|
fun getValue(
|
||||||
|
eol: EndOfLinePreference = definedExternally,
|
||||||
|
preserveBOM: Boolean = definedExternally,
|
||||||
|
): String
|
||||||
|
|
||||||
|
fun getValueLength(
|
||||||
|
eol: EndOfLinePreference = definedExternally,
|
||||||
|
preserveBOM: Boolean = definedExternally,
|
||||||
|
): Number
|
||||||
|
|
||||||
|
fun getValueInRange(range: IRange, eol: EndOfLinePreference = definedExternally): String
|
||||||
|
fun getValueLengthInRange(range: IRange): Number
|
||||||
|
fun getCharacterCountInRange(range: IRange): Number
|
||||||
|
fun getLineCount(): Number
|
||||||
|
fun getLineContent(lineNumber: Number): String
|
||||||
|
fun getLineLength(lineNumber: Number): Number
|
||||||
|
fun getLinesContent(): Array<String>
|
||||||
|
fun getEOL(): String
|
||||||
|
fun getLineMinColumn(lineNumber: Number): Number
|
||||||
|
fun getLineMaxColumn(lineNumber: Number): Number
|
||||||
|
fun getLineFirstNonWhitespaceColumn(lineNumber: Number): Number
|
||||||
|
fun getLineLastNonWhitespaceColumn(lineNumber: Number): Number
|
||||||
|
fun validatePosition(position: IPosition): Position
|
||||||
|
fun modifyPosition(position: IPosition, offset: Number): Position
|
||||||
|
fun validateRange(range: IRange): Range
|
||||||
|
fun getOffsetAt(position: IPosition): Number
|
||||||
|
fun getPositionAt(offset: Number): Position
|
||||||
|
fun getFullModelRange(): Range
|
||||||
|
fun isDisposed(): Boolean
|
||||||
|
fun findMatches(
|
||||||
|
searchString: String,
|
||||||
|
searchOnlyEditableRange: Boolean,
|
||||||
|
isRegex: Boolean,
|
||||||
|
matchCase: Boolean,
|
||||||
|
wordSeparators: String?,
|
||||||
|
captureMatches: Boolean,
|
||||||
|
limitResultCount: Number = definedExternally,
|
||||||
|
): Array<FindMatch>
|
||||||
|
|
||||||
|
fun findMatches(
|
||||||
|
searchString: String,
|
||||||
|
searchScope: IRange,
|
||||||
|
isRegex: Boolean,
|
||||||
|
matchCase: Boolean,
|
||||||
|
wordSeparators: String?,
|
||||||
|
captureMatches: Boolean,
|
||||||
|
limitResultCount: Number = definedExternally,
|
||||||
|
): Array<FindMatch>
|
||||||
|
|
||||||
|
fun findMatches(
|
||||||
|
searchString: String,
|
||||||
|
searchScope: Array<IRange>,
|
||||||
|
isRegex: Boolean,
|
||||||
|
matchCase: Boolean,
|
||||||
|
wordSeparators: String?,
|
||||||
|
captureMatches: Boolean,
|
||||||
|
limitResultCount: Number = definedExternally,
|
||||||
|
): Array<FindMatch>
|
||||||
|
|
||||||
|
fun findNextMatch(
|
||||||
|
searchString: String,
|
||||||
|
searchStart: IPosition,
|
||||||
|
isRegex: Boolean,
|
||||||
|
matchCase: Boolean,
|
||||||
|
wordSeparators: String?,
|
||||||
|
captureMatches: Boolean,
|
||||||
|
): FindMatch?
|
||||||
|
|
||||||
|
fun findPreviousMatch(
|
||||||
|
searchString: String,
|
||||||
|
searchStart: IPosition,
|
||||||
|
isRegex: Boolean,
|
||||||
|
matchCase: Boolean,
|
||||||
|
wordSeparators: String?,
|
||||||
|
captureMatches: Boolean,
|
||||||
|
): FindMatch?
|
||||||
|
|
||||||
|
fun getModeId(): String
|
||||||
|
fun getWordAtPosition(position: IPosition): IWordAtPosition?
|
||||||
|
fun getWordUntilPosition(position: IPosition): IWordAtPosition
|
||||||
|
fun deltaDecorations(
|
||||||
|
oldDecorations: Array<String>,
|
||||||
|
newDecorations: Array<IModelDeltaDecoration>,
|
||||||
|
ownerId: Number = definedExternally,
|
||||||
|
): Array<String>
|
||||||
|
|
||||||
|
fun getDecorationOptions(id: String): IModelDecorationOptions?
|
||||||
|
fun getDecorationRange(id: String): Range?
|
||||||
|
fun getLineDecorations(
|
||||||
|
lineNumber: Number,
|
||||||
|
ownerId: Number = definedExternally,
|
||||||
|
filterOutValidation: Boolean = definedExternally,
|
||||||
|
): Array<IModelDecoration>
|
||||||
|
|
||||||
|
fun getLinesDecorations(
|
||||||
|
startLineNumber: Number,
|
||||||
|
endLineNumber: Number,
|
||||||
|
ownerId: Number = definedExternally,
|
||||||
|
filterOutValidation: Boolean = definedExternally,
|
||||||
|
): Array<IModelDecoration>
|
||||||
|
|
||||||
|
fun getDecorationsInRange(
|
||||||
|
range: IRange,
|
||||||
|
ownerId: Number = definedExternally,
|
||||||
|
filterOutValidation: Boolean = definedExternally,
|
||||||
|
): Array<IModelDecoration>
|
||||||
|
|
||||||
|
fun getAllDecorations(
|
||||||
|
ownerId: Number = definedExternally,
|
||||||
|
filterOutValidation: Boolean = definedExternally,
|
||||||
|
): Array<IModelDecoration>
|
||||||
|
|
||||||
|
fun getOverviewRulerDecorations(
|
||||||
|
ownerId: Number = definedExternally,
|
||||||
|
filterOutValidation: Boolean = definedExternally,
|
||||||
|
): Array<IModelDecoration>
|
||||||
|
|
||||||
|
fun normalizeIndentation(str: String): String
|
||||||
|
fun updateOptions(newOpts: ITextModelUpdateOptions)
|
||||||
|
fun detectIndentation(defaultInsertSpaces: Boolean, defaultTabSize: Number)
|
||||||
|
fun pushStackElement()
|
||||||
|
fun pushEditOperations(
|
||||||
|
beforeCursorState: Array<Selection>?,
|
||||||
|
editOperations: Array<IIdentifiedSingleEditOperation>,
|
||||||
|
cursorStateComputer: ICursorStateComputer,
|
||||||
|
): Array<Selection>?
|
||||||
|
|
||||||
|
fun pushEOL(eol: EndOfLineSequence)
|
||||||
|
fun applyEdits(operations: Array<IIdentifiedSingleEditOperation>)
|
||||||
|
fun applyEdits(
|
||||||
|
operations: Array<IIdentifiedSingleEditOperation>,
|
||||||
|
computeUndoEdits: Boolean,
|
||||||
|
): dynamic /* Unit | Array */
|
||||||
|
|
||||||
|
fun setEOL(eol: EndOfLineSequence)
|
||||||
|
fun onDidChangeContent(listener: (e: IModelContentChangedEvent) -> Unit): IDisposable
|
||||||
|
fun onDidChangeDecorations(listener: (e: IModelDecorationsChangedEvent) -> Unit): IDisposable
|
||||||
|
fun onDidChangeOptions(listener: (e: IModelOptionsChangedEvent) -> Unit): IDisposable
|
||||||
|
fun onDidChangeLanguage(listener: (e: IModelLanguageChangedEvent) -> Unit): IDisposable
|
||||||
|
fun onDidChangeLanguageConfiguration(listener: (e: IModelLanguageConfigurationChangedEvent) -> Unit): IDisposable
|
||||||
|
fun onWillDispose(listener: () -> Unit): IDisposable
|
||||||
|
fun dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
external object EditorType {
|
||||||
|
var ICodeEditor: String
|
||||||
|
var IDiffEditor: String
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface IModelLanguageChangedEvent {
|
||||||
|
var oldLanguage: String
|
||||||
|
var newLanguage: String
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface IModelLanguageConfigurationChangedEvent
|
||||||
|
|
||||||
|
external interface IModelContentChange {
|
||||||
|
var range: IRange
|
||||||
|
var rangeOffset: Number
|
||||||
|
var rangeLength: Number
|
||||||
|
var text: String
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface IModelContentChangedEvent {
|
||||||
|
var changes: Array<IModelContentChange>
|
||||||
|
var eol: String
|
||||||
|
var versionId: Number
|
||||||
|
var isUndoing: Boolean
|
||||||
|
var isRedoing: Boolean
|
||||||
|
var isFlush: Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface IModelDecorationsChangedEvent {
|
||||||
|
var affectsMinimap: Boolean
|
||||||
|
var affectsOverviewRuler: Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface IModelOptionsChangedEvent {
|
||||||
|
var tabSize: Boolean
|
||||||
|
var indentSize: Boolean
|
||||||
|
var insertSpaces: Boolean
|
||||||
|
var trimAutoWhitespace: Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
external enum class CursorChangeReason {
|
||||||
|
NotSet /* = 0 */,
|
||||||
|
ContentFlush /* = 1 */,
|
||||||
|
RecoverFromMarkers /* = 2 */,
|
||||||
|
Explicit /* = 3 */,
|
||||||
|
Paste /* = 4 */,
|
||||||
|
Undo /* = 5 */,
|
||||||
|
Redo /* = 6 */
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface ICursorPositionChangedEvent {
|
||||||
|
var position: Position
|
||||||
|
var secondaryPositions: Array<Position>
|
||||||
|
var reason: CursorChangeReason
|
||||||
|
var source: String
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface ICursorSelectionChangedEvent {
|
||||||
|
var selection: Selection
|
||||||
|
var secondarySelections: Array<Selection>
|
||||||
|
var modelVersionId: Number
|
||||||
|
var oldSelections: Array<Selection>?
|
||||||
|
var oldModelVersionId: Number
|
||||||
|
var source: String
|
||||||
|
var reason: CursorChangeReason
|
||||||
|
}
|
||||||
|
|
||||||
|
external enum class AccessibilitySupport {
|
||||||
|
Unknown /* = 0 */,
|
||||||
|
Disabled /* = 1 */,
|
||||||
|
Enabled /* = 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
external enum class EditorAutoIndentStrategy {
|
||||||
|
None /* = 0 */,
|
||||||
|
Keep /* = 1 */,
|
||||||
|
Brackets /* = 2 */,
|
||||||
|
Advanced /* = 3 */,
|
||||||
|
Full /* = 4 */
|
||||||
|
}
|
8
web/src/main/kotlin/world/phantasmal/web/externals/monacoEditor/editorExtensions.kt
vendored
Normal file
8
web/src/main/kotlin/world/phantasmal/web/externals/monacoEditor/editorExtensions.kt
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
package world.phantasmal.web.externals.monacoEditor
|
||||||
|
|
||||||
|
inline operator fun IColors.get(name: String): String =
|
||||||
|
asDynamic()[name].unsafeCast<String>()
|
||||||
|
|
||||||
|
inline operator fun IColors.set(name: String, value: String) {
|
||||||
|
asDynamic()[name] = value
|
||||||
|
}
|
220
web/src/main/kotlin/world/phantasmal/web/externals/monacoEditor/languages.kt
vendored
Normal file
220
web/src/main/kotlin/world/phantasmal/web/externals/monacoEditor/languages.kt
vendored
Normal file
@ -0,0 +1,220 @@
|
|||||||
|
@file:JsModule("monaco-editor")
|
||||||
|
@file:JsNonModule
|
||||||
|
@file:JsQualifier("languages")
|
||||||
|
|
||||||
|
package world.phantasmal.web.externals.monacoEditor
|
||||||
|
|
||||||
|
import kotlin.js.RegExp
|
||||||
|
|
||||||
|
external fun register(language: ILanguageExtensionPoint)
|
||||||
|
|
||||||
|
external fun setLanguageConfiguration(
|
||||||
|
languageId: String,
|
||||||
|
configuration: LanguageConfiguration,
|
||||||
|
): IDisposable
|
||||||
|
|
||||||
|
external fun setMonarchTokensProvider(
|
||||||
|
languageId: String,
|
||||||
|
languageDef: IMonarchLanguage,
|
||||||
|
): IDisposable
|
||||||
|
|
||||||
|
external interface CommentRule {
|
||||||
|
var lineComment: String?
|
||||||
|
get() = definedExternally
|
||||||
|
set(value) = definedExternally
|
||||||
|
var blockComment: dynamic /* JsTuple<String, String> */
|
||||||
|
get() = definedExternally
|
||||||
|
set(value) = definedExternally
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface LanguageConfiguration {
|
||||||
|
var comments: CommentRule?
|
||||||
|
get() = definedExternally
|
||||||
|
set(value) = definedExternally
|
||||||
|
var brackets: Array<dynamic /* JsTuple<String, String> */>?
|
||||||
|
get() = definedExternally
|
||||||
|
set(value) = definedExternally
|
||||||
|
var wordPattern: RegExp?
|
||||||
|
get() = definedExternally
|
||||||
|
set(value) = definedExternally
|
||||||
|
var indentationRules: IndentationRule?
|
||||||
|
get() = definedExternally
|
||||||
|
set(value) = definedExternally
|
||||||
|
var onEnterRules: Array<OnEnterRule>?
|
||||||
|
get() = definedExternally
|
||||||
|
set(value) = definedExternally
|
||||||
|
var autoClosingPairs: Array<IAutoClosingPairConditional>?
|
||||||
|
get() = definedExternally
|
||||||
|
set(value) = definedExternally
|
||||||
|
var surroundingPairs: Array<IAutoClosingPair>?
|
||||||
|
get() = definedExternally
|
||||||
|
set(value) = definedExternally
|
||||||
|
var autoCloseBefore: String?
|
||||||
|
get() = definedExternally
|
||||||
|
set(value) = definedExternally
|
||||||
|
var folding: FoldingRules?
|
||||||
|
get() = definedExternally
|
||||||
|
set(value) = definedExternally
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface IndentationRule {
|
||||||
|
var decreaseIndentPattern: RegExp
|
||||||
|
var increaseIndentPattern: RegExp
|
||||||
|
var indentNextLinePattern: RegExp?
|
||||||
|
get() = definedExternally
|
||||||
|
set(value) = definedExternally
|
||||||
|
var unIndentedLinePattern: RegExp?
|
||||||
|
get() = definedExternally
|
||||||
|
set(value) = definedExternally
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface FoldingMarkers {
|
||||||
|
var start: RegExp
|
||||||
|
var end: RegExp
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface FoldingRules {
|
||||||
|
var offSide: Boolean?
|
||||||
|
get() = definedExternally
|
||||||
|
set(value) = definedExternally
|
||||||
|
var markers: FoldingMarkers?
|
||||||
|
get() = definedExternally
|
||||||
|
set(value) = definedExternally
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface OnEnterRule {
|
||||||
|
var beforeText: RegExp
|
||||||
|
var afterText: RegExp?
|
||||||
|
get() = definedExternally
|
||||||
|
set(value) = definedExternally
|
||||||
|
var oneLineAboveText: RegExp?
|
||||||
|
get() = definedExternally
|
||||||
|
set(value) = definedExternally
|
||||||
|
var action: EnterAction
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface IDocComment {
|
||||||
|
var open: String
|
||||||
|
var close: String?
|
||||||
|
get() = definedExternally
|
||||||
|
set(value) = definedExternally
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface IAutoClosingPair {
|
||||||
|
var open: String
|
||||||
|
var close: String
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface IAutoClosingPairConditional : IAutoClosingPair {
|
||||||
|
var notIn: Array<String>?
|
||||||
|
get() = definedExternally
|
||||||
|
set(value) = definedExternally
|
||||||
|
}
|
||||||
|
|
||||||
|
external enum class IndentAction {
|
||||||
|
None /* = 0 */,
|
||||||
|
Indent /* = 1 */,
|
||||||
|
IndentOutdent /* = 2 */,
|
||||||
|
Outdent /* = 3 */
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface EnterAction {
|
||||||
|
var indentAction: IndentAction
|
||||||
|
var appendText: String?
|
||||||
|
get() = definedExternally
|
||||||
|
set(value) = definedExternally
|
||||||
|
var removeText: Number?
|
||||||
|
get() = definedExternally
|
||||||
|
set(value) = definedExternally
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface ILanguageExtensionPoint {
|
||||||
|
var id: String
|
||||||
|
var extensions: Array<String>?
|
||||||
|
get() = definedExternally
|
||||||
|
set(value) = definedExternally
|
||||||
|
var filenames: Array<String>?
|
||||||
|
get() = definedExternally
|
||||||
|
set(value) = definedExternally
|
||||||
|
var filenamePatterns: Array<String>?
|
||||||
|
get() = definedExternally
|
||||||
|
set(value) = definedExternally
|
||||||
|
var firstLine: String?
|
||||||
|
get() = definedExternally
|
||||||
|
set(value) = definedExternally
|
||||||
|
var aliases: Array<String>?
|
||||||
|
get() = definedExternally
|
||||||
|
set(value) = definedExternally
|
||||||
|
var mimetypes: Array<String>?
|
||||||
|
get() = definedExternally
|
||||||
|
set(value) = definedExternally
|
||||||
|
var configuration: Uri?
|
||||||
|
get() = definedExternally
|
||||||
|
set(value) = definedExternally
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface IMonarchLanguageTokenizer
|
||||||
|
|
||||||
|
external interface IMonarchLanguage {
|
||||||
|
var tokenizer: IMonarchLanguageTokenizer
|
||||||
|
var ignoreCase: Boolean?
|
||||||
|
get() = definedExternally
|
||||||
|
set(value) = definedExternally
|
||||||
|
var unicode: Boolean?
|
||||||
|
get() = definedExternally
|
||||||
|
set(value) = definedExternally
|
||||||
|
var defaultToken: String?
|
||||||
|
get() = definedExternally
|
||||||
|
set(value) = definedExternally
|
||||||
|
var brackets: Array<IMonarchLanguageBracket>?
|
||||||
|
get() = definedExternally
|
||||||
|
set(value) = definedExternally
|
||||||
|
var start: String?
|
||||||
|
get() = definedExternally
|
||||||
|
set(value) = definedExternally
|
||||||
|
var tokenPostfix: String?
|
||||||
|
get() = definedExternally
|
||||||
|
set(value) = definedExternally
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface IExpandedMonarchLanguageRule {
|
||||||
|
var regex: RegExp
|
||||||
|
var action: IExpandedMonarchLanguageAction
|
||||||
|
var include: String
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface IExpandedMonarchLanguageAction {
|
||||||
|
var group: Array<dynamic /* IShortMonarchLanguageAction | IExpandedMonarchLanguageAction | Array<IShortMonarchLanguageAction> | Array<IExpandedMonarchLanguageAction> */>?
|
||||||
|
get() = definedExternally
|
||||||
|
set(value) = definedExternally
|
||||||
|
var cases: Any?
|
||||||
|
get() = definedExternally
|
||||||
|
set(value) = definedExternally
|
||||||
|
var token: String?
|
||||||
|
get() = definedExternally
|
||||||
|
set(value) = definedExternally
|
||||||
|
var next: String?
|
||||||
|
get() = definedExternally
|
||||||
|
set(value) = definedExternally
|
||||||
|
var switchTo: String?
|
||||||
|
get() = definedExternally
|
||||||
|
set(value) = definedExternally
|
||||||
|
var goBack: Number?
|
||||||
|
get() = definedExternally
|
||||||
|
set(value) = definedExternally
|
||||||
|
var bracket: String?
|
||||||
|
get() = definedExternally
|
||||||
|
set(value) = definedExternally
|
||||||
|
var nextEmbedded: String?
|
||||||
|
get() = definedExternally
|
||||||
|
set(value) = definedExternally
|
||||||
|
var log: String?
|
||||||
|
get() = definedExternally
|
||||||
|
set(value) = definedExternally
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface IMonarchLanguageBracket {
|
||||||
|
var open: String
|
||||||
|
var close: String
|
||||||
|
var token: String
|
||||||
|
}
|
10
web/src/main/kotlin/world/phantasmal/web/externals/monacoEditor/languagesExtensions.kt
vendored
Normal file
10
web/src/main/kotlin/world/phantasmal/web/externals/monacoEditor/languagesExtensions.kt
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package world.phantasmal.web.externals.monacoEditor
|
||||||
|
|
||||||
|
typealias IMonarchLanguageRule = IExpandedMonarchLanguageRule
|
||||||
|
|
||||||
|
inline operator fun IMonarchLanguageTokenizer.get(name: String): Array<IMonarchLanguageRule> =
|
||||||
|
asDynamic()[name].unsafeCast<Array<IMonarchLanguageRule>>()
|
||||||
|
|
||||||
|
inline operator fun IMonarchLanguageTokenizer.set(name: String, value: Array<IMonarchLanguageRule>) {
|
||||||
|
asDynamic()[name] = value
|
||||||
|
}
|
183
web/src/main/kotlin/world/phantasmal/web/externals/monacoEditor/monacoEditor.kt
vendored
Normal file
183
web/src/main/kotlin/world/phantasmal/web/externals/monacoEditor/monacoEditor.kt
vendored
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
@file:JsModule("monaco-editor")
|
||||||
|
@file:JsNonModule
|
||||||
|
@file:Suppress("CovariantEquals", "unused")
|
||||||
|
|
||||||
|
package world.phantasmal.web.externals.monacoEditor
|
||||||
|
|
||||||
|
external interface IDisposable {
|
||||||
|
fun dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
external enum class MarkerTag {
|
||||||
|
Unnecessary /* = 1 */,
|
||||||
|
Deprecated /* = 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
external enum class MarkerSeverity {
|
||||||
|
Hint /* = 1 */,
|
||||||
|
Info /* = 2 */,
|
||||||
|
Warning /* = 4 */,
|
||||||
|
Error /* = 8 */
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface IRange {
|
||||||
|
var startLineNumber: Number
|
||||||
|
var startColumn: Number
|
||||||
|
var endLineNumber: Number
|
||||||
|
var endColumn: Number
|
||||||
|
}
|
||||||
|
|
||||||
|
open external class Range(
|
||||||
|
startLineNumber: Number,
|
||||||
|
startColumn: Number,
|
||||||
|
endLineNumber: Number,
|
||||||
|
endColumn: Number,
|
||||||
|
) {
|
||||||
|
open var startLineNumber: Number
|
||||||
|
open var startColumn: Number
|
||||||
|
open var endLineNumber: Number
|
||||||
|
open var endColumn: Number
|
||||||
|
open fun isEmpty(): Boolean
|
||||||
|
open fun containsPosition(position: IPosition): Boolean
|
||||||
|
open fun containsRange(range: IRange): Boolean
|
||||||
|
open fun strictContainsRange(range: IRange): Boolean
|
||||||
|
open fun plusRange(range: IRange): Range
|
||||||
|
open fun intersectRanges(range: IRange): Range?
|
||||||
|
open fun equalsRange(other: IRange?): Boolean
|
||||||
|
open fun getEndPosition(): Position
|
||||||
|
open fun getStartPosition(): Position
|
||||||
|
override fun toString(): String
|
||||||
|
open fun setEndPosition(endLineNumber: Number, endColumn: Number): Range
|
||||||
|
open fun setStartPosition(startLineNumber: Number, startColumn: Number): Range
|
||||||
|
open fun collapseToStart(): Range
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun isEmpty(range: IRange): Boolean
|
||||||
|
fun containsPosition(range: IRange, position: IPosition): Boolean
|
||||||
|
fun containsRange(range: IRange, otherRange: IRange): Boolean
|
||||||
|
fun strictContainsRange(range: IRange, otherRange: IRange): Boolean
|
||||||
|
fun plusRange(a: IRange, b: IRange): Range
|
||||||
|
fun intersectRanges(a: IRange, b: IRange): Range?
|
||||||
|
fun equalsRange(a: IRange?, b: IRange?): Boolean
|
||||||
|
fun getEndPosition(range: IRange): Position
|
||||||
|
fun getStartPosition(range: IRange): Position
|
||||||
|
fun collapseToStart(range: IRange): Range
|
||||||
|
fun fromPositions(start: IPosition, end: IPosition = definedExternally): Range
|
||||||
|
fun lift(range: Nothing?): Nothing?
|
||||||
|
fun lift(range: IRange): Range
|
||||||
|
fun isIRange(obj: Any): Boolean
|
||||||
|
fun areIntersectingOrTouching(a: IRange, b: IRange): Boolean
|
||||||
|
fun areIntersecting(a: IRange, b: IRange): Boolean
|
||||||
|
fun compareRangesUsingStarts(a: IRange?, b: IRange?): Number
|
||||||
|
fun compareRangesUsingEnds(a: IRange, b: IRange): Number
|
||||||
|
fun spansMultipleLines(range: IRange): Boolean
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface ISelection {
|
||||||
|
var selectionStartLineNumber: Number
|
||||||
|
var selectionStartColumn: Number
|
||||||
|
var positionLineNumber: Number
|
||||||
|
var positionColumn: Number
|
||||||
|
}
|
||||||
|
|
||||||
|
open external class Selection(
|
||||||
|
selectionStartLineNumber: Number,
|
||||||
|
selectionStartColumn: Number,
|
||||||
|
positionLineNumber: Number,
|
||||||
|
positionColumn: Number,
|
||||||
|
) : Range {
|
||||||
|
open var selectionStartLineNumber: Number
|
||||||
|
open var selectionStartColumn: Number
|
||||||
|
open var positionLineNumber: Number
|
||||||
|
open var positionColumn: Number
|
||||||
|
override fun toString(): String
|
||||||
|
open fun equalsSelection(other: ISelection): Boolean
|
||||||
|
open fun getDirection(): SelectionDirection
|
||||||
|
override fun setEndPosition(endLineNumber: Number, endColumn: Number): Selection
|
||||||
|
open fun getPosition(): Position
|
||||||
|
override fun setStartPosition(startLineNumber: Number, startColumn: Number): Selection
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun selectionsEqual(a: ISelection, b: ISelection): Boolean
|
||||||
|
fun fromPositions(start: IPosition, end: IPosition = definedExternally): Selection
|
||||||
|
fun liftSelection(sel: ISelection): Selection
|
||||||
|
fun selectionsArrEqual(a: Array<ISelection>, b: Array<ISelection>): Boolean
|
||||||
|
fun isISelection(obj: Any): Boolean
|
||||||
|
fun createWithDirection(
|
||||||
|
startLineNumber: Number,
|
||||||
|
startColumn: Number,
|
||||||
|
endLineNumber: Number,
|
||||||
|
endColumn: Number,
|
||||||
|
direction: SelectionDirection,
|
||||||
|
): Selection
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
external enum class SelectionDirection {
|
||||||
|
LTR /* = 0 */,
|
||||||
|
RTL /* = 1 */
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface IPosition {
|
||||||
|
var lineNumber: Number
|
||||||
|
var column: Number
|
||||||
|
}
|
||||||
|
|
||||||
|
open external class Position(lineNumber: Number, column: Number) {
|
||||||
|
open var lineNumber: Number
|
||||||
|
open var column: Number
|
||||||
|
open fun with(
|
||||||
|
newLineNumber: Number = definedExternally,
|
||||||
|
newColumn: Number = definedExternally,
|
||||||
|
): Position
|
||||||
|
|
||||||
|
open fun delta(
|
||||||
|
deltaLineNumber: Number = definedExternally,
|
||||||
|
deltaColumn: Number = definedExternally,
|
||||||
|
): Position
|
||||||
|
|
||||||
|
open fun equals(other: IPosition): Boolean
|
||||||
|
open fun isBefore(other: IPosition): Boolean
|
||||||
|
open fun isBeforeOrEqual(other: IPosition): Boolean
|
||||||
|
open fun clone(): Position
|
||||||
|
override fun toString(): String
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun equals(a: IPosition?, b: IPosition?): Boolean
|
||||||
|
fun isBefore(a: IPosition, b: IPosition): Boolean
|
||||||
|
fun isBeforeOrEqual(a: IPosition, b: IPosition): Boolean
|
||||||
|
fun compare(a: IPosition, b: IPosition): Number
|
||||||
|
fun lift(pos: IPosition): Position
|
||||||
|
fun isIPosition(obj: Any): Boolean
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface UriComponents {
|
||||||
|
var scheme: String
|
||||||
|
var authority: String
|
||||||
|
var path: String
|
||||||
|
var query: String
|
||||||
|
var fragment: String
|
||||||
|
}
|
||||||
|
|
||||||
|
open external class Uri : UriComponents {
|
||||||
|
override var scheme: String
|
||||||
|
override var authority: String
|
||||||
|
override var path: String
|
||||||
|
override var query: String
|
||||||
|
override var fragment: String
|
||||||
|
open fun toString(skipEncoding: Boolean = definedExternally): String
|
||||||
|
open fun toJSON(): UriComponents
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun isUri(thing: Any): Boolean
|
||||||
|
fun parse(value: String, _strict: Boolean = definedExternally): Uri
|
||||||
|
fun file(path: String): Uri
|
||||||
|
fun joinPath(uri: Uri, vararg pathFragment: String): Uri
|
||||||
|
fun revive(data: UriComponents): Uri
|
||||||
|
fun revive(data: Uri): Uri
|
||||||
|
fun revive(data: UriComponents? = definedExternally): Uri?
|
||||||
|
fun revive(data: Uri? = definedExternally): Uri?
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,7 @@ import world.phantasmal.lib.fileFormats.quest.Episode
|
|||||||
import world.phantasmal.lib.fileFormats.quest.NpcType
|
import world.phantasmal.lib.fileFormats.quest.NpcType
|
||||||
import world.phantasmal.observable.value.Val
|
import world.phantasmal.observable.value.Val
|
||||||
import world.phantasmal.observable.value.mutableVal
|
import world.phantasmal.observable.value.mutableVal
|
||||||
|
import world.phantasmal.observable.value.orElse
|
||||||
import kotlin.time.Duration
|
import kotlin.time.Duration
|
||||||
|
|
||||||
class HuntMethodModel(
|
class HuntMethodModel(
|
||||||
@ -26,7 +27,7 @@ class HuntMethodModel(
|
|||||||
*/
|
*/
|
||||||
val userTime: Val<Duration?> = _userTime
|
val userTime: Val<Duration?> = _userTime
|
||||||
|
|
||||||
val time: Val<Duration> = userTime.map { it ?: defaultTime }
|
val time: Val<Duration> = userTime.orElse { defaultTime }
|
||||||
|
|
||||||
fun setUserTime(userTime: Duration?): HuntMethodModel {
|
fun setUserTime(userTime: Duration?): HuntMethodModel {
|
||||||
_userTime.value = userTime
|
_userTime.value = userTime
|
||||||
|
@ -8,6 +8,7 @@ import world.phantasmal.web.core.PwToolType
|
|||||||
import world.phantasmal.web.core.loading.AssetLoader
|
import world.phantasmal.web.core.loading.AssetLoader
|
||||||
import world.phantasmal.web.core.stores.UiStore
|
import world.phantasmal.web.core.stores.UiStore
|
||||||
import world.phantasmal.web.externals.babylon.Engine
|
import world.phantasmal.web.externals.babylon.Engine
|
||||||
|
import world.phantasmal.web.questEditor.controllers.AssemblyEditorController
|
||||||
import world.phantasmal.web.questEditor.controllers.NpcCountsController
|
import world.phantasmal.web.questEditor.controllers.NpcCountsController
|
||||||
import world.phantasmal.web.questEditor.controllers.QuestEditorToolbarController
|
import world.phantasmal.web.questEditor.controllers.QuestEditorToolbarController
|
||||||
import world.phantasmal.web.questEditor.controllers.QuestInfoController
|
import world.phantasmal.web.questEditor.controllers.QuestInfoController
|
||||||
@ -18,6 +19,7 @@ import world.phantasmal.web.questEditor.rendering.QuestEditorMeshManager
|
|||||||
import world.phantasmal.web.questEditor.rendering.QuestRenderer
|
import world.phantasmal.web.questEditor.rendering.QuestRenderer
|
||||||
import world.phantasmal.web.questEditor.rendering.UserInputManager
|
import world.phantasmal.web.questEditor.rendering.UserInputManager
|
||||||
import world.phantasmal.web.questEditor.stores.AreaStore
|
import world.phantasmal.web.questEditor.stores.AreaStore
|
||||||
|
import world.phantasmal.web.questEditor.stores.AssemblyEditorStore
|
||||||
import world.phantasmal.web.questEditor.stores.QuestEditorStore
|
import world.phantasmal.web.questEditor.stores.QuestEditorStore
|
||||||
import world.phantasmal.web.questEditor.widgets.*
|
import world.phantasmal.web.questEditor.widgets.*
|
||||||
import world.phantasmal.webui.DisposableContainer
|
import world.phantasmal.webui.DisposableContainer
|
||||||
@ -33,6 +35,7 @@ class QuestEditor(
|
|||||||
override fun initialize(scope: CoroutineScope): Widget {
|
override fun initialize(scope: CoroutineScope): Widget {
|
||||||
// Renderer
|
// Renderer
|
||||||
val canvas = document.createElement("CANVAS") as HTMLCanvasElement
|
val canvas = document.createElement("CANVAS") as HTMLCanvasElement
|
||||||
|
canvas.style.outline = "none"
|
||||||
val renderer = addDisposable(QuestRenderer(canvas, createEngine(canvas)))
|
val renderer = addDisposable(QuestRenderer(canvas, createEngine(canvas)))
|
||||||
|
|
||||||
// Asset Loaders
|
// Asset Loaders
|
||||||
@ -43,6 +46,7 @@ class QuestEditor(
|
|||||||
// Stores
|
// Stores
|
||||||
val areaStore = addDisposable(AreaStore(scope, areaAssetLoader))
|
val areaStore = addDisposable(AreaStore(scope, areaAssetLoader))
|
||||||
val questEditorStore = addDisposable(QuestEditorStore(scope, uiStore, areaStore))
|
val questEditorStore = addDisposable(QuestEditorStore(scope, uiStore, areaStore))
|
||||||
|
val assemblyEditorStore = addDisposable(AssemblyEditorStore(scope, questEditorStore))
|
||||||
|
|
||||||
// Controllers
|
// Controllers
|
||||||
val toolbarController = addDisposable(QuestEditorToolbarController(
|
val toolbarController = addDisposable(QuestEditorToolbarController(
|
||||||
@ -52,6 +56,7 @@ class QuestEditor(
|
|||||||
))
|
))
|
||||||
val questInfoController = addDisposable(QuestInfoController(questEditorStore))
|
val questInfoController = addDisposable(QuestInfoController(questEditorStore))
|
||||||
val npcCountsController = addDisposable(NpcCountsController(questEditorStore))
|
val npcCountsController = addDisposable(NpcCountsController(questEditorStore))
|
||||||
|
val assemblyEditorController = addDisposable(AssemblyEditorController(assemblyEditorStore))
|
||||||
|
|
||||||
// Rendering
|
// Rendering
|
||||||
addDisposables(
|
addDisposables(
|
||||||
@ -71,7 +76,8 @@ class QuestEditor(
|
|||||||
{ s -> QuestEditorToolbarWidget(s, toolbarController) },
|
{ s -> QuestEditorToolbarWidget(s, toolbarController) },
|
||||||
{ s -> QuestInfoWidget(s, questInfoController) },
|
{ s -> QuestInfoWidget(s, questInfoController) },
|
||||||
{ s -> NpcCountsWidget(s, npcCountsController) },
|
{ s -> NpcCountsWidget(s, npcCountsController) },
|
||||||
{ s -> QuestEditorRendererWidget(s, canvas, renderer) }
|
{ s -> QuestEditorRendererWidget(s, canvas, renderer) },
|
||||||
|
{ s -> AssemblyEditorWidget(s, assemblyEditorController) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
package world.phantasmal.web.questEditor.assembly
|
||||||
|
|
||||||
|
import world.phantasmal.core.disposable.TrackedDisposable
|
||||||
|
|
||||||
|
class AssemblyAnalyser : TrackedDisposable() {
|
||||||
|
fun setAssembly(assembly: List<String>) {
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
package world.phantasmal.web.questEditor.controllers
|
||||||
|
|
||||||
|
import world.phantasmal.observable.value.*
|
||||||
|
import world.phantasmal.web.externals.monacoEditor.ITextModel
|
||||||
|
import world.phantasmal.web.externals.monacoEditor.createModel
|
||||||
|
import world.phantasmal.web.questEditor.stores.AssemblyEditorStore
|
||||||
|
import world.phantasmal.webui.controllers.Controller
|
||||||
|
|
||||||
|
class AssemblyEditorController(assemblyEditorStore: AssemblyEditorStore) : Controller() {
|
||||||
|
val textModel: Val<ITextModel> = assemblyEditorStore.textModel.orElse { EMPTY_MODEL }
|
||||||
|
val enabled: Val<Boolean> = assemblyEditorStore.editingEnabled
|
||||||
|
val readOnly: Val<Boolean> = enabled.not() or assemblyEditorStore.textModel.isNull()
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val EMPTY_MODEL = createModel("", AssemblyEditorStore.ASM_LANG_ID)
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
package world.phantasmal.web.questEditor.models
|
package world.phantasmal.web.questEditor.models
|
||||||
|
|
||||||
|
import world.phantasmal.lib.assembly.Segment
|
||||||
import world.phantasmal.lib.fileFormats.quest.Episode
|
import world.phantasmal.lib.fileFormats.quest.Episode
|
||||||
import world.phantasmal.observable.value.Val
|
import world.phantasmal.observable.value.Val
|
||||||
import world.phantasmal.observable.value.list.ListVal
|
import world.phantasmal.observable.value.list.ListVal
|
||||||
@ -16,6 +17,7 @@ class QuestModel(
|
|||||||
mapDesignations: Map<Int, Int>,
|
mapDesignations: Map<Int, Int>,
|
||||||
npcs: MutableList<QuestNpcModel>,
|
npcs: MutableList<QuestNpcModel>,
|
||||||
objects: MutableList<QuestObjectModel>,
|
objects: MutableList<QuestObjectModel>,
|
||||||
|
val byteCodeIr: List<Segment>,
|
||||||
getVariant: (Episode, areaId: Int, variantId: Int) -> AreaVariantModel?,
|
getVariant: (Episode, areaId: Int, variantId: Int) -> AreaVariantModel?,
|
||||||
) {
|
) {
|
||||||
private val _id = mutableVal(0)
|
private val _id = mutableVal(0)
|
||||||
|
@ -0,0 +1,178 @@
|
|||||||
|
package world.phantasmal.web.questEditor.stores
|
||||||
|
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import world.phantasmal.lib.assembly.disassemble
|
||||||
|
import world.phantasmal.observable.value.Val
|
||||||
|
import world.phantasmal.observable.value.trueVal
|
||||||
|
import world.phantasmal.web.externals.monacoEditor.*
|
||||||
|
import world.phantasmal.webui.obj
|
||||||
|
import world.phantasmal.webui.stores.Store
|
||||||
|
import kotlin.js.RegExp
|
||||||
|
|
||||||
|
class AssemblyEditorStore(
|
||||||
|
scope: CoroutineScope,
|
||||||
|
questEditorStore: QuestEditorStore,
|
||||||
|
) : Store(scope) {
|
||||||
|
private var _textModel: ITextModel? = null
|
||||||
|
|
||||||
|
val inlineStackArgs: Val<Boolean> = trueVal()
|
||||||
|
|
||||||
|
val textModel: Val<ITextModel?> =
|
||||||
|
questEditorStore.currentQuest.map(inlineStackArgs) { quest, inlineArgs ->
|
||||||
|
_textModel?.dispose()
|
||||||
|
|
||||||
|
_textModel =
|
||||||
|
if (quest == null) null
|
||||||
|
else {
|
||||||
|
val assembly = disassemble(quest.byteCodeIr, inlineArgs)
|
||||||
|
createModel(assembly.joinToString("\n"), ASM_LANG_ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
_textModel
|
||||||
|
}
|
||||||
|
|
||||||
|
val editingEnabled: Val<Boolean> = questEditorStore.questEditingEnabled
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val ASM_LANG_ID = "psoasm"
|
||||||
|
|
||||||
|
init {
|
||||||
|
register(obj { id = ASM_LANG_ID })
|
||||||
|
|
||||||
|
setMonarchTokensProvider(ASM_LANG_ID, obj {
|
||||||
|
defaultToken = "invalid"
|
||||||
|
|
||||||
|
tokenizer = obj {
|
||||||
|
this["root"] = arrayOf(
|
||||||
|
// Strings.
|
||||||
|
obj {
|
||||||
|
// Unterminated string.
|
||||||
|
regex = RegExp('"' + """([^"\\]|\.)*$""")
|
||||||
|
action = obj { token = "string.invalid" }
|
||||||
|
},
|
||||||
|
obj {
|
||||||
|
regex = RegExp("\"")
|
||||||
|
action = obj {
|
||||||
|
token = "string.quote"
|
||||||
|
bracket = "@open"
|
||||||
|
next = "@string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Registers.
|
||||||
|
obj {
|
||||||
|
regex = RegExp("""r\d+""")
|
||||||
|
action = obj { token = "predefined" }
|
||||||
|
},
|
||||||
|
|
||||||
|
// Labels.
|
||||||
|
obj {
|
||||||
|
regex = RegExp("""[^\s]+:""")
|
||||||
|
action = obj { token = "tag" }
|
||||||
|
},
|
||||||
|
|
||||||
|
// Numbers.
|
||||||
|
obj {
|
||||||
|
regex = RegExp("""0x[0-9a-fA-F]+""")
|
||||||
|
action = obj { token = "number.hex" }
|
||||||
|
},
|
||||||
|
obj {
|
||||||
|
regex = RegExp("""-?\d+(\.\d+)?(e-?\d+)?""")
|
||||||
|
action = obj { token = "number.float" }
|
||||||
|
},
|
||||||
|
obj {
|
||||||
|
regex = RegExp("""-?[0-9]+""")
|
||||||
|
action = obj { token = "number" }
|
||||||
|
},
|
||||||
|
|
||||||
|
// Section markers.
|
||||||
|
obj {
|
||||||
|
regex = RegExp("""\.[^\s]+""")
|
||||||
|
action = obj { token = "keyword" }
|
||||||
|
},
|
||||||
|
|
||||||
|
// Identifiers.
|
||||||
|
obj {
|
||||||
|
regex = RegExp("""[a-z][a-z0-9_=<>!]*""")
|
||||||
|
action = obj { token = "identifier" }
|
||||||
|
},
|
||||||
|
|
||||||
|
// Whitespace.
|
||||||
|
obj {
|
||||||
|
regex = RegExp("""[ \t\r\n]+""")
|
||||||
|
action = obj { token = "white" }
|
||||||
|
},
|
||||||
|
// obj {
|
||||||
|
// regex = RegExp("""\/\*""")
|
||||||
|
// action = obj { token = "comment"; next = "@comment" }
|
||||||
|
// },
|
||||||
|
obj {
|
||||||
|
regex = RegExp("\\/\\/.*$")
|
||||||
|
action = obj { token = "comment" }
|
||||||
|
},
|
||||||
|
|
||||||
|
// Delimiters.
|
||||||
|
obj {
|
||||||
|
regex = RegExp(",")
|
||||||
|
action = obj { token = "delimiter" }
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// this["comment"] = arrayOf(
|
||||||
|
// obj {
|
||||||
|
// regex = RegExp("""[^/*]+""")
|
||||||
|
// action = obj { token = "comment" }
|
||||||
|
// },
|
||||||
|
// obj {
|
||||||
|
// // Nested comment.
|
||||||
|
// regex = RegExp("""\/\*""")
|
||||||
|
// action = obj { token = "comment"; next = "@push" }
|
||||||
|
// },
|
||||||
|
// obj {
|
||||||
|
// // Nested comment end.
|
||||||
|
// regex = RegExp("""\*/""")
|
||||||
|
// action = obj { token = "comment"; next = "@pop" }
|
||||||
|
// },
|
||||||
|
// obj {
|
||||||
|
// regex = RegExp("""[/*]""")
|
||||||
|
// action = obj { token = "comment" }
|
||||||
|
// },
|
||||||
|
// )
|
||||||
|
|
||||||
|
this["string"] = arrayOf(
|
||||||
|
obj {
|
||||||
|
regex = RegExp("""[^\\"]+""")
|
||||||
|
action = obj { token = "string" }
|
||||||
|
},
|
||||||
|
obj {
|
||||||
|
regex = RegExp("""\\(?:[n\\"])""")
|
||||||
|
action = obj { token = "string.escape" }
|
||||||
|
},
|
||||||
|
obj {
|
||||||
|
regex = RegExp("""\\.""")
|
||||||
|
action = obj { token = "string.escape.invalid" }
|
||||||
|
},
|
||||||
|
obj {
|
||||||
|
regex = RegExp("\"")
|
||||||
|
action = obj {
|
||||||
|
token = "string.quote"
|
||||||
|
bracket = "@close"
|
||||||
|
next = "@pop"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
setLanguageConfiguration(ASM_LANG_ID, obj {
|
||||||
|
indentationRules = obj<IndentationRule> {
|
||||||
|
increaseIndentPattern = RegExp("^\\s*\\d+:")
|
||||||
|
decreaseIndentPattern = RegExp("^\\s*(\\d+|\\.)")
|
||||||
|
}
|
||||||
|
autoClosingPairs = arrayOf(obj { open = "\""; close = "\"" })
|
||||||
|
surroundingPairs = arrayOf(obj { open = "\""; close = "\"" })
|
||||||
|
comments = obj<CommentRule> { lineComment = "//" }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -22,6 +22,7 @@ fun convertQuestToModel(
|
|||||||
// TODO: Add WaveModel to QuestNpcModel
|
// TODO: Add WaveModel to QuestNpcModel
|
||||||
quest.npcs.mapTo(mutableListOf()) { QuestNpcModel(it, null) },
|
quest.npcs.mapTo(mutableListOf()) { QuestNpcModel(it, null) },
|
||||||
quest.objects.mapTo(mutableListOf()) { QuestObjectModel(it) },
|
quest.objects.mapTo(mutableListOf()) { QuestObjectModel(it) },
|
||||||
|
quest.byteCodeIr,
|
||||||
getVariant
|
getVariant
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,70 @@
|
|||||||
|
package world.phantasmal.web.questEditor.widgets
|
||||||
|
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import org.w3c.dom.Node
|
||||||
|
import world.phantasmal.core.disposable.disposable
|
||||||
|
import world.phantasmal.web.externals.monacoEditor.IStandaloneCodeEditor
|
||||||
|
import world.phantasmal.web.externals.monacoEditor.create
|
||||||
|
import world.phantasmal.web.externals.monacoEditor.defineTheme
|
||||||
|
import world.phantasmal.web.externals.monacoEditor.set
|
||||||
|
import world.phantasmal.web.questEditor.controllers.AssemblyEditorController
|
||||||
|
import world.phantasmal.webui.dom.div
|
||||||
|
import world.phantasmal.webui.obj
|
||||||
|
import world.phantasmal.webui.widgets.Widget
|
||||||
|
|
||||||
|
class AssemblyEditorWidget(
|
||||||
|
scope: CoroutineScope,
|
||||||
|
private val ctrl: AssemblyEditorController,
|
||||||
|
) : Widget(scope) {
|
||||||
|
private lateinit var editor: IStandaloneCodeEditor
|
||||||
|
|
||||||
|
override fun Node.createElement() =
|
||||||
|
div {
|
||||||
|
editor = create(this, obj {
|
||||||
|
theme = "phantasmal-world"
|
||||||
|
scrollBeyondLastLine = false
|
||||||
|
autoIndent = "full"
|
||||||
|
fontSize = 13
|
||||||
|
wordWrap = "on"
|
||||||
|
wrappingIndent = "indent"
|
||||||
|
renderIndentGuides = false
|
||||||
|
folding = false
|
||||||
|
})
|
||||||
|
|
||||||
|
addDisposable(disposable { editor.dispose() })
|
||||||
|
|
||||||
|
observe(ctrl.textModel) { editor.setModel(it) }
|
||||||
|
|
||||||
|
observe(ctrl.readOnly) { editor.updateOptions(obj { readOnly = it }) }
|
||||||
|
|
||||||
|
addDisposable(size.observe { (size) ->
|
||||||
|
editor.layout(obj {
|
||||||
|
width = size.width
|
||||||
|
height = size.height
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
init {
|
||||||
|
defineTheme("phantasmal-world", obj {
|
||||||
|
base = "vs-dark"
|
||||||
|
inherit = true
|
||||||
|
rules = arrayOf(
|
||||||
|
obj { token = ""; foreground = "E0E0E0"; background = "#181818" },
|
||||||
|
obj { token = "tag"; foreground = "99BBFF" },
|
||||||
|
obj { token = "keyword"; foreground = "D0A0FF"; fontStyle = "bold" },
|
||||||
|
obj { token = "predefined"; foreground = "BBFFBB" },
|
||||||
|
obj { token = "number"; foreground = "FFFFAA" },
|
||||||
|
obj { token = "number.hex"; foreground = "FFFFAA" },
|
||||||
|
obj { token = "string"; foreground = "88FFFF" },
|
||||||
|
obj { token = "string.escape"; foreground = "8888FF" },
|
||||||
|
)
|
||||||
|
colors = obj {
|
||||||
|
this["editor.background"] = "#181818"
|
||||||
|
this["editor.lineHighlightBackground"] = "#202020"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -26,6 +26,7 @@ class QuestEditorWidget(
|
|||||||
private val createQuestInfoWidget: (CoroutineScope) -> Widget,
|
private val createQuestInfoWidget: (CoroutineScope) -> Widget,
|
||||||
private val createNpcCountsWidget: (CoroutineScope) -> Widget,
|
private val createNpcCountsWidget: (CoroutineScope) -> Widget,
|
||||||
private val createQuestRendererWidget: (CoroutineScope) -> Widget,
|
private val createQuestRendererWidget: (CoroutineScope) -> Widget,
|
||||||
|
private val createAssemblyEditorWidget: (CoroutineScope) -> Widget,
|
||||||
) : Widget(scope) {
|
) : Widget(scope) {
|
||||||
override fun Node.createElement() =
|
override fun Node.createElement() =
|
||||||
div {
|
div {
|
||||||
@ -60,7 +61,7 @@ class QuestEditorWidget(
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
DockedStack(
|
DockedRow(
|
||||||
flex = 9,
|
flex = 9,
|
||||||
items = listOf(
|
items = listOf(
|
||||||
DockedWidget(
|
DockedWidget(
|
||||||
@ -71,7 +72,7 @@ class QuestEditorWidget(
|
|||||||
DockedWidget(
|
DockedWidget(
|
||||||
title = "Script",
|
title = "Script",
|
||||||
id = "asm_editor",
|
id = "asm_editor",
|
||||||
createWidget = ::TestWidget
|
createWidget = createAssemblyEditorWidget
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package world.phantasmal.web.test
|
package world.phantasmal.web.test
|
||||||
|
|
||||||
|
import world.phantasmal.lib.assembly.Segment
|
||||||
import world.phantasmal.lib.fileFormats.quest.Episode
|
import world.phantasmal.lib.fileFormats.quest.Episode
|
||||||
import world.phantasmal.lib.fileFormats.quest.NpcType
|
import world.phantasmal.lib.fileFormats.quest.NpcType
|
||||||
import world.phantasmal.lib.fileFormats.quest.QuestNpc
|
import world.phantasmal.lib.fileFormats.quest.QuestNpc
|
||||||
@ -15,6 +16,7 @@ fun createQuestModel(
|
|||||||
episode: Episode = Episode.I,
|
episode: Episode = Episode.I,
|
||||||
npcs: List<QuestNpcModel> = emptyList(),
|
npcs: List<QuestNpcModel> = emptyList(),
|
||||||
objects: List<QuestObjectModel> = emptyList(),
|
objects: List<QuestObjectModel> = emptyList(),
|
||||||
|
byteCodeIr: List<Segment> = emptyList(),
|
||||||
): QuestModel =
|
): QuestModel =
|
||||||
QuestModel(
|
QuestModel(
|
||||||
id,
|
id,
|
||||||
@ -26,6 +28,7 @@ fun createQuestModel(
|
|||||||
emptyMap(),
|
emptyMap(),
|
||||||
npcs.toMutableList(),
|
npcs.toMutableList(),
|
||||||
objects.toMutableList(),
|
objects.toMutableList(),
|
||||||
|
byteCodeIr,
|
||||||
) { _, _, _ -> null }
|
) { _, _, _ -> null }
|
||||||
|
|
||||||
fun createQuestNpcModel(type: NpcType, episode: Episode): QuestNpcModel =
|
fun createQuestNpcModel(type: NpcType, episode: Episode): QuestNpcModel =
|
||||||
|
4
web/webpack.config.d/webpack.config.js
Normal file
4
web/webpack.config.d/webpack.config.js
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
config.module.rules.push({
|
||||||
|
test: /\.(gif|jpg|png|svg|ttf)$/,
|
||||||
|
loader: "file-loader",
|
||||||
|
});
|
@ -1,4 +1,4 @@
|
|||||||
package world.phantasmal.webui
|
package world.phantasmal.webui
|
||||||
|
|
||||||
fun <T> obj(block: T.() -> Unit): T =
|
inline fun <T> obj(block: T.() -> Unit): T =
|
||||||
js("{}").unsafeCast<T>().apply(block)
|
js("{}").unsafeCast<T>().apply(block)
|
||||||
|
@ -0,0 +1,94 @@
|
|||||||
|
package world.phantasmal.webui.dom
|
||||||
|
|
||||||
|
import org.w3c.dom.HTMLElement
|
||||||
|
import world.phantasmal.core.disposable.Disposable
|
||||||
|
import world.phantasmal.core.disposable.disposable
|
||||||
|
import world.phantasmal.core.unsafeToNonNull
|
||||||
|
import world.phantasmal.observable.Observer
|
||||||
|
import world.phantasmal.observable.value.AbstractVal
|
||||||
|
|
||||||
|
data class Size(val width: Double, val height: Double)
|
||||||
|
|
||||||
|
class HTMLElementSizeVal(element: HTMLElement? = null) : AbstractVal<Size>() {
|
||||||
|
private var resizeObserver: dynamic = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set to true right before actual observers are added.
|
||||||
|
*/
|
||||||
|
private var hasObservers = false
|
||||||
|
|
||||||
|
private var _value: Size? = null
|
||||||
|
|
||||||
|
var element: HTMLElement? = null
|
||||||
|
set(element) {
|
||||||
|
if (resizeObserver != null) {
|
||||||
|
if (field != null) {
|
||||||
|
resizeObserver.unobserve(field)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element != null) {
|
||||||
|
resizeObserver.observe(element)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
field = element
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
// Ensure we call the setter with element.
|
||||||
|
this.element = element
|
||||||
|
}
|
||||||
|
|
||||||
|
override val value: Size
|
||||||
|
get() {
|
||||||
|
if (!hasObservers) {
|
||||||
|
_value = getSize()
|
||||||
|
}
|
||||||
|
|
||||||
|
return _value.unsafeToNonNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun observe(callNow: Boolean, observer: Observer<Size>): Disposable {
|
||||||
|
if (!hasObservers) {
|
||||||
|
hasObservers = true
|
||||||
|
|
||||||
|
if (resizeObserver == null) {
|
||||||
|
@Suppress("UNUSED_VARIABLE")
|
||||||
|
val resize = ::resizeCallback
|
||||||
|
resizeObserver = js("new ResizeObserver(resize);")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element != null) {
|
||||||
|
resizeObserver.observe(element)
|
||||||
|
}
|
||||||
|
|
||||||
|
_value = getSize()
|
||||||
|
}
|
||||||
|
|
||||||
|
val superDisposable = super.observe(callNow, observer)
|
||||||
|
|
||||||
|
return disposable {
|
||||||
|
superDisposable.dispose()
|
||||||
|
|
||||||
|
if (observers.isEmpty()) {
|
||||||
|
hasObservers = false
|
||||||
|
resizeObserver.disconnect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getSize(): Size =
|
||||||
|
element
|
||||||
|
?.let { Size(it.offsetWidth.toDouble(), it.offsetHeight.toDouble()) }
|
||||||
|
?: Size(0.0, 0.0)
|
||||||
|
|
||||||
|
private fun resizeCallback(entries: Array<dynamic>) {
|
||||||
|
entries.forEach { entry ->
|
||||||
|
_value = Size(
|
||||||
|
entry.contentRect.width.unsafeCast<Double>(),
|
||||||
|
entry.contentRect.height.unsafeCast<Double>()
|
||||||
|
)
|
||||||
|
emit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,12 +3,13 @@ package world.phantasmal.webui.widgets
|
|||||||
import kotlinx.browser.document
|
import kotlinx.browser.document
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import org.w3c.dom.*
|
import org.w3c.dom.*
|
||||||
import world.phantasmal.core.disposable.disposable
|
|
||||||
import world.phantasmal.observable.Observable
|
import world.phantasmal.observable.Observable
|
||||||
import world.phantasmal.observable.value.*
|
import world.phantasmal.observable.value.*
|
||||||
import world.phantasmal.observable.value.list.ListVal
|
import world.phantasmal.observable.value.list.ListVal
|
||||||
import world.phantasmal.observable.value.list.ListValChangeEvent
|
import world.phantasmal.observable.value.list.ListValChangeEvent
|
||||||
import world.phantasmal.webui.DisposableContainer
|
import world.phantasmal.webui.DisposableContainer
|
||||||
|
import world.phantasmal.webui.dom.HTMLElementSizeVal
|
||||||
|
import world.phantasmal.webui.dom.Size
|
||||||
|
|
||||||
abstract class Widget(
|
abstract class Widget(
|
||||||
protected val scope: CoroutineScope,
|
protected val scope: CoroutineScope,
|
||||||
@ -25,8 +26,7 @@ abstract class Widget(
|
|||||||
) : DisposableContainer() {
|
) : DisposableContainer() {
|
||||||
private val _ancestorVisible = mutableVal(true)
|
private val _ancestorVisible = mutableVal(true)
|
||||||
private val _children = mutableListOf<Widget>()
|
private val _children = mutableListOf<Widget>()
|
||||||
private var initResizeObserverRequested = false
|
private val _size = HTMLElementSizeVal()
|
||||||
private var resizeObserverInitialized = false
|
|
||||||
|
|
||||||
private val elementDelegate = lazy {
|
private val elementDelegate = lazy {
|
||||||
val el = document.createDocumentFragment().createElement()
|
val el = document.createDocumentFragment().createElement()
|
||||||
@ -54,9 +54,7 @@ abstract class Widget(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (initResizeObserverRequested) {
|
_size.element = el
|
||||||
initResizeObserver(el)
|
|
||||||
}
|
|
||||||
|
|
||||||
interceptElement(el)
|
interceptElement(el)
|
||||||
el
|
el
|
||||||
@ -77,6 +75,8 @@ abstract class Widget(
|
|||||||
*/
|
*/
|
||||||
val selfOrAncestorVisible: Val<Boolean> = visible and ancestorVisible
|
val selfOrAncestorVisible: Val<Boolean> = visible and ancestorVisible
|
||||||
|
|
||||||
|
val size: Val<Size> = _size
|
||||||
|
|
||||||
val children: List<Widget> = _children
|
val children: List<Widget> = _children
|
||||||
|
|
||||||
open fun focus() {
|
open fun focus() {
|
||||||
@ -189,40 +189,6 @@ abstract class Widget(
|
|||||||
spliceChildren(0, 0, list.value)
|
spliceChildren(0, 0, list.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Called whenever [element] is resized.
|
|
||||||
* Must be initialized with [observeResize].
|
|
||||||
*/
|
|
||||||
protected open fun resized(width: Double, height: Double) {}
|
|
||||||
|
|
||||||
protected fun observeResize() {
|
|
||||||
if (elementDelegate.isInitialized()) {
|
|
||||||
initResizeObserver(element)
|
|
||||||
} else {
|
|
||||||
initResizeObserverRequested = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun initResizeObserver(element: Element) {
|
|
||||||
if (resizeObserverInitialized) return
|
|
||||||
|
|
||||||
resizeObserverInitialized = true
|
|
||||||
@Suppress("UNUSED_VARIABLE")
|
|
||||||
val resize = ::resizeCallback
|
|
||||||
val observer = js("new ResizeObserver(resize);")
|
|
||||||
observer.observe(element)
|
|
||||||
addDisposable(disposable { observer.disconnect().unsafeCast<Unit>() })
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun resizeCallback(entries: Array<dynamic>) {
|
|
||||||
entries.forEach { entry ->
|
|
||||||
resized(
|
|
||||||
entry.contentRect.width.unsafeCast<Double>(),
|
|
||||||
entry.contentRect.height.unsafeCast<Double>()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val STYLE_EL by lazy {
|
private val STYLE_EL by lazy {
|
||||||
val el = document.createElement("style") as HTMLStyleElement
|
val el = document.createElement("style") as HTMLStyleElement
|
||||||
|
Loading…
Reference in New Issue
Block a user