Initial commit
commit
037306a7a0
@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
kind: pipeline
|
||||||
|
name: build
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: build
|
||||||
|
image: d.xr.to/jdk
|
||||||
|
command: ./gradle build
|
@ -0,0 +1,3 @@
|
|||||||
|
out
|
||||||
|
build
|
||||||
|
.gradle
|
@ -0,0 +1,10 @@
|
|||||||
|
group 'me.eater.threedom'
|
||||||
|
version '1.0-SNAPSHOT'
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
repositories {
|
||||||
|
jcenter()
|
||||||
|
mavenCentral()
|
||||||
|
maven { url 'https://jitpack.io' }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
<graph>
|
||||||
|
<box diameter="10" position="[33, 40, 3]">
|
||||||
|
|
||||||
|
</box>
|
||||||
|
</graph>
|
@ -0,0 +1,3 @@
|
|||||||
|
kotlin.code.style=official
|
||||||
|
# kapt.incremental.apt=true
|
||||||
|
# kapt.use.worker.api=true
|
Binary file not shown.
@ -0,0 +1,6 @@
|
|||||||
|
#Fri May 15 20:32:10 CEST 2020
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1-all.zip
|
||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
@ -0,0 +1,183 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright 2015 the original author or authors.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
##
|
||||||
|
## Gradle start up script for UN*X
|
||||||
|
##
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
PRG="$0"
|
||||||
|
# Need this for relative symlinks.
|
||||||
|
while [ -h "$PRG" ] ; do
|
||||||
|
ls=`ls -ld "$PRG"`
|
||||||
|
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||||
|
if expr "$link" : '/.*' > /dev/null; then
|
||||||
|
PRG="$link"
|
||||||
|
else
|
||||||
|
PRG=`dirname "$PRG"`"/$link"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
SAVED="`pwd`"
|
||||||
|
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||||
|
APP_HOME="`pwd -P`"
|
||||||
|
cd "$SAVED" >/dev/null
|
||||||
|
|
||||||
|
APP_NAME="Gradle"
|
||||||
|
APP_BASE_NAME=`basename "$0"`
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD="maximum"
|
||||||
|
|
||||||
|
warn () {
|
||||||
|
echo "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
die () {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
nonstop=false
|
||||||
|
case "`uname`" in
|
||||||
|
CYGWIN* )
|
||||||
|
cygwin=true
|
||||||
|
;;
|
||||||
|
Darwin* )
|
||||||
|
darwin=true
|
||||||
|
;;
|
||||||
|
MINGW* )
|
||||||
|
msys=true
|
||||||
|
;;
|
||||||
|
NONSTOP* )
|
||||||
|
nonstop=true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||||
|
else
|
||||||
|
JAVACMD="$JAVA_HOME/bin/java"
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD="java"
|
||||||
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||||
|
MAX_FD_LIMIT=`ulimit -H -n`
|
||||||
|
if [ $? -eq 0 ] ; then
|
||||||
|
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||||
|
MAX_FD="$MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
ulimit -n $MAX_FD
|
||||||
|
if [ $? -ne 0 ] ; then
|
||||||
|
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Darwin, add options to specify how the application appears in the dock
|
||||||
|
if $darwin; then
|
||||||
|
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
|
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||||
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
|
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||||
|
|
||||||
|
# We build the pattern for arguments to be converted via cygpath
|
||||||
|
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||||
|
SEP=""
|
||||||
|
for dir in $ROOTDIRSRAW ; do
|
||||||
|
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||||
|
SEP="|"
|
||||||
|
done
|
||||||
|
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||||
|
# Add a user-defined pattern to the cygpath arguments
|
||||||
|
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||||
|
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||||
|
fi
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
i=0
|
||||||
|
for arg in "$@" ; do
|
||||||
|
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||||
|
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||||
|
|
||||||
|
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||||
|
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||||
|
else
|
||||||
|
eval `echo args$i`="\"$arg\""
|
||||||
|
fi
|
||||||
|
i=`expr $i + 1`
|
||||||
|
done
|
||||||
|
case $i in
|
||||||
|
0) set -- ;;
|
||||||
|
1) set -- "$args0" ;;
|
||||||
|
2) set -- "$args0" "$args1" ;;
|
||||||
|
3) set -- "$args0" "$args1" "$args2" ;;
|
||||||
|
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||||
|
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||||
|
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||||
|
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||||
|
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||||
|
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Escape application args
|
||||||
|
save () {
|
||||||
|
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||||
|
echo " "
|
||||||
|
}
|
||||||
|
APP_ARGS=`save "$@"`
|
||||||
|
|
||||||
|
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||||
|
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||||
|
|
||||||
|
exec "$JAVACMD" "$@"
|
@ -0,0 +1,100 @@
|
|||||||
|
@rem
|
||||||
|
@rem Copyright 2015 the original author or authors.
|
||||||
|
@rem
|
||||||
|
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@rem you may not use this file except in compliance with the License.
|
||||||
|
@rem You may obtain a copy of the License at
|
||||||
|
@rem
|
||||||
|
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
@rem
|
||||||
|
@rem Unless required by applicable law or agreed to in writing, software
|
||||||
|
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@rem See the License for the specific language governing permissions and
|
||||||
|
@rem limitations under the License.
|
||||||
|
@rem
|
||||||
|
|
||||||
|
@if "%DEBUG%" == "" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables with windows NT shell
|
||||||
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%" == "" set DIRNAME=.
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if "%ERRORLEVEL%" == "0" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:init
|
||||||
|
@rem Get command-line arguments, handling Windows variants
|
||||||
|
|
||||||
|
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||||
|
|
||||||
|
:win9xME_args
|
||||||
|
@rem Slurp the command line arguments.
|
||||||
|
set CMD_LINE_ARGS=
|
||||||
|
set _SKIP=2
|
||||||
|
|
||||||
|
:win9xME_args_slurp
|
||||||
|
if "x%~1" == "x" goto execute
|
||||||
|
|
||||||
|
set CMD_LINE_ARGS=%*
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||||
|
|
||||||
|
:end
|
||||||
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||||
|
|
||||||
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
|
rem the _cmd.exe /c_ return code!
|
||||||
|
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||||
|
exit /b 1
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
@ -0,0 +1,39 @@
|
|||||||
|
val items = listOf("xyzw", "rgba", "stpq").map { it.toCharArray() }
|
||||||
|
|
||||||
|
|
||||||
|
val map = mutableMapOf<Int, MutableSet<List<Int>>>()
|
||||||
|
|
||||||
|
for (firstChar in 0..3) {
|
||||||
|
for (secondChar in 0..3) {
|
||||||
|
val char2 = listOf(firstChar, secondChar)
|
||||||
|
map.getOrPut(char2.max()!!, ::mutableSetOf).add(char2)
|
||||||
|
|
||||||
|
for (thirdChar in 0..3) {
|
||||||
|
val char3 = listOf(firstChar, secondChar, thirdChar)
|
||||||
|
map.getOrPut(char3.max()!!, ::mutableSetOf)
|
||||||
|
.add(char3)
|
||||||
|
|
||||||
|
for (fourth in 0..3) {
|
||||||
|
val char4 = listOf(firstChar, secondChar, thirdChar, fourth)
|
||||||
|
map.getOrPut(char4.max()!!, ::mutableSetOf)
|
||||||
|
.add(char4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for ((dim, nrs) in map) {
|
||||||
|
if (dim != 1) {
|
||||||
|
println("Vec${maxOf(dim + 1, 2)} ->")
|
||||||
|
}
|
||||||
|
|
||||||
|
for (item in nrs) {
|
||||||
|
|
||||||
|
for (set in items) {
|
||||||
|
println(
|
||||||
|
" val ${item.map(set::get).joinToString("")}\n get() = KVec${item.size}(${item.map(items[0]::get)
|
||||||
|
.joinToString(",")})\n"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
rootProject.name = 'threedom'
|
||||||
|
|
||||||
|
include 'threedom-kapt'
|
||||||
|
include 'threedom'
|
||||||
|
|
@ -0,0 +1,18 @@
|
|||||||
|
plugins {
|
||||||
|
id 'org.jetbrains.kotlin.jvm' version '1.3.72'
|
||||||
|
}
|
||||||
|
|
||||||
|
group 'me.eater.threedom'
|
||||||
|
version '1.0-SNAPSHOT'
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||||
|
implementation 'com.github.yanex:takenoko:0.1'
|
||||||
|
}
|
||||||
|
|
||||||
|
compileKotlin {
|
||||||
|
kotlinOptions.jvmTarget = "1.8"
|
||||||
|
}
|
||||||
|
compileTestKotlin {
|
||||||
|
kotlinOptions.jvmTarget = "1.8"
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
package me.eater.threedom.kapt
|
||||||
|
|
||||||
|
@Target(AnnotationTarget.CLASS)
|
||||||
|
annotation class EventName(val eventName: String)
|
@ -0,0 +1,98 @@
|
|||||||
|
package me.eater.threedom.kapt
|
||||||
|
|
||||||
|
import org.yanex.takenoko.*
|
||||||
|
import java.io.File
|
||||||
|
import javax.annotation.processing.*
|
||||||
|
import javax.lang.model.SourceVersion
|
||||||
|
import javax.lang.model.element.Element
|
||||||
|
import javax.lang.model.element.TypeElement
|
||||||
|
import javax.tools.Diagnostic.Kind.ERROR
|
||||||
|
|
||||||
|
@SupportedSourceVersion(SourceVersion.RELEASE_8)
|
||||||
|
@SupportedAnnotationTypes("me.eater.threedom.kapt.EventName")
|
||||||
|
@SupportedOptions(EventNameProcessor.KAPT_KOTLIN_GENERATED_OPTION_NAME, "org.gradle.annotation.processing.aggregating")
|
||||||
|
class EventNameProcessor : AbstractProcessor() {
|
||||||
|
companion object {
|
||||||
|
const val KAPT_KOTLIN_GENERATED_OPTION_NAME = "kapt.kotlin.generated"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun process(annotations: MutableSet<out TypeElement>?, roundEnv: RoundEnvironment): Boolean {
|
||||||
|
val annotatedElements = roundEnv.getElementsAnnotatedWith(EventName::class.java)
|
||||||
|
if (annotatedElements.isEmpty()) return false
|
||||||
|
|
||||||
|
val kaptKotlinGeneratedDir = processingEnv.options[KAPT_KOTLIN_GENERATED_OPTION_NAME] ?: run {
|
||||||
|
processingEnv.messager.printMessage(ERROR, "Can't find the target directory for generated Kotlin files.")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
val generatedKtFile = kotlinFile("me.eater.threedom.generated") {
|
||||||
|
objectDeclaration("EventNames") {
|
||||||
|
val eventNames = mutableMapOf<String, String>()
|
||||||
|
|
||||||
|
for (element in annotatedElements) {
|
||||||
|
val typeElement = element.toTypeElementOrNull() ?: continue
|
||||||
|
val eventName = typeElement.getAnnotation(EventName::class.java).eventName
|
||||||
|
|
||||||
|
if (eventName in eventNames) {
|
||||||
|
processingEnv.messager.printMessage(
|
||||||
|
ERROR,
|
||||||
|
"Class ${eventNames[eventName]} already uses the event name '${eventName}'",
|
||||||
|
element
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
eventNames[typeElement.qualifiedName.toString()] = eventName
|
||||||
|
|
||||||
|
property(eventName) {
|
||||||
|
initializer(typeElement.qualifiedName.toString() + "::class")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
property("EVENT_MAPPING") {
|
||||||
|
initializer(
|
||||||
|
"mapOf(\n${eventNames.map { (k, v) -> " \"" + k.replace('"', '\"') + "\" to \"" + v.replace('"', '\"') + '"' }
|
||||||
|
.joinToString(",\n")}\n)"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function("getEventName") {
|
||||||
|
param<String>("eventClass")
|
||||||
|
returnType<String>()
|
||||||
|
|
||||||
|
body {
|
||||||
|
append("return EVENT_MAPPING[eventClass] ?: eventClass")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function(
|
||||||
|
"getEventName", INLINE
|
||||||
|
) {
|
||||||
|
typeParam("reified T")
|
||||||
|
returnType<String>()
|
||||||
|
|
||||||
|
body {
|
||||||
|
append("return getEventName(T::class.java.name)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
File("$kaptKotlinGeneratedDir/me/eater/threedom/generated", "EventNames.kt").apply {
|
||||||
|
parentFile.mkdirs()
|
||||||
|
writeText(generatedKtFile.accept(PrettyPrinter(PrettyPrinterConfiguration())))
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun Element.toTypeElementOrNull(): TypeElement? {
|
||||||
|
if (this !is TypeElement) {
|
||||||
|
processingEnv.messager.printMessage(ERROR, "Invalid element type, class expected", this)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
me.eater.threedom.kapt.EventNameProcessor,dynamic
|
@ -0,0 +1 @@
|
|||||||
|
me.eater.threedom.kapt.EventNameProcessor
|
@ -0,0 +1,37 @@
|
|||||||
|
plugins {
|
||||||
|
id 'org.jetbrains.kotlin.jvm' version '1.3.72'
|
||||||
|
id "org.jetbrains.kotlin.kapt" version "1.3.72"
|
||||||
|
}
|
||||||
|
|
||||||
|
group 'me.eater.threedom'
|
||||||
|
version '1.0-SNAPSHOT'
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
test {
|
||||||
|
useJUnitPlatform()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
kapt project(':threedom-kapt')
|
||||||
|
compileOnly project(':threedom-kapt')
|
||||||
|
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||||
|
|
||||||
|
implementation 'org.joml:joml:1.9.24'
|
||||||
|
|
||||||
|
|
||||||
|
testImplementation 'io.kotest:kotest-runner-junit5-jvm:4.0.5'
|
||||||
|
testImplementation 'io.kotest:kotest-assertions-core-jvm:4.0.5'
|
||||||
|
testImplementation 'io.kotest:kotest-property-jvm:4.0.5'
|
||||||
|
}
|
||||||
|
|
||||||
|
compileKotlin {
|
||||||
|
kotlinOptions.jvmTarget = "1.8"
|
||||||
|
}
|
||||||
|
compileTestKotlin {
|
||||||
|
kotlinOptions.jvmTarget = "1.8"
|
||||||
|
}
|
@ -0,0 +1,125 @@
|
|||||||
|
package me.eater.threedom.dom
|
||||||
|
|
||||||
|
import me.eater.threedom.dom.event.DOMTreeUpdate
|
||||||
|
import me.eater.threedom.dom.event.NodeClassListUpdate
|
||||||
|
import me.eater.threedom.dom.event.NodeIDUpdate
|
||||||
|
import me.eater.threedom.event.Event
|
||||||
|
import me.eater.threedom.generated.EventNames
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
class Document : IDocument {
|
||||||
|
val root: PlainNode = PlainNode(this)
|
||||||
|
private val eventTree = EventTree {
|
||||||
|
on<DOMTreeUpdate.Remove> { (event) ->
|
||||||
|
removeNodeFromSearch(event.child)
|
||||||
|
}
|
||||||
|
|
||||||
|
on<DOMTreeUpdate.Insert> { (event) ->
|
||||||
|
addNodeToSearch(event.child)
|
||||||
|
}
|
||||||
|
|
||||||
|
on<NodeIDUpdate> { (event) ->
|
||||||
|
event.old?.let {
|
||||||
|
byId.remove(it, event.node.nodeId)
|
||||||
|
}
|
||||||
|
|
||||||
|
event.new?.let {
|
||||||
|
byId.putIfAbsent(it, event.node.nodeId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
on<NodeClassListUpdate.Added> { (event) ->
|
||||||
|
for (className in event.classNames) {
|
||||||
|
byClass.getOrPut(className, ::mutableSetOf).add(event.node.nodeId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
on<NodeClassListUpdate.Removed> { (event) ->
|
||||||
|
for (className in event.classNames) {
|
||||||
|
val set = byClass[className] ?: continue
|
||||||
|
|
||||||
|
set.remove(event.node.nodeId)
|
||||||
|
|
||||||
|
if (set.size == 0) {
|
||||||
|
byClass.remove(className)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val allNodes: MutableMap<Long, INode<*>> = mutableMapOf()
|
||||||
|
private val byId: MutableMap<String, Long> = mutableMapOf()
|
||||||
|
private val byClass: MutableMap<String, MutableSet<Long>> = mutableMapOf()
|
||||||
|
|
||||||
|
override fun <T> addEventListener(eventName: String, refNode: INode<*>, block: (Event<T>) -> Unit) {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
eventTree.addEventListener(eventName, refNode, block as (Event<*>) -> Unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> addEventListener(eventName: String, block: (Event<T>) -> Unit) = addEventListener(eventName, root, block)
|
||||||
|
|
||||||
|
fun <T> addTopLevelEventListener(eventName: String, block: (Event<T>) -> Unit) =
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
eventTree.addTopLevelEventListener(eventName, block as (Event<*>) -> Unit)
|
||||||
|
|
||||||
|
override fun trigger(eventName: String, targetNode: INode<*>, event: Event<*>) =
|
||||||
|
eventTree.trigger(eventName, targetNode, event)
|
||||||
|
|
||||||
|
|
||||||
|
override fun addNode(newNode: INode<*>) = root.addNode(newNode)
|
||||||
|
override fun removeNode(refNode: INode<*>) = root.removeNode(refNode)
|
||||||
|
|
||||||
|
override fun deleteNode(refNode: INode<*>) {
|
||||||
|
refNode.detachFromDocument()
|
||||||
|
eventTree.removeNode(refNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun removeAll() = root.removeAll()
|
||||||
|
|
||||||
|
override fun replaceNode(newNode: INode<*>, refNode: INode<*>): Boolean = root.replaceNode(newNode, refNode)
|
||||||
|
|
||||||
|
override fun hasChild(refNode: INode<*>): Boolean = root.hasChild(refNode)
|
||||||
|
|
||||||
|
override fun sequence(): Sequence<INode<*>> = root.sequence()
|
||||||
|
override fun iterator(): Iterator<INode<*>> = root.iterator()
|
||||||
|
|
||||||
|
override fun getNodeById(id: String): INode<*>? = byId[id]?.let(allNodes::get)
|
||||||
|
|
||||||
|
override fun getNodesByClassName(className: String): Sequence<INode<*>> =
|
||||||
|
byClass[className]?.asSequence()?.mapNotNull(allNodes::get) ?: emptySequence()
|
||||||
|
|
||||||
|
|
||||||
|
private fun addNodeToSearch(node: INode<*>) {
|
||||||
|
allNodes[node.nodeId] = node
|
||||||
|
|
||||||
|
node.id?.let { byId.putIfAbsent(it, node.nodeId) }
|
||||||
|
|
||||||
|
for (className in node.classList) {
|
||||||
|
byClass.getOrPut(className, ::mutableSetOf).add(node.nodeId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeNodeFromSearch(node: INode<*>) {
|
||||||
|
allNodes.remove(node.nodeId)
|
||||||
|
|
||||||
|
node.id?.let { byId.remove(it, node.nodeId) }
|
||||||
|
|
||||||
|
for (className in node.classList) {
|
||||||
|
val set = byClass.get(className) ?: continue
|
||||||
|
set.remove(node.nodeId)
|
||||||
|
if (set.size == 0) {
|
||||||
|
byClass.remove(className)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified T> on(topLevel: Boolean = false, noinline block: (Event<T>) -> Unit) = if (topLevel)
|
||||||
|
addTopLevelEventListener(EventNames.getEventName<T>(), block)
|
||||||
|
else
|
||||||
|
addEventListener(EventNames.getEventName<T>(), block)
|
||||||
|
|
||||||
|
override fun <T : INode<T>> createNode(nodeType: KClass<T>): T =
|
||||||
|
nodeType.java.getConstructor(IDocument::class.java).newInstance(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,80 @@
|
|||||||
|
package me.eater.threedom.dom
|
||||||
|
|
||||||
|
import me.eater.threedom.event.Event
|
||||||
|
import me.eater.threedom.generated.EventNames
|
||||||
|
|
||||||
|
class EventTree(block: EventTree.() -> Unit = {}) {
|
||||||
|
private val topLevel: MutableMap<String, MutableSet<(Event<*>) -> Unit>> =
|
||||||
|
mutableMapOf()
|
||||||
|
private val listeners: MutableMap<String, MutableMap<Long, MutableSet<(Event<*>) -> Unit>>> =
|
||||||
|
mutableMapOf()
|
||||||
|
private val nodes: MutableMap<Long, MutableSet<String>> = mutableMapOf()
|
||||||
|
|
||||||
|
init {
|
||||||
|
block(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addTopLevelEventListener(eventName: String, handler: (Event<*>) -> Unit) {
|
||||||
|
this.topLevel.getOrPut(eventName, ::mutableSetOf).add(handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addEventListener(eventName: String, target: INode<*>, handler: (Event<*>) -> Unit) {
|
||||||
|
this.listeners.getOrPut(eventName, ::mutableMapOf).getOrPut(target.nodeId, ::mutableSetOf).add(handler)
|
||||||
|
this.nodes.getOrPut(target.nodeId, ::mutableSetOf).add(eventName)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeNode(node: INode<*>) {
|
||||||
|
val events = this.nodes.remove(node.nodeId) ?: return
|
||||||
|
for (event in events) {
|
||||||
|
this.listeners[event]?.remove(node.nodeId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun trigger(eventName: String, target: INode<*>, event: Event<*>) {
|
||||||
|
this.topLevel[eventName]?.let {
|
||||||
|
for (handler in it) {
|
||||||
|
handler(event)
|
||||||
|
|
||||||
|
if (!event.propagate) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!event.bubble) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val map = this.listeners[eventName] ?: return
|
||||||
|
var current: INode<*>? = target
|
||||||
|
|
||||||
|
while (current != null) {
|
||||||
|
val set = map[current.nodeId]
|
||||||
|
if (set != null) {
|
||||||
|
for (eventHandler in set) {
|
||||||
|
eventHandler(event)
|
||||||
|
|
||||||
|
if (!event.propagate) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!event.bubble) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
current = current.parentNode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
inline fun <reified T> on(target: INode<*>, noinline block: (Event<T>) -> Unit) =
|
||||||
|
addEventListener(EventNames.getEventName<T>(), target, block as (Event<*>) -> Unit)
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
inline fun <reified T> on(noinline block: (Event<T>) -> Unit) =
|
||||||
|
addTopLevelEventListener(EventNames.getEventName<T>(), block as (Event<*>) -> Unit)
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
package me.eater.threedom.dom
|
||||||
|
|
||||||
|
import me.eater.threedom.event.Event
|
||||||
|
import me.eater.threedom.event.EventDispatcher
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
interface IDocument : EventDispatcher, INodeContainer {
|
||||||
|
fun <T : INode<T>> createNode(nodeType: KClass<T>): T
|
||||||
|
fun deleteNode(refNode: INode<*>)
|
||||||
|
fun <T> addEventListener(eventName: String, refNode: INode<*>, block: (Event<T>) -> Unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified T : INode<T>> IDocument.createNode() = createNode(T::class)
|
@ -0,0 +1,41 @@
|
|||||||
|
package me.eater.threedom.dom
|
||||||
|
|
||||||
|
import org.joml.Matrix4d
|
||||||
|
import org.joml.Vector3d
|
||||||
|
import java.util.concurrent.atomic.AtomicLong
|
||||||
|
|
||||||
|
interface INode<T : INode<T>> : Comparable<INode<*>>, INodeContainer {
|
||||||
|
var id: String?
|
||||||
|
val classList: MutableSet<String>
|
||||||
|
val nodeId: Long
|
||||||
|
val parentNode: INode<*>?
|
||||||
|
val document: IDocument?
|
||||||
|
val absolute: Matrix4d
|
||||||
|
get() = (parentNode?.absolute ?: Matrix4d()).mul(model)
|
||||||
|
|
||||||
|
var model: Matrix4d
|
||||||
|
|
||||||
|
fun clone(deep: Boolean): T
|
||||||
|
fun updateParent(refNode: INode<*>?): Boolean
|
||||||
|
fun detachFromDocument()
|
||||||
|
fun hasParent(node: INode<*>): Boolean {
|
||||||
|
var current: INode<*>? = parentNode;
|
||||||
|
|
||||||
|
while (current != null) {
|
||||||
|
if (node.nodeId == current.nodeId) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
current = current.parentNode
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun compareTo(other: INode<*>): Int = this.nodeId.compareTo(other.nodeId)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val atomicNodeId = AtomicLong(0)
|
||||||
|
fun getNextNodeId() = atomicNodeId.getAndIncrement()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
package me.eater.threedom.dom
|
||||||
|
|
||||||
|
interface INodeContainer : INodeQueryCapable {
|
||||||
|
fun addNode(newNode: INode<*>)
|
||||||
|
fun removeNode(refNode: INode<*>)
|
||||||
|
fun removeAll()
|
||||||
|
fun replaceNode(newNode: INode<*>, refNode: INode<*>): Boolean
|
||||||
|
fun hasChild(refNode: INode<*>): Boolean
|
||||||
|
fun sequence(): Sequence<INode<*>>
|
||||||
|
|
||||||
|
operator fun iterator(): Iterator<INode<*>>
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package me.eater.threedom.dom
|
||||||
|
|
||||||
|
import me.eater.threedom.dom.query.NodeQuery
|
||||||
|
|
||||||
|
interface INodeQueryCapable {
|
||||||
|
fun getNodeById(id: String): INode<*>?
|
||||||
|
fun getNodesByClassName(className: String): Sequence<INode<*>>
|
||||||
|
fun find(query: NodeQuery): Sequence<INode<*>> = emptySequence()
|
||||||
|
fun findOne(query: NodeQuery): INode<*>? = find(query).firstOrNull()
|
||||||
|
}
|
@ -0,0 +1,177 @@
|
|||||||
|
package me.eater.threedom.dom
|
||||||
|
|
||||||
|
import me.eater.threedom.dom.event.DOMTreeUpdate
|
||||||
|
import me.eater.threedom.dom.event.NodeClassListUpdate
|
||||||
|
import me.eater.threedom.dom.event.NodeIDUpdate
|
||||||
|
import me.eater.threedom.event.Event
|
||||||
|
import me.eater.threedom.event.EventListener
|
||||||
|
import me.eater.threedom.event.trigger
|
||||||
|
import me.eater.threedom.utils.ObservableSet
|
||||||
|
import org.joml.Matrix4d
|
||||||
|
|
||||||
|
abstract class Node<T : INode<T>>(document: IDocument?) : INode<T>, EventListener {
|
||||||
|
override var document: IDocument? = document
|
||||||
|
protected set
|
||||||
|
override val nodeId = INode.getNextNodeId()
|
||||||
|
private val nodes: MutableSet<INode<*>> = mutableSetOf()
|
||||||
|
override var parentNode: INode<*>? = null
|
||||||
|
protected set
|
||||||
|
|
||||||
|
private var _id: String? = null
|
||||||
|
override var id: String?
|
||||||
|
get() = _id
|
||||||
|
set(value) {
|
||||||
|
val old = _id
|
||||||
|
_id = value
|
||||||
|
|
||||||
|
trigger(NodeIDUpdate(this, old, value))
|
||||||
|
}
|
||||||
|
|
||||||
|
override val classList: MutableSet<String> = ObservableSet {
|
||||||
|
when (it.action) {
|
||||||
|
ObservableSet.Action.Removed -> trigger(NodeClassListUpdate.Removed(it.elements, this))
|
||||||
|
ObservableSet.Action.Added -> trigger(NodeClassListUpdate.Added(it.elements, this))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private inline fun <reified T> trigger(event: T) {
|
||||||
|
if (parentNode !== null) {
|
||||||
|
document?.trigger(Event(event, this))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override var model: Matrix4d = Matrix4d()
|
||||||
|
|
||||||
|
var children: List<INode<*>> = nodes.toList()
|
||||||
|
|
||||||
|
override fun addNode(newNode: INode<*>) {
|
||||||
|
nodes.add(newNode)
|
||||||
|
if (newNode.parentNode != this) {
|
||||||
|
newNode.updateParent(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun removeNode(refNode: INode<*>) {
|
||||||
|
nodes.remove(refNode)
|
||||||
|
if (refNode.parentNode == this) {
|
||||||
|
refNode.updateParent(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun removeAll() {
|
||||||
|
nodes.forEach(::removeNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun replaceNode(newNode: INode<*>, refNode: INode<*>): Boolean {
|
||||||
|
if (nodes.remove(refNode)) {
|
||||||
|
nodes.add(newNode)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun sequence(): Sequence<INode<*>> = nodes.asSequence()
|
||||||
|
override fun iterator(): Iterator<INode<*>> = nodes.iterator()
|
||||||
|
|
||||||
|
fun recursiveSequence(): Sequence<INode<*>> = sequence {
|
||||||
|
val iterators = mutableListOf<Iterator<INode<*>>>()
|
||||||
|
var current = iterator()
|
||||||
|
while (true) {
|
||||||
|
for (node in current) {
|
||||||
|
yield(node)
|
||||||
|
iterators.add(node.iterator())
|
||||||
|
}
|
||||||
|
|
||||||
|
current = iterators.firstOrNull() ?: return@sequence
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected abstract fun cloneSelf(): T
|
||||||
|
|
||||||
|
override fun clone(deep: Boolean): T {
|
||||||
|
val cloned = cloneSelf()
|
||||||
|
|
||||||
|
if (!deep) {
|
||||||
|
return cloned
|
||||||
|
}
|
||||||
|
|
||||||
|
for (node in cloned) {
|
||||||
|
cloned.addNode(node.clone(true))
|
||||||
|
}
|
||||||
|
|
||||||
|
return cloned
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun updateParent(refNode: INode<*>?): Boolean {
|
||||||
|
val parent = parentNode
|
||||||
|
if (parent == refNode) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (refNode != null && !refNode.hasChild(this)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
parentNode = refNode
|
||||||
|
parent?.removeNode(this)
|
||||||
|
|
||||||
|
val event = when {
|
||||||
|
parent == null && refNode != null ->
|
||||||
|
DOMTreeUpdate.Insert(refNode, this).also(::trigger)
|
||||||
|
refNode == null && parent != null -> {
|
||||||
|
val data = DOMTreeUpdate.Remove(parent, this)
|
||||||
|
|
||||||
|
// Trigger on removed as well as on the parent
|
||||||
|
// Since you can't bubble from a detached node
|
||||||
|
var ev = Event(data, this)
|
||||||
|
document?.trigger(this, ev)
|
||||||
|
if (ev.bubble) {
|
||||||
|
document?.trigger(parent, Event(data, this))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trigger on removed as well as on the parent
|
||||||
|
// Since you can't bubble from a detached node
|
||||||
|
ev = Event(data, this)
|
||||||
|
document?.trigger<DOMTreeUpdate>(this, ev)
|
||||||
|
if (ev.bubble) {
|
||||||
|
document?.trigger<DOMTreeUpdate>(parent, Event(data, this))
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
refNode != null && parent != null ->
|
||||||
|
DOMTreeUpdate.Move(parent, refNode, this).also(::trigger)
|
||||||
|
else -> return true
|
||||||
|
}
|
||||||
|
|
||||||
|
trigger(event)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hasChild(refNode: INode<*>) = nodes.contains(refNode)
|
||||||
|
|
||||||
|
override fun <T> on(eventName: String, block: (Event<T>) -> Unit) {
|
||||||
|
document?.addEventListener(eventName, this, block)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun detachFromDocument() {
|
||||||
|
val doc = this.document ?: return
|
||||||
|
this.document = null
|
||||||
|
doc.removeNode(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun finalize() {
|
||||||
|
document?.removeNode(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getNodeById(id: String): INode<*>? = document?.getNodeById(id)?.takeIf { it.hasParent(this) }
|
||||||
|
override fun getNodesByClassName(className: String): Sequence<INode<*>> = if (nodes.isEmpty())
|
||||||
|
emptySequence()
|
||||||
|
else
|
||||||
|
document?.getNodesByClassName(className)?.filter { it.hasParent(this) } ?: emptySequence()
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package me.eater.threedom.dom
|
||||||
|
|
||||||
|
class PlainNode(document: IDocument?) : Node<PlainNode>(document) {
|
||||||
|
override fun cloneSelf(): PlainNode {
|
||||||
|
return PlainNode(document)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
package me.eater.threedom.dom.event
|
||||||
|
|
||||||
|
import me.eater.threedom.dom.INode
|
||||||
|
import me.eater.threedom.kapt.EventName
|
||||||
|
|
||||||
|
@EventName("DOMTreeUpdate")
|
||||||
|
sealed class DOMTreeUpdate {
|
||||||
|
abstract val child: INode<*>
|
||||||
|
|
||||||
|
@EventName("DOMNodeRemove")
|
||||||
|
data class Remove(val parent: INode<*>, override val child: INode<*>) : DOMTreeUpdate()
|
||||||
|
|
||||||
|
@EventName("DOMNodeInsert")
|
||||||
|
data class Insert(val parent: INode<*>, override val child: INode<*>) : DOMTreeUpdate()
|
||||||
|
|
||||||
|
@EventName("DOMNodeMove")
|
||||||
|
data class Move(val oldParent: INode<*>, val newParent: INode<*>, override val child: INode<*>) : DOMTreeUpdate()
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package me.eater.threedom.dom.event
|
||||||
|
|
||||||
|
import me.eater.threedom.dom.INode
|
||||||
|
|
||||||
|
sealed class NodeClassListUpdate {
|
||||||
|
abstract val node: INode<*>
|
||||||
|
|
||||||
|
class Removed(val classNames: Set<String>, override val node: INode<*>): NodeClassListUpdate()
|
||||||
|
class Added(val classNames: Set<String>, override val node: INode<*>): NodeClassListUpdate()
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
package me.eater.threedom.dom.event
|
||||||
|
|
||||||
|
import me.eater.threedom.dom.INode
|
||||||
|
|
||||||
|
data class NodeIDUpdate(val node: INode<*>, val old: String?, val new: String?)
|
@ -0,0 +1,5 @@
|
|||||||
|
package me.eater.threedom.dom.query
|
||||||
|
|
||||||
|
class NodeQuery() {
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
package me.eater.threedom.event
|
||||||
|
|
||||||
|
import me.eater.threedom.dom.INode
|
||||||
|
|
||||||
|
class Event<out T>(val data: T, val source: INode<*>) {
|
||||||
|
|
||||||
|
var bubble = true
|
||||||
|
private set
|
||||||
|
|
||||||
|
var propagate = true
|
||||||
|
private set
|
||||||
|
|
||||||
|
fun stopPropagation() {
|
||||||
|
propagate = false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stopBubbling() {
|
||||||
|
bubble = false
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun component1(): T = data
|
||||||
|
operator fun component2(): INode<*> = source
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
|||||||
|
package me.eater.threedom.event
|
||||||
|
|
||||||
|
interface EventControl {
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
package me.eater.threedom.event
|
||||||
|
|
||||||
|
import me.eater.threedom.dom.INode
|
||||||
|
import me.eater.threedom.generated.EventNames
|
||||||
|
|
||||||
|
interface EventDispatcher {
|
||||||
|
fun trigger(eventName: String, event: Event<*>) = trigger(eventName, event.source, event)
|
||||||
|
fun trigger(eventName: String, targetNode: INode<*>, event: Event<*>)
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified T> EventDispatcher.trigger(event: Event<T>) =
|
||||||
|
trigger(EventNames.getEventName<T>(), event)
|
||||||
|
|
||||||
|
inline fun <reified T> EventDispatcher.trigger(target: INode<*>, event: Event<T>) =
|
||||||
|
trigger(EventNames.getEventName<T>(), target, event)
|
@ -0,0 +1,7 @@
|
|||||||
|
package me.eater.threedom.event
|
||||||
|
|
||||||
|
interface EventListener {
|
||||||
|
fun <T> on(eventName: String, block: (Event<T>) -> Unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified T> EventListener.on(noinline block: (Event<T>) -> Unit) = on(T::class.java.name, block)
|
@ -0,0 +1,45 @@
|
|||||||
|
package me.eater.threedom.utils
|
||||||
|
|
||||||
|
class MutableOrderedSetListIterator<T>(private val collection: OrderedSet<T>, private var index: Int = 0) :
|
||||||
|
MutableListIterator<T> {
|
||||||
|
|
||||||
|
override fun hasPrevious() = collection.size > (index - 1) && index > 1
|
||||||
|
|
||||||
|
override fun nextIndex() = index + 1
|
||||||
|
override fun previous(): T {
|
||||||
|
if (!hasPrevious()) {
|
||||||
|
throw NoSuchElementException()
|
||||||
|
}
|
||||||
|
|
||||||
|
index = previousIndex()
|
||||||
|
return collection[index]
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun previousIndex() = index - 1
|
||||||
|
override fun add(element: T) {
|
||||||
|
collection.add(index, element)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hasNext() = nextIndex() < collection.size
|
||||||
|
|
||||||
|
override fun next(): T {
|
||||||
|
if (!hasNext()) {
|
||||||
|
throw NoSuchElementException()
|
||||||
|
}
|
||||||
|
|
||||||
|
index = nextIndex()
|
||||||
|
return collection[index]
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun remove() {
|
||||||
|
collection.removeAt(index)
|
||||||
|
|
||||||
|
if (index > 0) {
|
||||||
|
index -= 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun set(element: T) {
|
||||||
|
collection[index] = element
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,85 @@
|
|||||||
|
package me.eater.threedom.utils
|
||||||
|
|
||||||
|
class ObservableSet<T>(private val onChange: (Changed<T>) -> Unit) : MutableSet<T> {
|
||||||
|
private val storage: MutableSet<T> = mutableSetOf()
|
||||||
|
|
||||||
|
data class Changed<T>(val action: Action, val elements: Set<T>, val set: ObservableSet<T>)
|
||||||
|
|
||||||
|
enum class Action {
|
||||||
|
Removed,
|
||||||
|
Added;
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun add(element: T): Boolean {
|
||||||
|
if (storage.add(element)) {
|
||||||
|
onChange(Changed(Action.Added, setOf(element), this))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addAll(elements: Collection<T>): Boolean {
|
||||||
|
val items = elements.filter(storage::add)
|
||||||
|
|
||||||
|
if (items.isEmpty()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
onChange(Changed(Action.Added, items.toSet(), this))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun clear() {
|
||||||
|
if (this.isEmpty()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
onChange(Changed(Action.Removed, storage, this))
|
||||||
|
storage.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun iterator(): MutableIterator<T> = storage.iterator()
|
||||||
|
|
||||||
|
override fun remove(element: T): Boolean {
|
||||||
|
if (storage.remove(element)) {
|
||||||
|
onChange(Changed(Action.Removed, setOf(element), this))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun removeAll(elements: Collection<T>): Boolean {
|
||||||
|
val items = elements.filter(storage::remove)
|
||||||
|
|
||||||
|
if (items.isEmpty()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
onChange(Changed(Action.Removed, items.toSet(), this))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun retainAll(elements: Collection<T>): Boolean {
|
||||||
|
val temp = mutableSetOf<T>()
|
||||||
|
for (item in storage) {
|
||||||
|
if (!elements.contains(item)) {
|
||||||
|
temp.add(item)
|
||||||
|
storage.remove(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onChange(Changed(Action.Removed, temp, this))
|
||||||
|
return temp.size > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
override val size: Int
|
||||||
|
get() = storage.size
|
||||||
|
|
||||||
|
override fun contains(element: T) = storage.contains(element)
|
||||||
|
|
||||||
|
override fun containsAll(elements: Collection<T>) = storage.containsAll(elements)
|
||||||
|
|
||||||
|
override fun isEmpty(): Boolean = storage.isEmpty()
|
||||||
|
}
|
@ -0,0 +1,95 @@
|
|||||||
|
package me.eater.threedom.utils
|
||||||
|
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class OrderedSet<T>() : MutableSet<T>, MutableList<T> {
|
||||||
|
override val size
|
||||||
|
get() = items.size
|
||||||
|
|
||||||
|
private val set: MutableSet<T> = mutableSetOf()
|
||||||
|
private val items: MutableList<T> = mutableListOf()
|
||||||
|
|
||||||
|
constructor(elements: Collection<T>) : this() {
|
||||||
|
addAll(elements)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun contains(element: T) = set.contains(element)
|
||||||
|
override fun containsAll(elements: Collection<T>) = set.containsAll(elements)
|
||||||
|
override fun isEmpty() = items.isEmpty()
|
||||||
|
override fun iterator() = items.iterator()
|
||||||
|
override operator fun get(index: Int) = items[index]
|
||||||
|
override fun spliterator(): Spliterator<T> = set.spliterator()
|
||||||
|
override fun indexOf(element: T): Int = items.indexOf(element)
|
||||||
|
override fun lastIndexOf(element: T): Int = indexOf(element)
|
||||||
|
override fun listIterator(): MutableOrderedSetListIterator<T> = MutableOrderedSetListIterator(this)
|
||||||
|
override fun listIterator(index: Int): MutableOrderedSetListIterator<T> = MutableOrderedSetListIterator(this, index)
|
||||||
|
override fun subList(fromIndex: Int, toIndex: Int): OrderedSet<T> = OrderedSet(items.subList(fromIndex, toIndex))
|
||||||
|
|
||||||
|
override fun add(element: T): Boolean {
|
||||||
|
if (set.add(element)) {
|
||||||
|
items.add(element)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addAll(elements: Collection<T>): Boolean {
|
||||||
|
return elements.map(::add).any()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun clear() {
|
||||||
|
set.clear()
|
||||||
|
items.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun remove(element: T): Boolean {
|
||||||
|
if (set.remove(element)) {
|
||||||
|
items.remove(element)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun removeAll(elements: Collection<T>): Boolean {
|
||||||
|
return elements.map(::remove).any()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun retainAll(elements: Collection<T>): Boolean {
|
||||||
|
return set.toSet().map {
|
||||||
|
if (elements.contains(it)) {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
remove(it)
|
||||||
|
}
|
||||||
|
}.any()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun add(index: Int, element: T) {
|
||||||
|
if (set.add(element)) {
|
||||||
|
items.add(index, element)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addAll(index: Int, elements: Collection<T>): Boolean {
|
||||||
|
return items.addAll(index, elements.filter(set::add))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun removeAt(index: Int): T {
|
||||||
|
set.remove(items[index])
|
||||||
|
return items.removeAt(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun set(index: Int, element: T): T {
|
||||||
|
if (!set.add(element)) {
|
||||||
|
return element
|
||||||
|
}
|
||||||
|
|
||||||
|
val old = items[index]
|
||||||
|
items[index] = element
|
||||||
|
remove(old)
|
||||||
|
set.add(element)
|
||||||
|
return old
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
package me.eater.threedom.utils.joml
|
||||||
|
|
||||||
|
import org.joml.Matrix4d
|
||||||
|
import org.joml.Vector3d
|
||||||
|
|
||||||
|
fun <T : Number> Matrix4d.setTranslation(x: T, y: T, z: T): Matrix4d =
|
||||||
|
setTranslation(x.toDouble(), y.toDouble(), z.toDouble())
|
||||||
|
|
||||||
|
fun Matrix4d.getTranslation(): Vector3d = getTranslation(Vector3d())
|
||||||
|
|
||||||
|
@Suppress("FunctionName")
|
||||||
|
fun <T : Number> Vector3d(x: T, y: T, z: T) = Vector3d(x.toDouble(), y.toDouble(), z.toDouble())
|
@ -0,0 +1,134 @@
|
|||||||
|
package me.eater.test.threedom.dom
|
||||||
|
|
||||||
|
import io.kotest.core.spec.style.StringSpec
|
||||||
|
import io.kotest.matchers.sequences.shouldHaveCount
|
||||||
|
import io.kotest.matchers.sequences.shouldHaveSingleElement
|
||||||
|
import io.kotest.matchers.shouldBe
|
||||||
|
import me.eater.threedom.dom.Document
|
||||||
|
import me.eater.threedom.dom.PlainNode
|
||||||
|
import me.eater.threedom.dom.createNode
|
||||||
|
import me.eater.threedom.dom.event.DOMTreeUpdate
|
||||||
|
import me.eater.threedom.dom.event.NodeIDUpdate
|
||||||
|
|
||||||
|
class DocumentTest : StringSpec({
|
||||||
|
"test insert event" {
|
||||||
|
val document = Document()
|
||||||
|
var triggered = 0;
|
||||||
|
document.on<DOMTreeUpdate.Insert> {
|
||||||
|
triggered++
|
||||||
|
}
|
||||||
|
|
||||||
|
document.on<DOMTreeUpdate> {
|
||||||
|
triggered++
|
||||||
|
}
|
||||||
|
|
||||||
|
val newNode = document.createNode<PlainNode>()
|
||||||
|
document.addNode(newNode)
|
||||||
|
|
||||||
|
triggered shouldBe 2
|
||||||
|
}
|
||||||
|
|
||||||
|
"test move event" {
|
||||||
|
val document = Document()
|
||||||
|
|
||||||
|
|
||||||
|
val newNode = document.createNode<PlainNode>()
|
||||||
|
document.addNode(newNode)
|
||||||
|
val secondNode = document.createNode<PlainNode>()
|
||||||
|
document.addNode(secondNode)
|
||||||
|
var triggered = 0
|
||||||
|
|
||||||
|
document.on<DOMTreeUpdate.Move> { (data) ->
|
||||||
|
data.newParent shouldBe secondNode
|
||||||
|
data.oldParent shouldBe document.root
|
||||||
|
data.child shouldBe newNode
|
||||||
|
triggered++
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
secondNode.addNode(newNode)
|
||||||
|
triggered shouldBe 1
|
||||||
|
}
|
||||||
|
|
||||||
|
"test remove event" {
|
||||||
|
val document = Document()
|
||||||
|
var triggered = 0
|
||||||
|
document.on<DOMTreeUpdate.Remove> {
|
||||||
|
triggered++
|
||||||
|
}
|
||||||
|
|
||||||
|
document.on<DOMTreeUpdate> {
|
||||||
|
triggered++
|
||||||
|
}
|
||||||
|
|
||||||
|
val newNode = document.createNode<PlainNode>()
|
||||||
|
document.addNode(newNode)
|
||||||
|
document.removeNode(newNode)
|
||||||
|
|
||||||
|
triggered shouldBe 3
|
||||||
|
}
|
||||||
|
|
||||||
|
"test byId" {
|
||||||
|
val doc = Document()
|
||||||
|
|
||||||
|
val newNode = doc.createNode<PlainNode>()
|
||||||
|
val secondNewNode = doc.createNode<PlainNode>()
|
||||||
|
val thirdNewNode = doc.createNode<PlainNode>()
|
||||||
|
var triggered = 0
|
||||||
|
doc.on<NodeIDUpdate> {
|
||||||
|
triggered++
|
||||||
|
}
|
||||||
|
|
||||||
|
newNode.id = ":3"
|
||||||
|
doc.addNode(secondNewNode)
|
||||||
|
doc.addNode(thirdNewNode)
|
||||||
|
|
||||||
|
doc.getNodeById(":3") shouldBe null
|
||||||
|
thirdNewNode.addNode(newNode)
|
||||||
|
doc.getNodeById(":3") shouldBe newNode
|
||||||
|
secondNewNode.getNodeById(":3") shouldBe null
|
||||||
|
thirdNewNode.getNodeById(":3") shouldBe newNode
|
||||||
|
thirdNewNode.removeAll()
|
||||||
|
thirdNewNode.getNodeById(":3") shouldBe null
|
||||||
|
doc.getNodeById(":3") shouldBe null
|
||||||
|
doc.addNode(newNode)
|
||||||
|
doc.getNodeById(":3") shouldBe newNode
|
||||||
|
newNode.id = ":/"
|
||||||
|
doc.getNodeById(":3") shouldBe null
|
||||||
|
doc.getNodeById(":/") shouldBe newNode
|
||||||
|
}
|
||||||
|
|
||||||
|
"test byClass" {
|
||||||
|
val doc = Document()
|
||||||
|
|
||||||
|
val newNode = doc.createNode<PlainNode>()
|
||||||
|
val secondNewNode = doc.createNode<PlainNode>()
|
||||||
|
val thirdNewNode = doc.createNode<PlainNode>()
|
||||||
|
var triggered = 0
|
||||||
|
doc.on<NodeIDUpdate> {
|
||||||
|
triggered++
|
||||||
|
}
|
||||||
|
|
||||||
|
newNode.classList.add(":3")
|
||||||
|
doc.addNode(secondNewNode)
|
||||||
|
doc.addNode(thirdNewNode)
|
||||||
|
|
||||||
|
doc.getNodesByClassName(":3") shouldHaveCount 0
|
||||||
|
thirdNewNode.addNode(newNode)
|
||||||
|
doc.getNodesByClassName(":3") shouldHaveSingleElement newNode
|
||||||
|
secondNewNode.getNodesByClassName(":3") shouldHaveCount 0
|
||||||
|
thirdNewNode.getNodesByClassName(":3") shouldHaveSingleElement newNode
|
||||||
|
thirdNewNode.removeAll()
|
||||||
|
thirdNewNode.getNodesByClassName(":3") shouldHaveCount 0
|
||||||
|
doc.getNodesByClassName(":3") shouldHaveCount 0
|
||||||
|
newNode.classList.clear()
|
||||||
|
newNode.classList.add(":/")
|
||||||
|
doc.addNode(newNode)
|
||||||
|
doc.getNodesByClassName(":3") shouldHaveCount 0
|
||||||
|
doc.getNodesByClassName(":/") shouldHaveSingleElement newNode
|
||||||
|
newNode.classList.clear()
|
||||||
|
newNode.classList.add(":3")
|
||||||
|
doc.getNodesByClassName(":/") shouldHaveCount 0
|
||||||
|
doc.getNodesByClassName(":3") shouldHaveSingleElement newNode
|
||||||
|
}
|
||||||
|
})
|
@ -0,0 +1,26 @@
|
|||||||
|
package me.eater.test.threedom.dom
|
||||||
|
|
||||||
|
import io.kotest.core.spec.style.StringSpec
|
||||||
|
import io.kotest.matchers.shouldBe
|
||||||
|
import me.eater.threedom.dom.Document
|
||||||
|
import me.eater.threedom.dom.PlainNode
|
||||||
|
import me.eater.threedom.dom.createNode
|
||||||
|
import me.eater.threedom.utils.joml.Vector3d
|
||||||
|
import me.eater.threedom.utils.joml.getTranslation
|
||||||
|
import me.eater.threedom.utils.joml.setTranslation
|
||||||
|
|
||||||
|
class PositionTest : StringSpec({
|
||||||
|
"ensure positioning works" {
|
||||||
|
val doc = Document()
|
||||||
|
val node = doc.createNode<PlainNode>()
|
||||||
|
doc.addNode(node)
|
||||||
|
node.model.setTranslation(10, 0, 10)
|
||||||
|
node.absolute.getTranslation() shouldBe Vector3d(10, 0, 10)
|
||||||
|
val nodeTwo = doc.createNode<PlainNode>()
|
||||||
|
node.addNode(nodeTwo)
|
||||||
|
nodeTwo.model.setTranslation(-10, 20, 0)
|
||||||
|
nodeTwo.absolute.getTranslation() shouldBe Vector3d(0, 20, 10)
|
||||||
|
doc.addNode(nodeTwo)
|
||||||
|
nodeTwo.absolute.getTranslation() shouldBe Vector3d(-10, 20, 0)
|
||||||
|
}
|
||||||
|
})
|
Loading…
Reference in New Issue