diff --git a/build.sbt b/build.sbt index 6072f63..d0f09ae 100644 --- a/build.sbt +++ b/build.sbt @@ -21,6 +21,7 @@ scalaVersion := "2.13.3" resolvers += "Jcenter" at "https://jcenter.bintray.com/" resolvers += "JME Bintray" at "https://bintray.com/jmonkeyengine/com.jme3" +resolvers += "Jitpack" at "https://jitpack.io" resolvers += Resolver.mavenLocal resolvers += Resolver.sonatypeRepo("snapshots") @@ -48,7 +49,7 @@ lazy val root = (project in file(".")).settings( organization := "wow.doge", version := "1.0-SNAPSHOT", libraryDependencies ++= Seq( - "org.scala-lang.modules" %% "scala-parser-combinators" % "1.1.2", + // "org.scala-lang.modules" %% "scala-parser-combinators" % "1.1.2", // https://mvnrepository.com/artifact/org.jmonkeyengine/jme3-core "org.jmonkeyengine" % "jme3-core" % jmeVersion, // https://mvnrepository.com/artifact/org.jmonkeyengine/jme3-desktop @@ -58,23 +59,18 @@ lazy val root = (project in file(".")).settings( "org.jmonkeyengine" % "jme3-effects" % jmeVersion, "org.jmonkeyengine" % "jme3-plugins" % jmeVersion, "org.jmonkeyengine" % "jme3-blender" % jmeVersion, -// https://mvnrepository.com/artifact/com.github.stephengold/Minie "com.github.stephengold" % "Minie" % "3.0.0", -// https://mvnrepository.com/artifact/com.simsilica/zay-es "com.simsilica" % "zay-es" % "1.2.1", "org.typelevel" %% "cats-core" % "2.1.1", "com.lihaoyi" % "ammonite" % "2.2.0" cross CrossVersion.full, "org.jetbrains.kotlin" % "kotlin-main-kts" % "1.4.10", "org.jetbrains.kotlin" % "kotlin-scripting-jsr223" % "1.4.10", "org.codehaus.groovy" % "groovy-all" % "3.0.6" pomOnly (), - // "wow.doge" % "game" % "1.0-SNAPSHOT", "org.scalafx" %% "scalafx" % "14-R19", "com.typesafe.akka" %% "akka-actor-typed" % "2.6.10", - // "ch.qos.logback" % "logback-classic" % "1.2.3", "org.typelevel" %% "cats-core" % "2.1.1", "org.typelevel" %% "cats-effect" % "2.1.4", "io.monix" %% "monix" % "3.2.2", - // "io.monix" %% "monix-bio" % "1.0.0", "io.monix" %% "monix-bio" % "1.1.0", "io.circe" %% "circe-core" % "0.13.0", "io.circe" %% "circe-generic" % "0.13.0", @@ -85,15 +81,21 @@ lazy val root = (project in file(".")).settings( "com.github.valskalla" %% "odin-monix" % "0.8.1", "com.github.valskalla" %% "odin-json" % "0.9.1", "com.softwaremill.macwire" %% "util" % "2.3.7", - "com.softwaremill.macwire" %% "macros" % "2.3.6" % "provided", - "com.softwaremill.macwire" %% "macrosakka" % "2.3.6" % "provided", + "com.softwaremill.macwire" %% "macros" % "2.3.7" % "provided", + // "com.softwaremill.macwire" %% "macrosakka" % "2.3.6" % "provided", "com.github.valskalla" %% "odin-slf4j" % "0.8.1", "com.softwaremill.quicklens" %% "quicklens" % "1.6.1", "org.scala-lang.modules" %% "scala-parallel-collections" % "1.0.0-RC1", "com.typesafe.scala-logging" %% "scala-logging" % "3.9.2", "io.circe" %% "circe-config" % "0.8.0", "com.beachape" %% "enumeratum-circe" % "1.6.1", - "com.lihaoyi" %% "os-lib" % "0.7.1" + "com.lihaoyi" %% "os-lib" % "0.7.1", + // "com.jayfella" % "jme-jfx-11" % "1.1.5", + "com.github.goxr3plus" % "FX-BorderlessScene" % "4.4.0", + "com.github.Oshan96" % "CustomStage" % "v1.3.1", + "com.badlogicgames.gdx" % "gdx-ai" % "1.8.2", + "org.recast4j" % "recast" % "1.2.5", + "org.recast4j" % "detour" % "1.2.5" ), // Determine OS version of JavaFX binaries diff --git a/lib/jme-jfx-11-1.1.5.jar b/lib/jme-jfx-11-1.1.5.jar new file mode 100644 index 0000000..35efab9 Binary files /dev/null and b/lib/jme-jfx-11-1.1.5.jar differ diff --git a/src/main/scala/com/jayfella/jme/jfx/package.scala b/src/main/scala/com/jayfella/jme/jfx/package.scala new file mode 100644 index 0000000..36a431c --- /dev/null +++ b/src/main/scala/com/jayfella/jme/jfx/package.scala @@ -0,0 +1,10 @@ +package com.jayfella.jme + +package object jfx { +// object JavaFxUI { +// def apply(app: Application) = { +// JavaFxUI.initialize(app) +// JavaFxUI.getInstance() +// } +// } +} diff --git a/src/main/scala/com/jme3/animation/package.scala b/src/main/scala/com/jme3/animation/package.scala index ee24f30..d85f05a 100644 --- a/src/main/scala/com/jme3/animation/package.scala +++ b/src/main/scala/com/jme3/animation/package.scala @@ -4,59 +4,69 @@ import com.jme3.input.Action package object animation { - implicit class AnimChannelWrap(private val uval: AnimChannel) extends AnyVal { + implicit final class AnimChannelWrap(private val uval: AnimChannel) + extends AnyVal { /** - * Set the current animation that is played by this AnimChannel. - *

- * See {@link #setAnim(java.lang.String, float)}. - * The blendTime argument by default is 150 milliseconds. - * - * @param action The action (name) of the animation to play - */ - def setAnim(action: Action): Unit = uval.setAnim(action.name) - - /** - * Set the current animation that is played by this AnimChannel. - *

- * This resets the time to zero, and optionally blends the animation - * over blendTime seconds with the currently playing animation. - * Notice that this method will reset the control's speed to 1.0. - * - * @param action The action (name) of the animation to play - * @param blendTime The blend time over which to blend the new animation - * with the old one. If zero, then no blending will occur and the new - * animation will be applied instantly. - */ - def setAnim(action: Action, blendTime: Float): Unit = uval.setAnim(action.name, blendTime) + * Set the current animation that is played by this AnimChannel. + *

+ * See {@link #setAnim(java.lang.String, float)}. + * The blendTime argument by default is 150 milliseconds. + * + * @param action The action (name) of the animation to play + */ + def setAnim(action: Action): Unit = uval.setAnim(action.name) + /** + * Set the current animation that is played by this AnimChannel. + *

+ * This resets the time to zero, and optionally blends the animation + * over blendTime seconds with the currently playing animation. + * Notice that this method will reset the control's speed to 1.0. + * + * @param action The action (name) of the animation to play + * @param blendTime The blend time over which to blend the new animation + * with the old one. If zero, then no blending will occur and the new + * animation will be applied instantly. + */ + def setAnim(action: Action, blendTime: Float): Unit = + uval.setAnim(action.name, blendTime) } - implicit class AnimEventListenerWrap(private val uval: AnimEventListener) extends AnyVal { - + implicit final class AnimEventListenerWrap( + private val uval: AnimEventListener + ) extends AnyVal { + /** - * Invoked when an animation "cycle" is done. For non-looping animations, - * this event is invoked when the animation is finished playing. For - * looping animations, this even is invoked each time the animation is restarted. - * - * @param control The control to which the listener is assigned. - * @param channel The channel being altered - * @param action The new animation action that is done. - */ - def onAnimCycleDone(control: AnimControl, channel: AnimChannel, action: Action): Unit = + * Invoked when an animation "cycle" is done. For non-looping animations, + * this event is invoked when the animation is finished playing. For + * looping animations, this even is invoked each time the animation is restarted. + * + * @param control The control to which the listener is assigned. + * @param channel The channel being altered + * @param action The new animation action that is done. + */ + def onAnimCycleDone( + control: AnimControl, + channel: AnimChannel, + action: Action + ): Unit = uval.onAnimCycleDone(control, channel, action.name) /** - * Invoked when a animation is set to play by the user on the given channel. - * - * @param control The control to which the listener is assigned. - * @param channel The channel being altered - * @param action The new animation action set. - */ - def onAnimChange(control: AnimControl, channel: AnimChannel, action: Action): Unit = - uval.onAnimChange(control, channel, action.name) + * Invoked when a animation is set to play by the user on the given channel. + * + * @param control The control to which the listener is assigned. + * @param channel The channel being altered + * @param action The new animation action set. + */ + def onAnimChange( + control: AnimControl, + channel: AnimChannel, + action: Action + ): Unit = + uval.onAnimChange(control, channel, action.name) } - -} \ No newline at end of file +} diff --git a/src/main/scala/com/jme3/app/package.scala b/src/main/scala/com/jme3/app/package.scala index 658ea99..027b473 100644 --- a/src/main/scala/com/jme3/app/package.scala +++ b/src/main/scala/com/jme3/app/package.scala @@ -5,12 +5,13 @@ package com.jme3 */ package object app { - implicit class SimpleApplicationWrap(private val uval: SimpleApplication) extends AnyVal { + implicit final class SimpleApplicationWrap( + private val uval: SimpleApplication + ) extends AnyVal { //FIXME: proof of concept, remove later def testWrap: String = uval.hashCode().toString - } } diff --git a/src/main/scala/com/jme3/input/controls/package.scala b/src/main/scala/com/jme3/input/controls/package.scala index ac29ed3..803f425 100644 --- a/src/main/scala/com/jme3/input/controls/package.scala +++ b/src/main/scala/com/jme3/input/controls/package.scala @@ -5,31 +5,33 @@ package com.jme3.input */ package object controls { - implicit class ActionListenerWrap(private val uval: ActionListener) extends AnyVal { + implicit final class ActionListenerWrap(private val uval: ActionListener) + extends AnyVal { /** - * Called when an input to which this listener is registered to is invoked. - * - * @param action The action (name) of the mapping that was invoked - * @param isPressed True if the action is "pressed", false otherwise - * @param tpf The time per frame value. - */ + * Called when an input to which this listener is registered to is invoked. + * + * @param action The action (name) of the mapping that was invoked + * @param isPressed True if the action is "pressed", false otherwise + * @param tpf The time per frame value. + */ def onAction(action: Action, keyPressed: Boolean, tpf: Float): Unit = uval.onAction(action.name, keyPressed, tpf) } - implicit class AnalogListenerWrap(private val uval: AnalogListener) extends AnyVal { + implicit final class AnalogListenerWrap(private val uval: AnalogListener) + extends AnyVal { /** - * Called to notify the implementation that an analog event has occurred. - * - * The results of KeyTrigger and MouseButtonTrigger events will have tpf - * == value. - * - * @param action The action (name) of the mapping that was invoked - * @param value Value of the axis, from 0 to 1. - * @param tpf The time per frame value. - */ + * Called to notify the implementation that an analog event has occurred. + * + * The results of KeyTrigger and MouseButtonTrigger events will have tpf + * == value. + * + * @param action The action (name) of the mapping that was invoked + * @param value Value of the axis, from 0 to 1. + * @param tpf The time per frame value. + */ def onAnalog(action: Action, value: Float, tpf: Float): Unit = uval.onAnalog(action.name, value, tpf) } diff --git a/src/main/scala/com/jme3/input/package.scala b/src/main/scala/com/jme3/input/package.scala index fff855f..aa51000 100644 --- a/src/main/scala/com/jme3/input/package.scala +++ b/src/main/scala/com/jme3/input/package.scala @@ -8,7 +8,8 @@ import com.jme3.input.controls.Trigger */ package object input { - implicit class InputManagerWrap(private val uval: InputManager) extends AnyVal { + implicit final class InputManagerWrap(private val uval: InputManager) + extends AnyVal { def addMapping(action: Action, triggers: Trigger*): Unit = uval.addMapping(action.name, triggers: _*) def addListener(listener: InputListener, actions: Action*): Unit = diff --git a/src/main/scala/com/jme3/scene/package.scala b/src/main/scala/com/jme3/scene/package.scala index 3b6698b..59db398 100644 --- a/src/main/scala/com/jme3/scene/package.scala +++ b/src/main/scala/com/jme3/scene/package.scala @@ -33,14 +33,14 @@ package object scene { def apply(name: String): Node = new Node(name) } - implicit class NodeWrap(private val uval: Node) extends AnyVal { + implicit final class NodeWrap(private val uval: Node) extends AnyVal { def getControlMaybe[T <: Control](controlType: Class[T]): Option[T] = Option(uval.getControl(controlType)) } - implicit class SpatialWrap(private val uval: Spatial) extends AnyVal { + implicit final class SpatialWrap(private val uval: Spatial) extends AnyVal { def toNode: Either[ClassCastException, Node] = uval match { diff --git a/src/main/scala/org/slf4j/impl/StaticLoggerBuilder.scala b/src/main/scala/org/slf4j/impl/StaticLoggerBuilder.scala index 529ee98..0cb63ea 100644 --- a/src/main/scala/org/slf4j/impl/StaticLoggerBuilder.scala +++ b/src/main/scala/org/slf4j/impl/StaticLoggerBuilder.scala @@ -69,6 +69,9 @@ class StaticLoggerBinder extends OdinLoggerBinder[IO] { case asyncHttpClient if asyncHttpClient.startsWith("org.asynchttpclient.netty") => defaultConsoleLogger.withMinimalLevel(Level.Warn) + case s if s.startsWith("com.jayfella.jme.jfx.util.JfxPlatform") => + defaultConsoleLogger.withMinimalLevel(Level.Info) + // case s // if s.startsWith( // "wow.doge.mygame.subsystems.movement.PlayerMovementEventHandler" diff --git a/src/main/scala/wow/doge/mygame/Main.scala b/src/main/scala/wow/doge/mygame/Main.scala index e4c89d1..4a0a3b6 100644 --- a/src/main/scala/wow/doge/mygame/Main.scala +++ b/src/main/scala/wow/doge/mygame/Main.scala @@ -2,7 +2,6 @@ package wow.doge.mygame import scala.concurrent.duration._ -import _root_.monix.bio.Task import akka.util.Timeout import cats.effect.ExitCode import cats.implicits._ @@ -11,17 +10,19 @@ import io.odin._ import io.odin.json.Formatter import io.odin.syntax._ import wow.doge.mygame.game.GameAppResource -import wow.doge.mygame.subsystems.scriptsystem.ScriptSystemResource import _root_.monix.bio.BIOApp +import _root_.monix.bio.Task import _root_.monix.bio.UIO import cats.effect.Resource +import scalafx.scene.control.TextArea +import wow.doge.mygame.utils.GenericConsoleStream object Main extends BIOApp with MainModule { import java.util.logging.{Logger => JLogger, Level} JLogger.getLogger("").setLevel(Level.SEVERE) implicit val timeout = Timeout(1.second) - def appResource = + def appResource(consoleStream: GenericConsoleStream[TextArea]) = for { logger <- consoleLogger().withAsync(timeWindow = 1.milliseconds) |+| @@ -31,89 +32,38 @@ object Main extends BIOApp with MainModule { ).withAsync(timeWindow = 1.milliseconds) jmeScheduler <- jMESchedulerResource actorSystem <- actorSystemResource2(logger) - scriptCacheActor <- new ScriptSystemResource(os.pwd, actorSystem)( - timeout, - actorSystem.scheduler - ).make - // akkaScheduler = actorSystemResource2.scheduler // consoleTextArea <- Resource.liftF(Task(new TextArea())) // consoleStream <- wireWith(JFXConsoleStream.textAreaStream _) - (gameApp) <- { + gameApp <- { // new BulletAppState() // bas.setThreadingType(Thr) // gameAppResource(new StatsAppState()) - wire[GameAppResource].get2 + wire[GameAppResource].get } _ <- Resource.liftF( - new MainApp(logger, gameApp, actorSystem, jmeScheduler)( + new MainApp( + logger, + gameApp, + actorSystem, + jmeScheduler, + schedulers, + consoleStream + )( timeout, actorSystem.scheduler - ).gameInit + ).program ) - // fib <- Resource.liftF( - // gameApp - // .enqueueL(() => - // new MainApp(logger, gameApp, actorSystem, jmeScheduler)( - // timeout, - // actorSystem.scheduler - // ) - // ) - // .start - // ) - // _ <- Resource.liftF(fib.join.flatMap(_.gameInit) - // app = gameApp - // inputManager = gameApp.inputManager - // assetManager = gameApp.assetManager - // bulletAppState = new BulletAppState() - // (playerMovementEventBus, playerCameraEventBus) <- new EventsModule2( - // actorSystem - // ).resource - // b1 = playerMovementEventBus - // b2 = playerCameraEventBus - - // playerPos = ImVector3f.ZERO - // playerNode = None.taggedWith[Player] - // modelPath = os.rel / "Models" / "Jaime" / "Jaime.j3o".taggedWith[Player] - // playerController <- Resource.liftF { - // implicit val s = actorSystem.scheduler - // wire[PlayerController.Props].create.onErrorHandle(err => - // logger.error(err.toString()) - // ) - // } - - // gameSystemsInitializerFib <- Resource.make( - // logger.info("creating game systems initializer") >> - // gameApp - // .enqueueL(() => wire[GameSystemsInitializer]) - // .start - // )(c => logger.info("destroying game systems initializer") >> c.cancel) - // _ <- Resource.liftF(gameSystemsInitializerFib.join.flatMap(_.init)) } yield () - // def createPlayerController( - // playerMovementEventBus: ActorRef[ - // EventBus.Command[PlayerMovementEvent] - // ], - // playerCameraEventBus: ActorRef[EventBus.Command[PlayerCameraEvent]] - // ): IO[PlayerController.Error, Unit] = { - // val playerPos = ImVector3f.ZERO - // val playerNode = None.taggedWith[Player] - // val modelPath = os.rel / "Models" / "Jaime" / "Jaime.j3o" - // wire[PlayerController.Props].create - // } - def run(args: List[String]): UIO[ExitCode] = { - // Console.withOut( - // new JFXConsoleStream( - // new scalafx.scene.control.TextArea(), - // new ByteArrayOutputStream(35) - // ) - // )(()) - appResource - .use(_ => Task.unit) - .onErrorHandle(_.printStackTrace()) - .as(ExitCode.Success) + lazy val consoleStream = GenericConsoleStream.textAreaStream() + Console.withOut(consoleStream)( + appResource(consoleStream) + .use(_ => Task.unit) + .onErrorHandle(_.printStackTrace()) + .as(ExitCode.Success) + ) } } diff --git a/src/main/scala/wow/doge/mygame/MainApp.scala b/src/main/scala/wow/doge/mygame/MainApp.scala index 5bed4f7..b287386 100644 --- a/src/main/scala/wow/doge/mygame/MainApp.scala +++ b/src/main/scala/wow/doge/mygame/MainApp.scala @@ -20,7 +20,7 @@ import monix.bio.IO import monix.bio.Task import wow.doge.mygame.events.EventBus import wow.doge.mygame.game.GameApp2 -import wow.doge.mygame.game.nodes.Player +import wow.doge.mygame.game.nodes.PlayerTag import wow.doge.mygame.game.nodes.PlayerController import wow.doge.mygame.game.subsystems.input.GameInputHandler import wow.doge.mygame.implicits._ @@ -32,12 +32,32 @@ import wow.doge.mygame.game.subsystems.level.DefaultGameLevel import com.jme3.renderer.ViewPort import wow.doge.mygame.subsystems.scriptsystem.ScriptSystemResource import wow.doge.mygame.subsystems.scriptsystem.ScriptInitMode +import wow.doge.mygame.launcher.Launcher +import wow.doge.mygame.executors.Schedulers +import scalafx.application.JFXApp.PrimaryStage +import scalafx.geometry.Insets +import scalafx.scene.Scene +import scalafx.scene.control.Button +import scalafx.scene.layout.StackPane +import scalafx.scene.paint.Color +import scalafx.scene.shape.Rectangle +import scalafx.Includes._ +import scala.concurrent.duration._ +import cats.effect.concurrent.Deferred +import monix.bio.Fiber +import wow.doge.mygame.launcher.Launcher.LauncherResult +import scalafx.scene.control.TextArea +import com.jayfella.jme.jfx.JavaFxUI +import wow.doge.mygame.utils.GenericConsoleStream +import java.io.PrintStream class MainApp( logger: Logger[Task], gameApp: GameApp2, spawnProtocol: ActorSystem[SpawnProtocol.Command], - jmeThread: monix.execution.Scheduler + jmeThread: monix.execution.Scheduler, + schedulers: Schedulers, + consoleStream: GenericConsoleStream[TextArea] )(implicit @annotation.unused timeout: Timeout, @annotation.unused scheduler: Scheduler @@ -46,39 +66,96 @@ class MainApp( lazy val scriptSystemInit = new ScriptSystemResource(os.pwd, spawnProtocol, ScriptInitMode.Eager).init - lazy val gameInit: Task[Unit] = for { - eventsModule <- Task(new EventsModule2(spawnProtocol)) - playerMovementEventBus <- eventsModule.playerMovementEventBusTask - playerCameraEventBus <- eventsModule.playerCameraEventBusTask - gameAppFib <- gameApp.start.executeOn(jmeThread).start - /** - * schedule a fiber to run on the JME thread and wait for it's completion - * before proceeding forward, as a signal that JME thread has been - * initialized, otherwise we'll get NPEs trying to access the fields - * of the game app - */ - initFib <- gameApp.enqueueL(() => Task("done")).start - _ <- initFib.join - inputManager <- gameApp.inputManager - assetManager <- gameApp.assetManager - stateManager <- gameApp.stateManager - camera <- gameApp.camera - rootNode <- gameApp.rootNode - enqueueR <- Task(gameApp.enqueue _) - viewPort <- gameApp.viewPort - bulletAppState <- Task(new BulletAppState()) - appScheduler <- Task(gameApp.scheduler) - // enqueueL <- Task(gameApp.enqueueL _) - _ <- wire[MainAppDelegate].init(gameApp.scheduler) - _ <- gameAppFib.join - } yield () + def gameInit: Task[Fiber[Throwable, Unit]] = + for { + eventsModule <- Task(new EventsModule2(spawnProtocol)) + playerMovementEventBus <- eventsModule.playerMovementEventBusTask + playerCameraEventBus <- eventsModule.playerCameraEventBusTask + gameAppFib <- gameApp.start.executeOn(jmeThread).start + /** + * schedule a task to run on the JME thread and wait for it's completion + * before proceeding forward, as a signal that JME thread has been + * initialized, otherwise we'll get NPEs trying to access the fields + * of the game app + */ + res <- gameApp.enqueueL(() => Task("done")).flatten + + _ <- logger.info(s"Result = $res") + inputManager <- gameApp.inputManager + assetManager <- gameApp.assetManager + stateManager <- gameApp.stateManager + camera <- gameApp.camera + rootNode <- gameApp.rootNode + enqueueR <- Task(gameApp.enqueue _) + viewPort <- gameApp.viewPort + _ <- logger.info("before") + // jfxUI <- Task(JavaFxUI.initialize(gameApp.app)) + // .executeOn(gameApp.scheduler) >> Task.sleep(500.millis) >> Task( + // JavaFxUI.getInstance() + // ) + // .start + jfxUI <- gameApp.jfxUI + consoleTextArea <- Task(new TextArea { + text = "hello" + editable = false + wrapText = true + // maxHeight = 150 + // maxWidth = 300 + }) + _ <- Task(consoleStream := consoleTextArea) + _ <- Task(jfxUI += consoleTextArea) + // consoleStream <- Task( + // GenericConsoleStream.textAreaStream(consoleTextArea) + // ) + _ <- logger.info("after") + bulletAppState <- Task(new BulletAppState()) + _ <- Task(stateManager.attach(bulletAppState)) + _ <- logger.info("Initializing console stream") + // _ <- Task(GenericConsoleStream.textAreaStream(consoleTextArea)).bracket( + // consoleStream => + // Task { System.setOut(consoleStream) } >> wire[MainAppDelegate] + // .init(gameApp.scheduler, consoleStream) + // // consoleLogger + // // Console.withOut(consoleStream)( + // // wire[MainAppDelegate].init(gameApp.scheduler, consoleStream) + // // ) + // )(stream => Task(stream.close()).onErrorHandle(_.printStackTrace())) + // _ <- Task { System.setOut(new PrintStream(consoleStream, true)) } + // _ <- Task { + // Console.withOut(consoleStream)(println("hello")) + // } + _ <- wire[MainAppDelegate].init(gameApp.scheduler) + } yield (gameAppFib) lazy val program = for { scriptSystem <- scriptSystemInit - game <- gameInit + /** + * Signal for synchronization between the JavaFX launcher and the in-game JavaFX GUI + * Without this, we get a "Toolkit already initialized" excResult. The launch button + * in the launcher completes the signal. The game init process which listens for this + * signal can then continue + */ + launchSignal <- Deferred[Task, Launcher.LauncherResult] + launcher <- new Launcher.Props(schedulers, launchSignal).create + cancelToken <- launcher.init() + launchResult <- launchSignal.get + _ <- cancelToken.cancel + _ <- + if (launchResult == LauncherResult.Exit) + logger.info("Exiting") + else gameInit.flatMap(_.join) + // _ <- Task.sleep(2000.millis) + // gameAppFib <- gameInit + /** + * Wait for game window to close + */ + // _ <- gameAppFib.join } yield () } +/** + * Class with all dependencies in one place for easy wiring + */ class MainAppDelegate( gameApp: GameApp2, spawnProtocol: ActorSystem[SpawnProtocol.Command], @@ -101,16 +178,20 @@ class MainAppDelegate( @annotation.unused scheduler: Scheduler ) { - def init(appScheduler: monix.execution.Scheduler) = + def init( + appScheduler: monix.execution.Scheduler + // consoleStream: GenericConsoleStream[TextArea] + ) = for { _ <- loggerL.info("Initializing Systems") - _ <- Task(stateManager.attach(bulletAppState)) _ <- Task( assetManager.registerLocator( (os.rel / "assets" / "town.zip"), classOf[ZipLocator] ) ) + _ <- loggerL.info("test hmm") + // _ <- Task(consoleStream.println("text")) _ <- DefaultGameLevel(assetManager, viewPort) .addToGame( rootNode, @@ -131,7 +212,7 @@ class MainAppDelegate( @annotation.unused val playerPos = ImVector3f.ZERO @annotation.unused - val playerNode = None.taggedWith[Player] + val playerNode = None.taggedWith[PlayerTag] @annotation.unused val modelPath = os.rel / "Models" / "Jaime" / "Jaime.j3o" wire[PlayerController.Props].create diff --git a/src/main/scala/wow/doge/mygame/game/GameApp.scala b/src/main/scala/wow/doge/mygame/game/GameApp.scala index b364403..fc29785 100644 --- a/src/main/scala/wow/doge/mygame/game/GameApp.scala +++ b/src/main/scala/wow/doge/mygame/game/GameApp.scala @@ -16,81 +16,26 @@ import wow.doge.mygame.executors.GUIExecutorService import wow.doge.mygame.executors.Schedulers class GameApp( - // actorSystem: ActorSystem[SpawnProtocol.Command], schedulers: Schedulers, appStates: AppState* ) extends SimpleApplication(appStates: _*) { import GameApp._ - // implicit val timeout = Timeout(10.seconds) - // implicit val scheduler = actorSystem.scheduler - // private lazy val taskQueueS = new ConcurrentLinkedQueue[MyTask[_]]() + /** + * A non blocking synchronized queue using an immutable scala queue and monix's atomic class + */ private lazy val taskQueue2 = Atomic(Queue.empty[MyTask[_]]) private val tickSubject = ConcurrentSubject[Float](multicast = MulticastStrategy.publish)( schedulers.async ) - // (scheduler) - - override def simpleInitApp(): Unit = { - - println("gameapp" + Thread.currentThread().getName()) - - // val ship = ed.createEntity() - // val mbState = stateManager().state[EntityDataState]().map(_.getEntityData()) - // val mbState = Try( - // stateManager() - // .state[TestAppState]() - // .entity - // ).toOption.flatten.toRight(println("empty")) - // // .flatMap(_.entity) - // val x = mbState.flatMap( - // _.query - // .filter[TestComponent]("name", new Object()) - // // .filterOr[TestEntity]( - // // Filters - // // .fieldEquals(classOf[TestEntity], "", null) - // // ) - // .component[Tag]() - // .component[TestComponent]() - // .result - // .toRight(println("failed")) - // ) - - // rootNode - // .child(geom) - // .child(geom) - // .child(geom) - // .child(geom) - // .child(geom) - // .child(geom) - // .child(geom) - // .child(geom) - // Future(println("hello"))(jmeEC(this)) - // val wbActor: Future[ActorRef[Greeter.Greet]] = actorSystem.ask( - // SpawnProtocol.Spawn( - // behavior = Greeter(), - // name = "listener", - // DispatcherSelector.fromConfig("jme-dispatcher"), - // _ - // ) - // ) - - // wbActor.map(a => a.ask(Greeter.Greet("hello", _)).map(println)) - - } def tickObservable: Observable[Float] = tickSubject - override def simpleUpdate(tpf: Float): Unit = { - // val rot2 = rot.fromAngleAxis(FastMath.PI, new Vector3f(0, 0, 1)) - // val rotation = geom.getLocalRotation() - // rotation.add(rot2) - // geom.rotate(rot2) + override def simpleInitApp(): Unit = {} - // geom.updateModelBound() - // geom.updateGeometricState() + override def simpleUpdate(tpf: Float): Unit = { tickSubject.onNext(tpf) } @@ -101,32 +46,14 @@ class GameApp( def enqueueScala[T](cb: () => T): CancelableFuture[T] = { val p = Promise[T]() - // p.success(cb()) - // taskQueueS.add(MyTask(p, cb)) taskQueue2.transform(_ :+ MyTask(p, cb)) p.future } -// taskQueue2.transform(_ :+ MyTask(p, cb)) -// p + def enqueueL[T](cb: () => T): Task[T] = - // Task(Promise[T]()).flatMap { p => - // Task(taskQueue2.transform(_ :+ MyTask(p, cb))) >> - // Task.fromCancelablePromise(p) - // } - // Task.fromCancelablePromise { - // val p = Promise[T]() - // taskQueue2.transform(_ :+ MyTask(p, cb)) - // p - // } Task.deferFuture(enqueueScala(cb)) - // taskQueueS.add(MyTask(p, cb)) - override protected def runQueuedTasks(): Unit = { - // Option(taskQueueS.poll()).foreach { - // case MyTask(p, cb) => - // p.success(cb()) - // } taskQueue2.transform { current => current.dequeueOption.fold(current) { case (MyTask(p, cb), updated) => @@ -139,8 +66,9 @@ class GameApp( } object JMEExecutorService extends GUIExecutorService { - override def execute(command: Runnable) = - enqueue(command) + override def execute(command: Runnable): Unit = + enqueueScala(() => command.run()) + // enqueue(command) // new SingleThreadEventExecutor() // sys.addShutdownHook(JMEExecutorService.shutdown()) } @@ -150,3 +78,55 @@ class GameApp( object GameApp { private[game] case class MyTask[T](p: Promise[T], cb: () => T) } + +// val ship = ed.createEntity() +// val mbState = stateManager().state[EntityDataState]().map(_.getEntityData()) +// val mbState = Try( +// stateManager() +// .state[TestAppState]() +// .entity +// ).toOption.flatten.toRight(println("empty")) +// // .flatMap(_.entity) +// val x = mbState.flatMap( +// _.query +// .filter[TestComponent]("name", new Object()) +// // .filterOr[TestEntity]( +// // Filters +// // .fieldEquals(classOf[TestEntity], "", null) +// // ) +// .component[Tag]() +// .component[TestComponent]() +// .result +// .toRight(println("failed")) +// ) + +// rootNode +// .child(geom) +// .child(geom) +// .child(geom) +// .child(geom) +// .child(geom) +// .child(geom) +// .child(geom) +// .child(geom) +// Future(println("hello"))(jmeEC(this)) +// val wbActor: Future[ActorRef[Greeter.Greet]] = actorSystem.ask( +// SpawnProtocol.Spawn( +// behavior = Greeter(), +// name = "listener", +// DispatcherSelector.fromConfig("jme-dispatcher"), +// _ +// ) +// ) + +// wbActor.map(a => a.ask(Greeter.Greet("hello", _)).map(println)) + +// Task(Promise[T]()).flatMap { p => +// Task(taskQueue2.transform(_ :+ MyTask(p, cb))) >> +// Task.fromCancelablePromise(p) +// } +// Task.fromCancelablePromise { +// val p = Promise[T]() +// taskQueue2.transform(_ :+ MyTask(p, cb)) +// p +// } diff --git a/src/main/scala/wow/doge/mygame/game/GameApp2.scala b/src/main/scala/wow/doge/mygame/game/GameApp2.scala index e3c231f..54a9f28 100644 --- a/src/main/scala/wow/doge/mygame/game/GameApp2.scala +++ b/src/main/scala/wow/doge/mygame/game/GameApp2.scala @@ -6,11 +6,16 @@ import com.jme3.asset.AssetManager import com.jme3.input.InputManager import monix.bio.IO import monix.bio.Task +import com.jme3.scene.Node +import monix.catnap.Semaphore +import com.jme3.scene.Spatial +import wow.doge.mygame.game.GameApp2.SynchedObject +import wow.doge.mygame.game.subsystems.ui.JFxUI sealed trait Error case object FlyCamNotExists extends Error -class GameApp2(app: GameApp) { +class GameApp2(val app: GameApp) { def stateManager: Task[AppStateManager] = Task(app.getStateManager()) def inputManager: Task[InputManager] = Task(app.getInputManager()) def assetManager: Task[AssetManager] = Task(app.getAssetManager()) @@ -22,6 +27,7 @@ class GameApp2(app: GameApp) { def camera = Task(app.getCamera()) def viewPort = Task(app.getViewPort()) def rootNode = Ref[Task].of(app.getRootNode()) + def rootNode2 = SynchedObject(app.getRootNode()) def enqueue(cb: () => Unit) = app.enqueue(new Runnable { override def run() = cb() @@ -31,5 +37,35 @@ class GameApp2(app: GameApp) { def start = Task(app.start()) def stop = Task(app.stop()) def scheduler = app.scheduler + def jfxUI = JFxUI(app) } + +object GameApp2 { + + class WrappedNode(node: Node, lock: Semaphore[Task]) { + + def +=(spat: Spatial) = lock.withPermit(Task(node.attachChild(spat))) + } + + /** + * Synchronization wrapper for a mutable object + * + * @param obj the mutable object + * @param lock lock for synchronization + */ + class SynchedObject[A](obj: A, lock: Semaphore[Task]) { + def modify(f: A => Unit): Task[Unit] = + lock.withPermit(Task(f(obj))) + + def flatModify(f: A => Task[Unit]): Task[Unit] = + lock.withPermit(f(obj)) + + def get: Task[A] = lock.withPermit(Task(obj)) + } + + object SynchedObject { + def apply[A](obj: A) = + Semaphore[Task](1).flatMap(lock => Task(new SynchedObject(obj, lock))) + } +} diff --git a/src/main/scala/wow/doge/mygame/game/GameModule.scala b/src/main/scala/wow/doge/mygame/game/GameModule.scala index 7aabcdc..c7378e2 100644 --- a/src/main/scala/wow/doge/mygame/game/GameModule.scala +++ b/src/main/scala/wow/doge/mygame/game/GameModule.scala @@ -17,7 +17,7 @@ class GameAppResource( jmeScheduler: Scheduler, schedulers: Schedulers ) { - def get: Resource[Task, (GameApp2, Fiber[Throwable, Unit])] = + def get2: Resource[Task, (GameApp2, Fiber[Throwable, Unit])] = Resource.make( for { _ <- logger.info("Creating game app") @@ -37,7 +37,7 @@ class GameAppResource( } yield (app2 -> fib) )(logger.info("Closing game app") >> _._2.cancel) - def get2: Resource[Task, GameApp2] = + def get: Resource[Task, GameApp2] = Resource.make( for { _ <- logger.info("Creating game app") @@ -45,9 +45,12 @@ class GameAppResource( app2 <- Task { val settings = new AppSettings(true) settings.setVSync(true) - settings.setUseInput(true) - // new FlyCamAppState - // settings.setFrameRate(250) + + /** + * disables the launcher + * We'll be making our own launcher anyway + */ + app.setShowSettings(false) app.setSettings(settings) // JMERunner.runner = app new GameApp2(app) diff --git a/src/main/scala/wow/doge/mygame/game/GameSystemsInitializer.scala b/src/main/scala/wow/doge/mygame/game/GameSystemsInitializer.scala index 90f1dc7..ead2fc0 100644 --- a/src/main/scala/wow/doge/mygame/game/GameSystemsInitializer.scala +++ b/src/main/scala/wow/doge/mygame/game/GameSystemsInitializer.scala @@ -22,7 +22,7 @@ import monix.bio.IO import monix.bio.Task import monix.reactive.Consumer import wow.doge.mygame.events.EventBus -import wow.doge.mygame.game.nodes.Player +import wow.doge.mygame.game.nodes.PlayerTag import wow.doge.mygame.game.nodes.PlayerController import wow.doge.mygame.game.subsystems.input.GameInputHandler import wow.doge.mygame.implicits._ @@ -86,7 +86,7 @@ class GameSystemsInitializer( @annotation.unused val playerPos = ImVector3f.ZERO @annotation.unused - val playerNode = None.taggedWith[Player] + val playerNode = None.taggedWith[PlayerTag] @annotation.unused val modelPath = os.rel / "Models" / "Jaime" / "Jaime.j3o" wire[PlayerController.Props].create diff --git a/src/main/scala/wow/doge/mygame/game/nodes/PlayerController.scala b/src/main/scala/wow/doge/mygame/game/nodes/PlayerController.scala index 7a69d92..2dcc339 100644 --- a/src/main/scala/wow/doge/mygame/game/nodes/PlayerController.scala +++ b/src/main/scala/wow/doge/mygame/game/nodes/PlayerController.scala @@ -32,7 +32,7 @@ import wow.doge.mygame.subsystems.movement.ImMovementActor import wow.doge.mygame.utils.AkkaUtils // class PlayerNode(val name: String) extends Node(name) {} -sealed trait Player +sealed trait PlayerTag sealed trait PlayerCameraNode object PlayerController { @@ -54,7 +54,7 @@ object PlayerController { ], playerCameraEventBus: ActorRef[EventBus.Command[PlayerCameraEvent]], _playerPhysicsControl: Option[BetterCharacterControl], - _playerNode: Option[Node with Player] = None, + _playerNode: Option[Node with PlayerTag] = None, _cameraNode: Option[CameraNode with PlayerCameraNode] = None, appScheduler: monix.execution.Scheduler )(implicit timeout: Timeout, scheduler: Scheduler) { @@ -68,7 +68,7 @@ object PlayerController { playerPhysicsControl <- IO( _playerPhysicsControl .getOrElse(defaultPlayerPhysicsControl) - .taggedWith[Player] + .taggedWith[PlayerTag] ) playerNode <- IO.fromEither( _playerNode.fold( @@ -179,7 +179,7 @@ object Methods { def spawnMovementActor( enqueueR: Function1[() => Unit, Unit], spawnProtocol: ActorRef[SpawnProtocol.Command], - movable: BetterCharacterControl @@ Player, + movable: BetterCharacterControl @@ PlayerTag, playerMovementEventBus: ActorRef[ EventBus.Command[PlayerMovementEvent] ], diff --git a/src/main/scala/wow/doge/mygame/game/nodes/PlayerEventListeners.scala b/src/main/scala/wow/doge/mygame/game/nodes/PlayerEventListeners.scala index 1b9f7f6..2b49910 100644 --- a/src/main/scala/wow/doge/mygame/game/nodes/PlayerEventListeners.scala +++ b/src/main/scala/wow/doge/mygame/game/nodes/PlayerEventListeners.scala @@ -22,7 +22,7 @@ object PlayerMovementEventListener { Logger[PlayerMovementEventListener.type].underlying ), Behaviors.setup[PlayerMovementEvent](ctx => - Behaviors.receiveMessage { + Behaviors.receiveMessagePartial { case PlayerMovedLeft(pressed) => movementActor ! ImMovementActor.MovedLeft(pressed) Behaviors.same @@ -69,7 +69,7 @@ object PlayerCameraEventListener { Logger[PlayerCameraEventListener.type].underlying ), Behaviors.setup[PlayerCameraEvent](ctx => - Behaviors.receiveMessage { + Behaviors.receiveMessagePartial { case CameraMovedUp => enqueueR(() => { diff --git a/src/main/scala/wow/doge/mygame/game/subsystems/ai/GdxAiTest.scala b/src/main/scala/wow/doge/mygame/game/subsystems/ai/GdxAiTest.scala new file mode 100644 index 0000000..124a72d --- /dev/null +++ b/src/main/scala/wow/doge/mygame/game/subsystems/ai/GdxAiTest.scala @@ -0,0 +1,33 @@ +package wow.doge.mygame.game.subsystems.ai + +import com.badlogic.gdx.ai.pfa.Connection +import wow.doge.mygame.game.subsystems.ai.gdx.MyIndexedGraph +import scala.collection.immutable.ArraySeq +// import com.badlogic.gdx.ai.pfa.indexed.IndexedGraph +// import scala.jdk.javaapi.CollectionConverters._ + +case class City(x: Float, y: Float, name: String, index: Int) +case class Street(fromNode: City, toNode: City, cost: Float) + extends Connection[City] { + + override def getCost(): Float = cost + + override def getFromNode(): City = fromNode + + override def getToNode(): City = toNode + +} + +class CityGraph extends MyIndexedGraph[City] { + + override def getConnections( + city: City + ): IndexedSeq[Connection[City]] = + ArraySeq(Street(City(0f, 0f, "egw", 0), City(0f, 0f, "egw", 0), 1)) + // or Vector(Street(City(0f, 0f, "egw", 0), City(0f, 0f, "egw", 0), 1)) + + override def getIndex(city: City): Int = ??? + + override def getNodeCount(): Int = ??? + +} diff --git a/src/main/scala/wow/doge/mygame/game/subsystems/ai/gdx/Graph.java b/src/main/scala/wow/doge/mygame/game/subsystems/ai/gdx/Graph.java new file mode 100644 index 0000000..e62df12 --- /dev/null +++ b/src/main/scala/wow/doge/mygame/game/subsystems/ai/gdx/Graph.java @@ -0,0 +1,43 @@ +/******************************************************************************* + * Copyright 2014 See AUTHORS file. + * + * 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 + * + * http://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. + ******************************************************************************/ + +package wow.doge.mygame.game.subsystems.ai.gdx; + +import com.badlogic.gdx.ai.pfa.Connection; + +import com.badlogic.gdx.utils.Array; +// import java.lang.Iterable; +import java.util.List; +import scala.collection.immutable.IndexedSeq; + +/** + * A graph is a collection of nodes, each one having a collection of outgoing + * {@link Connection connections}. + * + * @param Type of node + * + * @author davebaol + */ +public interface Graph { + + /** + * Returns the connections outgoing from the given node. + * + * @param fromNode the node whose outgoing connections will be returned + * @return the array of connections outgoing from the given node. + */ + public IndexedSeq> getConnections(N fromNode); +} diff --git a/src/main/scala/wow/doge/mygame/game/subsystems/ai/gdx/IndexedAStarPathFinder.java b/src/main/scala/wow/doge/mygame/game/subsystems/ai/gdx/IndexedAStarPathFinder.java new file mode 100644 index 0000000..85e7e8a --- /dev/null +++ b/src/main/scala/wow/doge/mygame/game/subsystems/ai/gdx/IndexedAStarPathFinder.java @@ -0,0 +1,371 @@ +/******************************************************************************* + * Copyright 2014 See AUTHORS file. + * + * 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 + * + * http://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. + ******************************************************************************/ + +package wow.doge.mygame.game.subsystems.ai.gdx; + +import com.badlogic.gdx.ai.pfa.Connection; +import com.badlogic.gdx.ai.pfa.GraphPath; +import com.badlogic.gdx.ai.pfa.Heuristic; +import com.badlogic.gdx.ai.pfa.PathFinder; +import com.badlogic.gdx.ai.pfa.PathFinderQueue; +import com.badlogic.gdx.ai.pfa.PathFinderRequest; +import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.BinaryHeap; +import com.badlogic.gdx.utils.TimeUtils; +import java.util.List; +import wow.doge.mygame.game.subsystems.ai.gdx.Graph; +import wow.doge.mygame.game.subsystems.ai.gdx.MyIndexedGraph; +import scala.collection.immutable.IndexedSeq; + +/** + * A fully implemented {@link PathFinder} that can perform both interruptible + * and non-interruptible pathfinding. + *

+ * This implementation is a common variation of the A* algorithm that is faster + * than the general A*. + *

+ * In the general A* implementation, data are held for each node in the open or + * closed lists, and these data are held as a NodeRecord instance. Records are + * created when a node is first considered and then moved between the open and + * closed lists, as required. There is a key step in the algorithm where the + * lists are searched for a node record corresponding to a particular node. This + * operation is something time-consuming. + *

+ * The indexed A* algorithm improves execution speed by using an array of all + * the node records for every node in the graph. Nodes must be numbered using + * sequential integers (see {@link MyIndexedGraph#getIndex(Object)}), so we + * don't need to search for a node in the two lists at all. We can simply use + * the node index to look up its record in the array (creating it if it is + * missing). This means that the close list is no longer needed. To know whether + * a node is open or closed, we use the {@link NodeRecord#category category} of + * the node record. This makes the search step very fast indeed (in fact, there + * is no search, and we can go straight to the information we need). + * Unfortunately, we can't get rid of the open list because we still need to be + * able to retrieve the element with the lowest cost. However, we use a + * {@link BinaryHeap} for the open list in order to keep performance as high as + * possible. + * + * @param Type of node + * + * @author davebaol + */ +public class IndexedAStarPathFinder implements PathFinder { + MyIndexedGraph graph; + NodeRecord[] nodeRecords; + BinaryHeap> openList; + NodeRecord current; + public Metrics metrics; + + /** The unique ID for each search run. Used to mark nodes. */ + private int searchId; + + private static final int UNVISITED = 0; + private static final int OPEN = 1; + private static final int CLOSED = 2; + + public IndexedAStarPathFinder(MyIndexedGraph graph) { + this(graph, false); + } + + @SuppressWarnings("unchecked") + public IndexedAStarPathFinder(MyIndexedGraph graph, boolean calculateMetrics) { + this.graph = graph; + this.nodeRecords = (NodeRecord[]) new NodeRecord[graph.getNodeCount()]; + this.openList = new BinaryHeap>(); + if (calculateMetrics) + this.metrics = new Metrics(); + } + + @Override + public boolean searchConnectionPath(N startNode, N endNode, Heuristic heuristic, + GraphPath> outPath) { + + // Perform AStar + boolean found = search(startNode, endNode, heuristic); + + if (found) { + // Create a path made of connections + generateConnectionPath(startNode, outPath); + } + + return found; + } + + @Override + public boolean searchNodePath(N startNode, N endNode, Heuristic heuristic, GraphPath outPath) { + + // Perform AStar + boolean found = search(startNode, endNode, heuristic); + + if (found) { + // Create a path made of nodes + generateNodePath(startNode, outPath); + } + + return found; + } + + protected boolean search(N startNode, N endNode, Heuristic heuristic) { + + initSearch(startNode, endNode, heuristic); + + // Iterate through processing each node + do { + // Retrieve the node with smallest estimated total cost from the open list + current = openList.pop(); + current.category = CLOSED; + + // Terminate if we reached the goal node + if (current.node == endNode) + return true; + + visitChildren(endNode, heuristic); + + } while (openList.size > 0); + + // We've run out of nodes without finding the goal, so there's no solution + return false; + } + + @Override + public boolean search(PathFinderRequest request, long timeToRun) { + + long lastTime = TimeUtils.nanoTime(); + + // We have to initialize the search if the status has just changed + if (request.statusChanged) { + initSearch(request.startNode, request.endNode, request.heuristic); + request.statusChanged = false; + } + + // Iterate through processing each node + do { + + // Check the available time + long currentTime = TimeUtils.nanoTime(); + timeToRun -= currentTime - lastTime; + if (timeToRun <= PathFinderQueue.TIME_TOLERANCE) + return false; + + // Retrieve the node with smallest estimated total cost from the open list + current = openList.pop(); + current.category = CLOSED; + + // Terminate if we reached the goal node; we've found a path. + if (current.node == request.endNode) { + request.pathFound = true; + + generateNodePath(request.startNode, request.resultPath); + + return true; + } + + // Visit current node's children + visitChildren(request.endNode, request.heuristic); + + // Store the current time + lastTime = currentTime; + + } while (openList.size > 0); + + // The open list is empty and we've not found a path. + request.pathFound = false; + return true; + } + + protected void initSearch(N startNode, N endNode, Heuristic heuristic) { + if (metrics != null) + metrics.reset(); + + // Increment the search id + if (++searchId < 0) + searchId = 1; + + // Initialize the open list + openList.clear(); + + // Initialize the record for the start node and add it to the open list + NodeRecord startRecord = getNodeRecord(startNode); + startRecord.node = startNode; + startRecord.connection = null; + startRecord.costSoFar = 0; + addToOpenList(startRecord, heuristic.estimate(startNode, endNode)); + + current = null; + } + + protected void visitChildren(N endNode, Heuristic heuristic) { + // Get current node's outgoing connections + IndexedSeq> connections = graph.getConnections(current.node); + + // Loop through each connection in turn + for (int i = 0; i < connections.size(); i++) { + if (metrics != null) + metrics.visitedNodes++; + + Connection connection = connections.apply(i); + + // Get the cost estimate for the node + N node = connection.getToNode(); + float nodeCost = current.costSoFar + connection.getCost(); + + float nodeHeuristic; + NodeRecord nodeRecord = getNodeRecord(node); + if (nodeRecord.category == CLOSED) { // The node is closed + + // If we didn't find a shorter route, skip + if (nodeRecord.costSoFar <= nodeCost) + continue; + + // We can use the node's old cost values to calculate its heuristic + // without calling the possibly expensive heuristic function + nodeHeuristic = nodeRecord.getEstimatedTotalCost() - nodeRecord.costSoFar; + } else if (nodeRecord.category == OPEN) { // The node is open + + // If our route is no better, then skip + if (nodeRecord.costSoFar <= nodeCost) + continue; + + // Remove it from the open list (it will be re-added with the new cost) + openList.remove(nodeRecord); + + // We can use the node's old cost values to calculate its heuristic + // without calling the possibly expensive heuristic function + nodeHeuristic = nodeRecord.getEstimatedTotalCost() - nodeRecord.costSoFar; + } else { // the node is unvisited + + // We'll need to calculate the heuristic value using the function, + // since we don't have a node record with a previously calculated value + nodeHeuristic = heuristic.estimate(node, endNode); + } + + // Update node record's cost and connection + nodeRecord.costSoFar = nodeCost; + nodeRecord.connection = connection; + + // Add it to the open list with the estimated total cost + addToOpenList(nodeRecord, nodeCost + nodeHeuristic); + } + + } + + protected void generateConnectionPath(N startNode, GraphPath> outPath) { + + // Work back along the path, accumulating connections + // outPath.clear(); + while (current.node != startNode) { + outPath.add(current.connection); + current = nodeRecords[graph.getIndex(current.connection.getFromNode())]; + } + + // Reverse the path + outPath.reverse(); + } + + protected void generateNodePath(N startNode, GraphPath outPath) { + + // Work back along the path, accumulating nodes + // outPath.clear(); + while (current.connection != null) { + outPath.add(current.node); + current = nodeRecords[graph.getIndex(current.connection.getFromNode())]; + } + outPath.add(startNode); + + // Reverse the path + outPath.reverse(); + } + + protected void addToOpenList(NodeRecord nodeRecord, float estimatedTotalCost) { + openList.add(nodeRecord, estimatedTotalCost); + nodeRecord.category = OPEN; + if (metrics != null) { + metrics.openListAdditions++; + metrics.openListPeak = Math.max(metrics.openListPeak, openList.size); + } + } + + protected NodeRecord getNodeRecord(N node) { + int index = graph.getIndex(node); + NodeRecord nr = nodeRecords[index]; + if (nr != null) { + if (nr.searchId != searchId) { + nr.category = UNVISITED; + nr.searchId = searchId; + } + return nr; + } + nr = nodeRecords[index] = new NodeRecord(); + nr.node = node; + nr.searchId = searchId; + return nr; + } + + /** + * This nested class is used to keep track of the information we need for each + * node during the search. + * + * @param Type of node + * + * @author davebaol + */ + static class NodeRecord extends BinaryHeap.Node { + /** The reference to the node. */ + N node; + + /** The incoming connection to the node */ + Connection connection; + + /** The actual cost from the start node. */ + float costSoFar; + + /** The node category: {@link #UNVISITED}, {@link #OPEN} or {@link #CLOSED}. */ + int category; + + /** ID of the current search. */ + int searchId; + + /** Creates a {@code NodeRecord}. */ + public NodeRecord() { + super(0); + } + + /** Returns the estimated total cost. */ + public float getEstimatedTotalCost() { + return getValue(); + } + } + + /** + * A class used by {@link IndexedAStarPathFinder} to collect search metrics. + * + * @author davebaol + */ + public static class Metrics { + public int visitedNodes; + public int openListAdditions; + public int openListPeak; + + public Metrics() { + } + + public void reset() { + visitedNodes = 0; + openListAdditions = 0; + openListPeak = 0; + } + } +} diff --git a/src/main/scala/wow/doge/mygame/game/subsystems/ai/gdx/MyIndexedGraph.java b/src/main/scala/wow/doge/mygame/game/subsystems/ai/gdx/MyIndexedGraph.java new file mode 100644 index 0000000..652700b --- /dev/null +++ b/src/main/scala/wow/doge/mygame/game/subsystems/ai/gdx/MyIndexedGraph.java @@ -0,0 +1,41 @@ +/******************************************************************************* + * Copyright 2014 See AUTHORS file. + * + * 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 + * + * http://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. + ******************************************************************************/ + +package wow.doge.mygame.game.subsystems.ai.gdx; + +import wow.doge.mygame.game.subsystems.ai.gdx.Graph; + +/** + * A graph for the {@link IndexedAStarPathFinder}. + * + * @param Type of node + * + * @author davebaol + */ +public interface MyIndexedGraph extends Graph { + + /** + * Returns the unique index of the given node. + * + * @param node the node whose index will be returned + * @return the unique index of the given node. + */ + public int getIndex(N node); + + /** Returns the number of nodes in this graph. */ + public int getNodeCount(); + +} diff --git a/src/main/scala/wow/doge/mygame/game/subsystems/input/GameInputHandler.scala b/src/main/scala/wow/doge/mygame/game/subsystems/input/GameInputHandler.scala index ca82781..b8457d7 100644 --- a/src/main/scala/wow/doge/mygame/game/subsystems/input/GameInputHandler.scala +++ b/src/main/scala/wow/doge/mygame/game/subsystems/input/GameInputHandler.scala @@ -65,30 +65,6 @@ object GameInputHandler { def setupKeys(inputManager: InputManager) = inputManager - // .withMapping( - // PlayerMovementInput.WalkLeft.entryName, - // new KeyTrigger(KeyInput.KEY_A) - // // new KeyTrigger(KeyInput.KEY_LEFT) - // ) - // .withMapping( - // PlayerMovementInput.WalkRight.entryName, - // new KeyTrigger(KeyInput.KEY_D) - // // new KeyTrigger(KeyInput.KEY_RIGHT) - // ) - // .withMapping( - // PlayerMovementInput.WalkForward.entryName, - // new KeyTrigger(KeyInput.KEY_W) - // // new KeyTrigger(KeyInput.KEY_UP) - // ) - // .withMapping( - // PlayerMovementInput.WalkBackward.entryName, - // new KeyTrigger(KeyInput.KEY_S) - // // new KeyTrigger(KeyInput.KEY_DOWN) - // ) - // .withMapping( - // PlayerMovementInput.Jump.entryName, - // new KeyTrigger(KeyInput.KEY_SPACE) - // ) .withMapping( PlayerAnalogInput.TurnRight.entryName, new KeyTrigger(KeyInput.KEY_RIGHT), diff --git a/src/main/scala/wow/doge/mygame/game/subsystems/level/DefaultGameLevel.scala b/src/main/scala/wow/doge/mygame/game/subsystems/level/DefaultGameLevel.scala index fb55178..d1eeacc 100644 --- a/src/main/scala/wow/doge/mygame/game/subsystems/level/DefaultGameLevel.scala +++ b/src/main/scala/wow/doge/mygame/game/subsystems/level/DefaultGameLevel.scala @@ -17,8 +17,8 @@ object DefaultGameLevel { lazy val sceneModel: Spatial = assetManager.loadModel("main.scene") lazy val sceneShape = CollisionShapeFactory.createMeshShape( sceneModel.toNode match { - case util.Right(node) => node - case util.Left(ex) => + case Right(node) => node + case Left(ex) => throw new NotImplementedError("No fallback sceneshape") } ) @@ -40,7 +40,7 @@ object DefaultGameLevel { dl.setColor(ColorRGBA.White); dl.setDirection(new Vector3f(2.8f, -2.8f, -2.8f).normalizeLocal()); // app.rootNode.addLight(dl); - new Level( + new GameLevel( model = sceneModel, physicsControl = landscape, ambientLight = al, diff --git a/src/main/scala/wow/doge/mygame/game/subsystems/level/Level.scala b/src/main/scala/wow/doge/mygame/game/subsystems/level/GameLevel.scala similarity index 79% rename from src/main/scala/wow/doge/mygame/game/subsystems/level/Level.scala rename to src/main/scala/wow/doge/mygame/game/subsystems/level/GameLevel.scala index a5ca94e..c260071 100644 --- a/src/main/scala/wow/doge/mygame/game/subsystems/level/Level.scala +++ b/src/main/scala/wow/doge/mygame/game/subsystems/level/GameLevel.scala @@ -10,7 +10,7 @@ import monix.bio.Task import com.jme3.scene.Node import wow.doge.mygame.implicits._ -class Level( +class GameLevel( model: Spatial, physicsControl: RigidBodyControl, ambientLight: AmbientLight, @@ -19,14 +19,8 @@ class Level( def addToGame(rootNode: Ref[Task, Node], physicsSpace: PhysicsSpace) = { for { _ <- rootNode.update(_ :+ model) - _ <- rootNode.update { r => - r.addLight(ambientLight) - r - } - _ <- rootNode.update { r => - r.addLight(directionalLight) - r - } + _ <- rootNode.update(_ :+ ambientLight) + _ <- rootNode.update(_ :+ directionalLight) _ <- Task(physicsSpace += model) _ <- Task(physicsSpace += physicsControl) } yield () diff --git a/src/main/scala/wow/doge/mygame/game/subsystems/ui/JmeJfx.scala b/src/main/scala/wow/doge/mygame/game/subsystems/ui/JmeJfx.scala new file mode 100644 index 0000000..5bf3cfc --- /dev/null +++ b/src/main/scala/wow/doge/mygame/game/subsystems/ui/JmeJfx.scala @@ -0,0 +1,43 @@ +package wow.doge.mygame.game.subsystems.ui + +import com.jme3.app.Application +import com.jayfella.jme.jfx.JavaFxUI +import scalafx.application.Platform +import monix.execution.CancelablePromise +import monix.bio.Task +import cats.effect.concurrent.Deferred +import scala.concurrent.duration._ +import wow.doge.mygame.game.GameApp + +object JFxUI { + def apply(app: GameApp) = + Task(JavaFxUI.initialize(app)) + .executeOn(app.scheduler) >> Task.sleep(500.millis) >> Task( + JavaFxUI.getInstance() + ) + // Task { + // Platform.runLater(() => { + // println("here jfx") + // JavaFxUI.initialize(app) + // println("here2 jfx2") + // val inst = JavaFxUI.getInstance() + // println(inst) + // }) + // } + // Task.fromFuture { + // val p = CancelablePromise[JavaFxUI]() + // Platform.runLater(() => { + // println("here") + // JavaFxUI.initialize(app) + // println("here2") + // val inst = JavaFxUI.getInstance() + // println(inst) + // p.success(inst) + // }) + // p.future + // } +// for { +// d <- Deferred[Task, JavaFxUI] +// _ <- Task(JavaFxUI.initialize(app)) +// } yield () +} diff --git a/src/main/scala/wow/doge/mygame/implicits/JavaFXMonixObservables.scala b/src/main/scala/wow/doge/mygame/implicits/JavaFXMonixObservables.scala new file mode 100644 index 0000000..34b69d6 --- /dev/null +++ b/src/main/scala/wow/doge/mygame/implicits/JavaFXMonixObservables.scala @@ -0,0 +1,88 @@ +package wow.doge.mygame.implicits + +import javafx.{ + collections => jfxc, + event => jfxe, + geometry => jfxg, + scene => jfxs, + util => jfxu +} +import javafx.scene.{input => jfxsi, layout => jfxsl, paint => jfxsp} +import scalafx.scene.Scene +import monix.execution.Cancelable +import monix.reactive.OverflowStrategy +import monix.reactive.Observable +import monix.execution.Ack +import scalafx.scene.control.ButtonBase + +object JavaFXMonixObservables { + + implicit final class SceneObservables(private val scene: Scene) + extends AnyVal { + def observableMousePressed(): Observable[jfxsi.MouseEvent] = { + import monix.execution.cancelables.SingleAssignCancelable + Observable.create(OverflowStrategy.Unbounded) { sub => + val c = SingleAssignCancelable() + val l = new jfxe.EventHandler[jfxsi.MouseEvent] { + override def handle(event: jfxsi.MouseEvent): Unit = { + if (sub.onNext(event) == Ack.Stop) c.cancel() + } + } + + scene.onMousePressed = l + c := Cancelable(() => + scene.removeEventHandler( + jfxsi.MouseEvent.MOUSE_PRESSED, + l + ) + ) + c + } + } + def observableMouseDragged(): Observable[jfxsi.MouseEvent] = { + import monix.execution.cancelables.SingleAssignCancelable + Observable.create(OverflowStrategy.Unbounded) { sub => + val c = SingleAssignCancelable() + val l = new jfxe.EventHandler[jfxsi.MouseEvent] { + override def handle(event: jfxsi.MouseEvent): Unit = { + if (sub.onNext(event) == Ack.Stop) c.cancel() + } + } + + scene.onMouseDragged = l + c := Cancelable(() => + scene.removeEventHandler( + jfxsi.MouseEvent.MOUSE_DRAGGED, + l + ) + ) + c + } + } + } + + implicit final class OnActionObservable( + private val button: ButtonBase + ) extends AnyVal { + def observableAction(): Observable[jfxe.ActionEvent] = { + import monix.execution.cancelables.SingleAssignCancelable + Observable.create(OverflowStrategy.Unbounded) { sub => + val c = SingleAssignCancelable() + val l = new jfxe.EventHandler[jfxe.ActionEvent] { + override def handle(event: jfxe.ActionEvent): Unit = { + if (sub.onNext(event) == Ack.Stop) c.cancel() + } + } + + button.onAction = l + c := Cancelable(() => + button.removeEventHandler( + jfxe.ActionEvent.ACTION, + l + ) + ) + c + } + } + } +} diff --git a/src/main/scala/wow/doge/mygame/implicits/TestEnum.scala b/src/main/scala/wow/doge/mygame/implicits/TestEnum.scala deleted file mode 100644 index d568b2b..0000000 --- a/src/main/scala/wow/doge/mygame/implicits/TestEnum.scala +++ /dev/null @@ -1,43 +0,0 @@ -package wow.doge.mygame.implicits - -import enumeratum._ - -sealed trait TestEnum extends EnumEntry - -object TestEnum extends Enum[TestEnum] { - val values = findValues - case object Test2 extends TestEnum -} - -sealed trait Greeting extends EnumEntry - -object Greeting extends Enum[Greeting] { - - /* - `findValues` is a protected method that invokes a macro to find all `Greeting` object declarations inside an `Enum` - - You use it to implement the `val values` member - */ - val values = findValues - - case object Hello extends Greeting - case object GoodBye extends Greeting - case object Hi extends Greeting - case object Bye extends Greeting - -} -object ObsTest {} - -sealed trait PlayerMovementEnum extends EnumEntry { - def test: String -} - -object PlayerMovementEnum extends Enum[PlayerMovementEnum] { - val values = findValues - case object MOVE_RIGHT extends PlayerMovementEnum { - val test = "hmm" - } - case object MOVE_LEFT extends PlayerMovementEnum { - val test = "mmh" - } -} diff --git a/src/main/scala/wow/doge/mygame/implicits/observables/package.scala b/src/main/scala/wow/doge/mygame/implicits/observables/package.scala new file mode 100644 index 0000000..d587ffe --- /dev/null +++ b/src/main/scala/wow/doge/mygame/implicits/observables/package.scala @@ -0,0 +1,18 @@ +package wow.doge.mygame.implicits + +import javafx.{ + collections => jfxc, + event => jfxe, + geometry => jfxg, + scene => jfxs, + util => jfxu +} +import javafx.scene.{input => jfxsi, layout => jfxsl, paint => jfxsp} +import scalafx.scene.Scene +import monix.execution.Cancelable +import monix.reactive.OverflowStrategy +import monix.reactive.Observable +import monix.execution.Ack +import scalafx.scene.control.Button + +package object observables {} diff --git a/src/main/scala/wow/doge/mygame/implicits/package.scala b/src/main/scala/wow/doge/mygame/implicits/package.scala index 0d76f9b..e670a14 100644 --- a/src/main/scala/wow/doge/mygame/implicits/package.scala +++ b/src/main/scala/wow/doge/mygame/implicits/package.scala @@ -6,7 +6,6 @@ import scala.reflect.ClassTag import akka.actor.typed.ActorRef import akka.actor.typed.Scheduler import akka.util.Timeout -import cats.effect.concurrent.Ref import com.jme3.app.Application import com.jme3.app.SimpleApplication import com.jme3.app.state.AppState @@ -49,6 +48,8 @@ import monix.reactive.OverflowStrategy import monix.reactive.observers.Subscriber import wow.doge.mygame.math.ImVector3f import wow.doge.mygame.state.MyBaseState +import com.jme3.light.Light +import com.jayfella.jme.jfx.JavaFxUI case class ActionEvent(binding: Action, value: Boolean, tpf: Float) case class EnumActionEvent[T <: EnumEntry]( @@ -70,7 +71,7 @@ package object implicits { type PhysicsTickObservable = Observable[Either[PrePhysicsTickEvent, PhysicsTickEvent]] - implicit class JMEAppExt(private val app: Application) extends AnyVal { + implicit final class JMEAppExt(private val app: Application) extends AnyVal { def enqueueR(cb: () => Unit) = app.enqueue(new Runnable { @@ -78,7 +79,7 @@ package object implicits { }) } - implicit class StateManagerExt(private val asm: AppStateManager) + implicit final class StateManagerExt(private val asm: AppStateManager) extends AnyVal { def state[S <: AppState]()(implicit c: ClassTag[S]): S = asm.getState(c.runtimeClass.asInstanceOf[Class[S]]) @@ -87,8 +88,9 @@ package object implicits { } - implicit class SimpleApplicationExt[T <: SimpleApplication](private val sa: T) - extends AnyVal { + implicit final class SimpleApplicationExt[T <: SimpleApplication]( + private val sa: T + ) extends AnyVal { def stateManager: AppStateManager = sa.getStateManager() def inputManager: InputManager = sa.getInputManager() def assetManager: AssetManager = sa.getAssetManager() @@ -121,7 +123,8 @@ package object implicits { } } - implicit class AssetManagerExt(private val am: AssetManager) extends AnyVal { + implicit final class AssetManagerExt(private val am: AssetManager) + extends AnyVal { def registerLocator( assetPath: os.RelPath, locator: Class[_ <: AssetLocator] @@ -134,13 +137,13 @@ package object implicits { } } - implicit class BulletAppStateExt(private val bas: BulletAppState) + implicit final class BulletAppStateExt(private val bas: BulletAppState) extends AnyVal { def physicsSpace = bas.getPhysicsSpace() def speed = bas.getSpeed() } - implicit class BetterCharacterControlExt( + implicit final class BetterCharacterControlExt( private val bcc: BetterCharacterControl ) { def withJumpForce(force: ImVector3f) = { @@ -149,11 +152,12 @@ package object implicits { } } - implicit class SpatialExt[T <: Spatial](private val spat: T) extends AnyVal { - def asRef = Ref[Task].of(spat) + implicit final class SpatialExt[T <: Spatial](private val spat: T) + extends AnyVal { + // def asRef = Ref[Task].of(spat) } - implicit class NodeExt[T <: Node](private val n: T) extends AnyVal { + implicit final class NodeExt[T <: Node](private val n: T) extends AnyVal { /** * Attaches the given child @@ -167,18 +171,13 @@ package object implicits { } /** - * Gets the list of children as a monix observable - * - * @return + * @return Gets the list of children as a monix observable */ - // def children = n.getChildren().asScala.toSeq def observableChildren = Observable.fromIterable(n.getChildren().asScala) /** - * A copy of the list of children of this node as a lazy list - * - * @return + * @return A copy of the list of children of this node as a lazy list */ def children = LazyList.from(n.getChildren().asScala) @@ -199,6 +198,11 @@ package object implicits { n } + def :+(light: Light) = { + n.addLight(light) + n + } + def -=(spatial: Spatial) = n.detachChild(spatial) def :-(spatial: Spatial) = { @@ -206,6 +210,11 @@ package object implicits { n } + def :-(light: Light) = { + n.removeLight(light) + n + } + def depthFirst(cb: Spatial => Unit) = n.depthFirstTraversal(new SceneGraphVisitor() { override def visit(s: Spatial) = cb(s) @@ -313,7 +322,7 @@ package object implicits { } - implicit class CameraNodeExt(private val cn: CameraNode) { + implicit final class CameraNodeExt(private val cn: CameraNode) { def withControlDir(controlDir: ControlDirection) = { cn.setControlDir(controlDir) cn @@ -325,14 +334,15 @@ package object implicits { } } - implicit class EntityDataExt(private val ed: EntityData) extends AnyVal { + implicit final class EntityDataExt(private val ed: EntityData) + extends AnyVal { def query = new EntityQuery(ed) // def entities[T <: EntityComponent](entities: Seq[T]) } - implicit class EntityExt(private val e: EntityId) extends AnyVal { + implicit final class EntityExt(private val e: EntityId) extends AnyVal { def withComponents(classes: EntityComponent*)(implicit ed: EntityData ): EntityId = { @@ -341,7 +351,8 @@ package object implicits { } } - implicit class ActorRefExt[Req](private val a: ActorRef[Req]) extends AnyVal { + implicit final class ActorRefExt[Req](private val a: ActorRef[Req]) + extends AnyVal { import akka.actor.typed.scaladsl.AskPattern._ /** @@ -374,7 +385,7 @@ package object implicits { // ask(replyTo)(timeout, scheduler) // } - implicit class InputManagerExt(private val inputManager: InputManager) + implicit final class InputManagerExt(private val inputManager: InputManager) extends AnyVal { def withMapping(mapping: String, triggers: Trigger*): InputManager = { inputManager.addMapping(mapping, triggers: _*) @@ -507,7 +518,7 @@ package object implicits { } } - implicit class PhysicsSpaceExt(private val space: PhysicsSpace) + implicit final class PhysicsSpaceExt(private val space: PhysicsSpace) extends AnyVal { def collisionObservable(): Observable[PhysicsCollisionEvent] = { @@ -587,9 +598,11 @@ package object implicits { space } + // def asRef = Ref[Task].of(space) + } - implicit class Vector3fExt(private val v: Vector3f) extends AnyVal { + implicit final class Vector3fExt(private val v: Vector3f) extends AnyVal { //TODO add more operations def +=(that: Vector3f) = v.addLocal(that) def +=(f: Float) = v.addLocal(f, f, f) @@ -608,7 +621,7 @@ package object implicits { def immutable = ImVector3f(v.x, v.y, v.z) } - implicit class ImVector3fExt(private val v: ImVector3f) extends AnyVal { + implicit final class ImVector3fExt(private val v: ImVector3f) extends AnyVal { def +(that: ImVector3f) = v.copy(v.x + that.x, v.y + that.y, v.z + that.z) def +(f: Float): ImVector3f = v.copy(v.x + f, v.y + f, v.z + f) def *(that: ImVector3f) = v.copy(v.x * that.x, v.y * that.y, v.z * that.z) @@ -628,4 +641,8 @@ package object implicits { // f.hideErrors // } + implicit final class JavaFxUIExt(private val jfxui: JavaFxUI) extends AnyVal { + def +=(node: scalafx.scene.Node) = jfxui.attachChild(node) + def -=(node: scalafx.scene.Node) = jfxui.detachChild(node) + } } diff --git a/src/main/scala/wow/doge/mygame/launcher/DefaultUI.scala b/src/main/scala/wow/doge/mygame/launcher/DefaultUI.scala new file mode 100644 index 0000000..7dfb948 --- /dev/null +++ b/src/main/scala/wow/doge/mygame/launcher/DefaultUI.scala @@ -0,0 +1,102 @@ +package wow.doge.mygame.launcher + +import scalafx.geometry.Insets +import scalafx.scene.Scene +import scalafx.scene.effect.DropShadow +import scalafx.scene.layout.HBox +import scalafx.scene.paint.Color._ +import scalafx.scene.paint._ +import scalafx.scene.text.Text +import scalafx.scene.control.Button +import scalafx.scene.layout.VBox +import scalafx.scene.layout.FlowPane +import scalafx.geometry.Orientation +import scalafx.geometry.Pos +import scalafx.stage.Stage + +object DefaultUI { + def scene( + // stage: Stage, + launchButton: Button, + exitButton: Button + ) = + new Scene { + fill = Color.rgb(38, 38, 38) + content = new VBox { + children = Seq( + new HBox { + padding = Insets(50, 80, 50, 80) + children = Seq( + new Text { + text = "JMonkeyEngine" + style = "-fx-font: normal bold 50pt sans-serif" + fill = new LinearGradient(endX = 0, stops = Stops(Red, DarkRed)) + }, + new Text { + text = " Game" + style = "-fx-font: italic bold 50pt sans-serif" + fill = new LinearGradient( + endX = 0, + stops = Stops(White, DarkGray) + ) + effect = new DropShadow { + color = DarkGray + radius = 15 + spread = 0.25 + } + } + ) + }, + new FlowPane { + hgap = 10 + padding = Insets(50, 80, 50, 80) + orientation = Orientation.Horizontal + alignment = Pos.Center + children = Seq(launchButton, exitButton) + } + ) + } + // onMousePressed = (pressEvent) => { + // onMouseDragged = (dragEvent) => { + // stage.setX(dragEvent.getScreenX() - pressEvent.getSceneX()) + // stage.setY(dragEvent.getScreenY() - pressEvent.getSceneY()) + // } + // } + } + + def box(launchButton: Button, exitButton: Button) = + new VBox { + children = Seq( + new HBox { + padding = Insets(50, 80, 50, 80) + children = Seq( + new Text { + text = "JMonkeyEngine" + style = "-fx-font: normal bold 50pt sans-serif" + fill = new LinearGradient(endX = 0, stops = Stops(Red, DarkRed)) + }, + new Text { + text = " Game" + style = "-fx-font: italic bold 50pt sans-serif" + fill = new LinearGradient( + endX = 0, + stops = Stops(White, DarkGray) + ) + effect = new DropShadow { + color = DarkGray + radius = 15 + spread = 0.25 + } + } + ) + }, + new FlowPane { + hgap = 10 + padding = Insets(50, 80, 50, 80) + orientation = Orientation.Horizontal + alignment = Pos.Center + children = Seq(launchButton, exitButton) + } + ) + } +} diff --git a/src/main/scala/wow/doge/mygame/launcher/Launcher.scala b/src/main/scala/wow/doge/mygame/launcher/Launcher.scala new file mode 100644 index 0000000..73671cd --- /dev/null +++ b/src/main/scala/wow/doge/mygame/launcher/Launcher.scala @@ -0,0 +1,153 @@ +package wow.doge.mygame.launcher + +import scala.concurrent.duration.FiniteDuration +import scalafx.application.JFXApp +import scalafx.application.JFXApp.PrimaryStage +import wow.doge.mygame.executors.Schedulers +import cats.effect.Resource +import monix.bio.Task +import scala.concurrent.duration._ +import javafx.application.Platform +import scalafx.scene.control.Button +import cats.effect.concurrent.Deferred +import wow.doge.mygame.utils.IOUtils._ +import monix.eval.{Task => ETask} +import monix.reactive.Observable +import monix.bio.Fiber +import scalafx.stage.StageStyle +import scalafx.Includes._ +import wow.doge.mygame.utils.ResizeHelper +import scalafx.scene.Scene +import scalafx.scene.layout.VBox +import wow.doge.mygame.implicits.JavaFXMonixObservables._ +import monix.catnap.cancelables.SingleAssignCancelableF +import monix.catnap.CancelableF +// import wow.doge.mygame.implicits.JavaFXMonixObservables._ + +// import scala.language.implicitConversions + +// object Stage { +// implicit def sfxStage2jfx(v: Stage): jfxs.Stage = if (v != null) v.delegate else null +// } + +object Launcher { + sealed trait LauncherResult + object LauncherResult { + case object LaunchGame extends LauncherResult + case object Exit extends LauncherResult + } + + class Props( + val schedulers: Schedulers, + val signal: Deferred[Task, LauncherResult] + ) { + // val resource2 + // : Resource[Task, (LauncherApp, Task[Ref[Task, Stage]], Fiber[Unit])] = + // Resource.make(for { + // app <- Task(new LauncherApp(this)) + // fib <- app.init.start + // } yield ((app, app.stageRef, fib)))(_._3.cancel) + val create = Task(new Launcher(this)) + + val resource: Resource[Task, Launcher] = + Resource.make(for { + app <- Task(new Launcher(this)) + // fib <- app.init.start + } yield (app))(_ => Task.unit) + } +} +class Launcher private (props: Launcher.Props) { + import Launcher._ + + private lazy val launchButton = new Button { + text = "Launch" + } + // private lazy val launchButtonObs = + + private lazy val launchAction = + launchButton + .observableAction() + .doOnNext(_ => toTask(props.signal.complete(LauncherResult.LaunchGame))) + + private lazy val exitButton = new Button { + text = "Exit" + } + // private lazy val exitButtonObs = + + private lazy val exitAction = + exitButton + .observableAction() + .doOnNext(_ => toTask(props.signal.complete(LauncherResult.Exit))) + + private lazy val _scene = + // new Scene { + // content = new VBox + // } + DefaultUI.scene(launchButton, exitButton) + + private lazy val _stage = new PrimaryStage { + scene = _scene + } + + private lazy val internal = new JFXApp { + stage = _stage + stage.initStyle(StageStyle.Undecorated) + // ResizeHelper.addResizeListener(stage) + } + + private lazy val sceneDragObservable = { + lazy val mpo = _scene.observableMousePressed() + lazy val mdo = _scene.observableMouseDragged() + + mpo.mergeMap(pressEvent => + mdo.doOnNext(dragEvent => + ETask( + _stage.setX(dragEvent.screenX - pressEvent.sceneX) + ) >> + ETask( + _stage.setY( + dragEvent.screenY - pressEvent.sceneY + ) + ) + ) + ) + } + + // var stage = internal.stage + + // lazy val stageRef = Ref.of[Task, Stage](internal.stage) +// stage: => PrimaryStage + def init(delay: FiniteDuration = 2000.millis) = + for { + _ <- Task(Platform.setImplicitExit(false)) + + fib <- Task(internal.main(Array.empty)).start + _ <- Task.sleep(500.millis) + // _ <- Task { + // // lazy val _stage = new CustomStageBuilder() + // // .setWindowTitle("CustomStage example") + // // .setWindowColor("rgb(34,54,122)") + // // .build() + // internal.stage.scene = + // DefaultUI.scene(internal.stage, launchButton, exitButton) + // // _stage.setScene(DefaultUI.scene(launchButton, exitButton)) + // // JFXApp.Stage = _stage + // }.executeOn(props.schedulers.fx) + // c <- SingleAssignCancelableF[Task] + sceneDragFib <- toIO(sceneDragObservable.completedL).start + fib2 <- toIO( + Observable(launchAction, exitAction).merge + .doOnNext(_ => + ETask(internal.stage.close()).executeOn(props.schedulers.fx) + ) + // .doOnNext(_ => toTask(fib.cancel)) + .completedL + ).start + c <- CancelableF[Task](fib.cancel >> fib2.cancel >> sceneDragFib.cancel) + // _ <- Task { + // internal.stage = stage + // }.executeOn(props.schedulers.fx) + // .delayExecution(delay) + } yield (c) + +} diff --git a/src/main/scala/wow/doge/mygame/subsystems/events/MovementEvents.scala b/src/main/scala/wow/doge/mygame/subsystems/events/MovementEvents.scala index 871c4f6..c6b8e39 100644 --- a/src/main/scala/wow/doge/mygame/subsystems/events/MovementEvents.scala +++ b/src/main/scala/wow/doge/mygame/subsystems/events/MovementEvents.scala @@ -3,7 +3,6 @@ package wow.doge.mygame.subsystems.events import wow.doge.mygame.game.subsystems.movement.CanMove sealed trait EntityMovementEvent - object EntityMovementEvent { final case class MovedLeft[T: CanMove](pressed: Boolean, movable: T) extends EntityMovementEvent @@ -29,5 +28,6 @@ object EntityMovementEvent { final case object PlayerRotatedLeft extends PlayerMovementEvent final case object PlayerCameraUp extends PlayerMovementEvent final case object PlayerCameraDown extends PlayerMovementEvent + } } diff --git a/src/main/scala/wow/doge/mygame/subsystems/moddingsystem/ModdingSystem.scala b/src/main/scala/wow/doge/mygame/subsystems/moddingsystem/ModdingSystem.scala index e225134..f85683d 100644 --- a/src/main/scala/wow/doge/mygame/subsystems/moddingsystem/ModdingSystem.scala +++ b/src/main/scala/wow/doge/mygame/subsystems/moddingsystem/ModdingSystem.scala @@ -23,9 +23,6 @@ final case class Test1(hello1: String, hello2: String) final case class Test2(hello1: String) final case class Plugin(name: String, priority: Int) object Plugin { - // @annotation.nowarn( - // "msg=Block result was adapted via implicit conversion" - // ) implicit val pluginFormat: Decoder[Plugin] = deriveDecoder } diff --git a/src/main/scala/wow/doge/mygame/subsystems/scriptsystem/ScriptActor.scala b/src/main/scala/wow/doge/mygame/subsystems/scriptsystem/ScriptActor.scala index f330603..8027fc0 100644 --- a/src/main/scala/wow/doge/mygame/subsystems/scriptsystem/ScriptActor.scala +++ b/src/main/scala/wow/doge/mygame/subsystems/scriptsystem/ScriptActor.scala @@ -29,6 +29,11 @@ object ScriptActor { result: ActorRef[Either[Error, Any]] ) extends Command + final case class CompileAll( + paths: Seq[os.Path], + result: ActorRef[Map[os.Path, Either[Error, Any]]] + ) extends Command + lazy val defaultScalaRunner = ammonite .Main( @@ -126,15 +131,18 @@ class ScriptActor( import ScriptActor._ def receiveMessage: Behavior[Command] = - Behaviors.receiveMessage { msg => - msg match { - case CompileAny(path, requester) => - context.log.debug(s"Received $path") - val res = getScript(path) - context.log.debug(s"result = $res") - requester ! res - Behaviors.same - } + Behaviors.receiveMessage { + case CompileAny(path, requester) => + context.log.debug(s"Received $path") + val res = getScript(path) + context.log.debug(s"result = $res") + requester ! res + Behaviors.same + + case CompileAll(paths, requester) => + context.log.debug(s"Received $paths") + requester ! compileAll(paths) + Behaviors.same } def getScript(path: os.Path): Either[Error, Any] = @@ -148,4 +156,25 @@ class ScriptActor( case l @ Left(err) => l } + type LOL = Map[os.Path, Either[wow.doge.mygame.state.ScriptActor.Error, Any]] + + def compileAll( + paths: Seq[os.Path] + ): LOL = { + @annotation.tailrec + def loop( + paths: Seq[os.Path], + scriptsMap: Map[ + os.Path, + Either[wow.doge.mygame.state.ScriptActor.Error, Any] + ] + ): LOL = { + paths match { + case head :: next => loop(next, scriptsMap + (head -> getScript(head))) + case Nil => scriptsMap + } + } + loop(paths, Map.empty) + } + } diff --git a/src/main/scala/wow/doge/mygame/subsystems/scriptsystem/ScriptCachingActor.scala b/src/main/scala/wow/doge/mygame/subsystems/scriptsystem/ScriptCachingActor.scala index 7e6e3c6..2f90914 100644 --- a/src/main/scala/wow/doge/mygame/subsystems/scriptsystem/ScriptCachingActor.scala +++ b/src/main/scala/wow/doge/mygame/subsystems/scriptsystem/ScriptCachingActor.scala @@ -37,6 +37,11 @@ object ScriptCachingActor { requester: ActorRef[ScriptResult], force: Boolean = false ) extends Command + // final case class GetAll( + // scriptPaths: Seq[os.Path], + // requester: ActorRef[Map[os.Path, ScriptResult]], + // force: Boolean = false + // ) extends Command final case class GetMap(requester: ActorRef[ScriptsMap]) extends Command final case class Put(scriptPath: os.Path, script: ScriptObject) extends Command @@ -47,6 +52,15 @@ object ScriptCachingActor { scriptPath: os.Path, requester: ActorRef[ScriptResult] ) extends Command + // private[scriptsystem] final case class DelegateAllToChild( + // scriptPaths: Seq[os.Path], + // requester: ActorRef[Map[os.Path, ScriptResult]] + // ) extends Command + + // private[scriptsystem] final case class ReplyWithPrecompiled( + // precompiled: Map[os.Path, ScriptResult], + // requester: ActorRef[Map[os.Path, ScriptResult]] + // ) extends Command // final case class Props( // ctx: ActorContext[Command], @@ -102,6 +116,51 @@ class ScriptCachingActor( ) Behaviors.same + // case GetAll(scriptPaths, requester, force) => + // import scala.concurrent.duration._ + // implicit val timeout = Timeout(15.seconds) + + // /** + // * Holy complexity batman this is getting too complex + // */ + // if (force) { + // scriptPaths + // .sliding( + // if (scriptPaths.length > 3 && scriptPaths.length % 2 == 0) 2 + // else 3 + // ) + // .foreach(lst => + // ctx.self ! DelegateAllToChild(scriptPaths, requester) + // ) + // } else { + // val (failures, successes) = scriptPaths + // .partitionMap(path => + // state.scriptsMap.get(path) match { + // case Some(value) => Right(path -> value) + // case None => Left(path) + // } + // ) match { + // case (failures, successes) => + // import cats.syntax.either._ + // failures -> Map.from(successes.map { + // case (p, obj) => p -> obj.asRight[ScriptActor.Error] + // }) + // } + // ctx.ask(scriptActor, ScriptActor.CompileAll(failures, _)) { + // case Success(value) => + // val total = successes ++ value + // requester ! total + // value.foreach { + // case (path, res) => res.foreach(r => ctx.self ! Put(path, r)) + // } + // NoOp + // case Failure(exception) => NoOp + // } + // } + // // scriptPaths.foreach(p => ctx.self ! Get(p)) + + // Behaviors.same + case DelegateToChild(scriptActor, scriptPath, requester) => import scala.concurrent.duration._ implicit val timeout = Timeout(15.seconds) @@ -114,6 +173,19 @@ class ScriptCachingActor( ) Behaviors.same + // case DelegateAllToChild(scriptPaths, requester) => + // import scala.concurrent.duration._ + // implicit val timeout = Timeout(15.seconds) + // ctx.ask(scriptActor, ScriptActor.CompileAll(scriptPaths, _)) { + // case Success(value) => + // value.foreach { + // case (path, res) => res.foreach(r => ctx.self ! Put(path, r)) + // } + // NoOp + // case Failure(exception) => NoOp + // } + // Behaviors.same + case GetMap(requester) => requester ! state.scriptsMap Behaviors.same @@ -128,6 +200,7 @@ class ScriptCachingActor( case NoOp => Behaviors.same } } + } object ScriptActorPool { diff --git a/src/main/scala/wow/doge/mygame/subsystems/scriptsystem/ScriptSystemModule.scala b/src/main/scala/wow/doge/mygame/subsystems/scriptsystem/ScriptSystemModule.scala index 04e3171..5214c4e 100644 --- a/src/main/scala/wow/doge/mygame/subsystems/scriptsystem/ScriptSystemModule.scala +++ b/src/main/scala/wow/doge/mygame/subsystems/scriptsystem/ScriptSystemModule.scala @@ -35,6 +35,7 @@ class ScriptSystemResource( Resource.liftF(scriptCacheActor) } + // sys.ask(ref => ScriptCachingActor.GetAll(os.pwd/'assets/'scripts/'scala/"hello2.sc",ref, false)) val init = for { scriptFiles <- Task(findScriptFiles(os.pwd / "assets" / "scripts")) diff --git a/src/main/scala/wow/doge/mygame/utils/BorderlessScene.scala b/src/main/scala/wow/doge/mygame/utils/BorderlessScene.scala new file mode 100644 index 0000000..8fe3f0c --- /dev/null +++ b/src/main/scala/wow/doge/mygame/utils/BorderlessScene.scala @@ -0,0 +1,750 @@ +package wow.doge.mygame.utils + +/* + * Copyright (c) 2011-2019, ScalaFX Project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the ScalaFX Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE SCALAFX PROJECT OR ITS CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import javafx.scene.{input => jfxsi, layout => jfxsl, paint => jfxsp} +import javafx.{ + collections => jfxc, + event => jfxe, + geometry => jfxg, + scene => jfxs, + util => jfxu +} +import scalafx.Includes._ +import scalafx.beans.property.{ + ObjectProperty, + ReadOnlyDoubleProperty, + ReadOnlyObjectProperty +} +import scalafx.collections._ +import scalafx.delegate.SFXDelegate +import scalafx.geometry.NodeOrientation +import scalafx.scene.image.WritableImage +import scalafx.scene.input.{Dragboard, Mnemonic, TransferMode} +import scalafx.scene.paint.Paint +import com.goxr3plus.fxborderlessscene.borderless.{BorderlessScene => BScene} + +import scala.language.implicitConversions +import scalafx.scene.Cursor +import scalafx.scene._ +import scalafx.stage.Stage +import scalafx.stage.StageStyle + +object BorderlessScene { + implicit def sfxScene2jfx(v: BorderlessScene): Scene = + if (v != null) v.delegate else null +} + +/** + * Wraps [[http://docs.oracle.com/javase/8/javafx/api/javafx/scene/Scene.html]]. + * + * @constructor Create a new ScalaFX Scene with JavaFX Scene as delegate. + * @param delegate JavaFX Scene delegated. Its default value is a JavaFX Scene with a + * [[http://docs.oracle.com/javase/8/javafx/api/javafx/scene/Group.html Group]] as root Node. + */ +class BorderlessScene( + override val delegate: BScene +) extends SFXDelegate[BScene] { + + def this(stage: Stage, stageStyle: StageStyle, parent: Parent) = + this(new BScene(stage, stageStyle, parent)) + + /** + * Returns the root Node of the scene graph + */ + def root: ObjectProperty[jfxs.Parent] = delegate.rootProperty + + /** + * Sets the root Node of the scene graph + */ + def root_=(v: Parent): Unit = { + root() = v + } + + /** + * Returns Nodes children from this Scene's `root`. + */ + def getChildren = + root.value match { + case group: jfxs.Group => group.getChildren + case pane: jfxsl.Pane => pane.getChildren + case _ => + throw new IllegalStateException( + "Cannot access children of root: " + root + "\n" + + "Use a class that extends Group or Pane, or override the getChildren method." + ) + } + + /** + * Returns scene's antialiasing setting. + */ + def antialiasing: SceneAntialiasing = delegate.getAntiAliasing + + /** + * Returns Content's Node children from this Scene's `root`. + */ + def content: jfxc.ObservableList[jfxs.Node] = getChildren + + /** + * Sets the list of Nodes children from this Scene's `root`, replacing the prior content. If you want append to + * current content, use `add` or similar. + * + * @param c list of Nodes children from this Scene's `root` to replace prior content. + */ + def content_=(c: Iterable[Node]): Unit = { + fillSFXCollection(this.content, c) + } + + /** + * Sets a Node child, replacing the prior content. If you want append to current content, use `add` or similar. + * + * @param n Node child to replace prior content. + */ + def content_=(n: Node): Unit = { + fillSFXCollectionWithOne(this.content, n) + } + + /** + * Specifies the type of camera use for rendering this `Scene`. + */ + def camera: ObjectProperty[jfxs.Camera] = delegate.cameraProperty + + def camera_=(v: Camera): Unit = { + camera() = v + } + + /** + * Defines the mouse cursor for this `Scene`. + */ + def cursor: ObjectProperty[jfxs.Cursor] = delegate.cursorProperty + + def cursor_=(v: Cursor): Unit = { + cursor() = v + } + + /** The effective node orientation of a scene resolves the inheritance of node orientation, returning either left-to-right or right-to-left. */ + def effectiveNodeOrientation: ReadOnlyObjectProperty[jfxg.NodeOrientation] = + delegate.effectiveNodeOrientationProperty + + /** + * Specifies the event dispatcher for this scene. + */ + def eventDispatcher: ObjectProperty[jfxe.EventDispatcher] = + delegate.eventDispatcherProperty + + def eventDispatcher_=(v: jfxe.EventDispatcher): Unit = { + eventDispatcher() = v + } + + /** + * Defines the background fill of this Scene. + */ + def fill: ObjectProperty[jfxsp.Paint] = delegate.fillProperty + + def fill_=(v: Paint): Unit = { + fill() = v + } + + /** + * The height of this Scene + */ + def height: ReadOnlyDoubleProperty = delegate.heightProperty + + /** + * The width of this Scene + */ + def width: ReadOnlyDoubleProperty = delegate.widthProperty + + def nodeOrientation: ObjectProperty[jfxg.NodeOrientation] = + delegate.nodeOrientationProperty + + def nodeOrientation_=(v: NodeOrientation): Unit = { + ObjectProperty.fillProperty[jfxg.NodeOrientation](this.nodeOrientation, v) + } + + /** + * Defines a function to be called when a mouse button has been clicked (pressed and released) on this `Scene`. + */ + def onContextMenuRequested = delegate.onContextMenuRequestedProperty + + def onContextMenuRequested_=( + v: jfxe.EventHandler[_ >: jfxsi.ContextMenuEvent] + ): Unit = { + onContextMenuRequested() = v + } + + /** + * Defines a function to be called when drag gesture has been detected. + */ + def onDragDetected = delegate.onDragDetectedProperty + + def onDragDetected_=(v: jfxe.EventHandler[_ >: jfxsi.MouseEvent]): Unit = { + onDragDetected() = v + } + + /** + * Defines a function to be called when this `Scene` is a drag and drop gesture source after its data has been + * dropped on a drop target. + */ + def onDragDone = delegate.onDragDoneProperty + + def onDragDone_=(v: jfxe.EventHandler[_ >: jfxsi.DragEvent]): Unit = { + onDragDone() = v + } + + /** + * Defines a function to be called when the mouse button is released on this `Scene` during drag and drop gesture. + */ + def onDragDropped = delegate.onDragDroppedProperty + + def onDragDropped_=(v: jfxe.EventHandler[_ >: jfxsi.DragEvent]): Unit = { + onDragDropped() = v + } + + /** + * Defines a function to be called when drag gesture enters this Scene. + */ + def onDragEntered = delegate.onDragEnteredProperty + + def onDragEntered_=(v: jfxe.EventHandler[_ >: jfxsi.DragEvent]): Unit = { + onDragEntered() = v + } + + /** + * Defines a function to be called when drag gesture exits this Scene. + */ + def onDragExited = delegate.onDragExitedProperty + + def onDragExited_=(v: jfxe.EventHandler[_ >: jfxsi.DragEvent]): Unit = { + onDragExited() = v + } + + /** + * Defines a function to be called when drag gesture progresses within this `Scene`. + */ + def onDragOver = delegate.onDragOverProperty + + def onDragOver_=(v: jfxe.EventHandler[_ >: jfxsi.DragEvent]): Unit = { + onDragOver() = v + } + + /** + * Defines a function to be called when this `Node` has input focus and the input method text has changed. + */ + def onInputMethodTextChanged = delegate.onInputMethodTextChangedProperty + + def onInputMethodTextChanged_=( + v: jfxe.EventHandler[_ >: jfxsi.InputMethodEvent] + ): Unit = { + onInputMethodTextChanged() = v + } + + /** + * Defines a function to be called when some `Node` of this `Scene` has input focus and a key has been pressed. + */ + def onKeyPressed = delegate.onKeyPressedProperty + + def onKeyPressed_=(v: jfxe.EventHandler[_ >: jfxsi.KeyEvent]): Unit = { + onKeyPressed() = v + } + + /** + * Defines a function to be called when some `Node` of this `Scene` has input focus and a key has been released. + */ + def onKeyReleased = delegate.onKeyReleasedProperty + + def onKeyReleased_=(v: jfxe.EventHandler[_ >: jfxsi.KeyEvent]): Unit = { + onKeyReleased() = v + } + + /** + * Defines a function to be called when some `Node` of this `Scene` has input focus and a key has been typed. + */ + def onKeyTyped = delegate.onKeyTypedProperty + + def onKeyTyped_=(v: jfxe.EventHandler[_ >: jfxsi.KeyEvent]): Unit = { + onKeyTyped() = v + } + + /** + * Defines a function to be called when a mouse button has been clicked (pressed and released) on this `Scene`. + */ + def onMouseClicked = delegate.onMouseClickedProperty + + def onMouseClicked_=(v: jfxe.EventHandler[_ >: jfxsi.MouseEvent]): Unit = { + onMouseClicked() = v + } + + /** + * Defines a function to be called when a mouse button is pressed on this `Scene` and then dragged. + */ + def onMouseDragged = delegate.onMouseDraggedProperty + + def onMouseDragged_=(v: jfxe.EventHandler[_ >: jfxsi.MouseEvent]): Unit = { + onMouseDragged() = v + } + + /** + * Defines a function to be called when a full press-drag-release gesture enters this `Scene`. + */ + def onMouseDragEntered = delegate.onMouseDragEnteredProperty + + def onMouseDragEntered_=( + v: jfxe.EventHandler[_ >: jfxsi.MouseDragEvent] + ): Unit = { + onMouseDragEntered() = v + } + + /** + * Defines a function to be called when a full press-drag-release gesture exits this `Scene`. + */ + def onMouseDragExited = delegate.onMouseDragExitedProperty + + def onMouseDragExited_=( + v: jfxe.EventHandler[_ >: jfxsi.MouseDragEvent] + ): Unit = { + onMouseDragExited() = v + } + + /** + * Defines a function to be called when a full press-drag-release gesture progresses within this `Scene`. + */ + def onMouseDragOver = delegate.onMouseDragOverProperty + + def onMouseDragOver_=( + v: jfxe.EventHandler[_ >: jfxsi.MouseDragEvent] + ): Unit = { + onMouseDragOver() = v + } + + /** + * Defines a function to be called when a full press-drag-release gesture ends within this `Scene`. + */ + def onMouseDragReleased = delegate.onMouseDragReleasedProperty + + def onMouseDragReleased_=( + v: jfxe.EventHandler[_ >: jfxsi.MouseDragEvent] + ): Unit = { + onMouseDragReleased() = v + } + + /** + * Defines a function to be called when the mouse enters this `Scene`. + */ + def onMouseEntered = delegate.onMouseEnteredProperty + + def onMouseEntered_=(v: jfxe.EventHandler[_ >: jfxsi.MouseEvent]): Unit = { + onMouseEntered() = v + } + + /** + * Defines a function to be called when the mouse exits this `Scene`. + */ + def onMouseExited = delegate.onMouseExitedProperty + + def onMouseExited_=(v: jfxe.EventHandler[_ >: jfxsi.MouseEvent]): Unit = { + onMouseExited() = v + } + + /** + * Defines a function to be called when mouse cursor moves within this `Scene` but no buttons have been pushed. + */ + def onMouseMoved = delegate.onMouseMovedProperty + + def onMouseMoved_=(v: jfxe.EventHandler[_ >: jfxsi.MouseEvent]): Unit = { + onMouseMoved() = v + } + + /** + * Defines a function to be called when a mouse button has been pressed on this `Scene`. + */ + def onMousePressed = delegate.onMousePressedProperty + + def onMousePressed_=(v: jfxe.EventHandler[_ >: jfxsi.MouseEvent]): Unit = { + onMousePressed() = v + } + + /** + * Defines a function to be called when a mouse button has been released on this `Scene`. + */ + def onMouseReleased = delegate.onMouseReleasedProperty + + def onMouseReleased_=(v: jfxe.EventHandler[_ >: jfxsi.MouseEvent]): Unit = { + onMouseReleased() = v + } + + /** + * Defines a function to be called when user performs a scrolling action. + */ + def onScroll = delegate.onScrollProperty + + def onScroll_=(v: jfxe.EventHandler[_ >: jfxsi.ScrollEvent]): Unit = { + onScroll() = v + } + + /** + * The URL of the user-agent stylesheet that will be used by this Scene in place of the the platform-default + * user-agent stylesheet. If the URL does not resolve to a valid location, the platform-default user-agent + * stylesheet will be used. + * + * For additional information about using CSS with the scene graph, see the + * [[http://docs.oracle.com/javase/8/javafx/api/javafx/scene/doc-files/cssref.html CSS Reference Guide]]. + * + * @return The URL of the user-agent stylesheet that will be used by this SubScene, or null if has not been set. + */ + def userAgentStylesheet: ObjectProperty[String] = + delegate.userAgentStylesheetProperty + + /** + * Set the URL of the user-agent stylesheet that will be used by this Scene in place of the the platform-default + * user-agent stylesheet. If the URL does not resolve to a valid location, the platform-default user-agent + * stylesheet will be used. + * + * For additional information about using CSS with the scene graph, see the + * [[http://docs.oracle.com/javase/8/javafx/api/javafx/scene/doc-files/cssref.html CSS Reference Guide]]. + * + * @param url The URL is a hierarchical URI of the form `[scheme:][//authority][path]`. + * If the URL does not have a `[scheme:]` component, the URL is considered to be the `[path]` + * component only. Any leading '/' character of the `[path]` is ignored and the `[path]` is + * treated as a path relative to the root of the application's classpath. + */ + def userAgentStylesheet_=(url: String): Unit = { + ObjectProperty.fillProperty[String](userAgentStylesheet, url) + } + + /** + * The `Window` for this Scene + */ + def window: ReadOnlyObjectProperty[javafx.stage.Window] = + delegate.windowProperty + + /** + * The horizontal location of this `Scene` on the `Window`. + */ + def x: ReadOnlyDoubleProperty = delegate.xProperty + + /** + * The vertical location of this `Scene` on the `Window`. + */ + def y: ReadOnlyDoubleProperty = delegate.yProperty + + /** + * Retrieves the depth buffer attribute for this scene. + */ + def depthBuffer = delegate.isDepthBuffer + + /** + * Gets an observable list of string URLs linking to the stylesheets to use with this Parent's contents. + */ + def stylesheets: jfxc.ObservableList[String] = delegate.getStylesheets + + /** + * Sets the list of stylesheets URLs, replacing the prior content. If you want append to current content, use `add` or + * similar. + * + * @param c list of stylesheets URLs to replace prior content. + */ + def stylesheets_=(c: Iterable[String]): Unit = { + fillCollection(stylesheets, c) + } + + /** + * Looks for any node within the scene graph based on the specified CSS selector. + * + * @param selector The css selector to look up + * @return A [[scala.Some]] containing the Node in the scene which matches the CSS selector, or [[scala.None]] + * if none is found. + */ + def lookup(selector: String): Option[Node] = Option(delegate.lookup(selector)) + + /** + * Registers the specified mnemonic. + * + * @param m The Mnemonic + */ + def addMnemonic(m: Mnemonic): Unit = { + delegate.addMnemonic(m) + } + + /** + * Unregisters the specified mnemonic. + * + * @param m The Mnemonic to be removed. + */ + def removeMnemonic(m: Mnemonic): Unit = { + delegate.removeMnemonic(m) + } + + /** + * Gets the list of mnemonics for this `Scene`. + */ + def getMnemonics + : jfxc.ObservableMap[jfxsi.KeyCombination, jfxc.ObservableList[ + jfxsi.Mnemonic + ]] = delegate.getMnemonics + + /** + * Gets the list of accelerators for this Scene. + */ + def accelerators: jfxc.ObservableMap[jfxsi.KeyCombination, Runnable] = + delegate.getAccelerators + + /** + * Confirms a potential drag and drop gesture that is recognized over this `Scene`. + * + * @param transferModes The supported `TransferMode`(s) of this `Node` + * @return A `Dragboard` to place this `Scene`'s data on + */ + def startDragAndDrop(transferModes: TransferMode*): Dragboard = + delegate.startDragAndDrop(transferModes.map(_.delegate): _*) + + /** + * Starts a full press-drag-release gesture with this scene as gesture source. + */ + def startFullDrag(): Unit = { + delegate.startFullDrag() + } + + /** + * The scene's current focus owner node. This node's "focused" variable might be false if this scene has no window, + * or if the window is inactive (window.focused == false). + * + * @since 2.2 + */ + def focusOwner: ReadOnlyObjectProperty[jfxs.Node] = + delegate.focusOwnerProperty() + + /** + * Defines a function to be called when user performs a rotation action. + * + * @since 2.2 + */ + def onRotate = delegate.onRotateProperty + + def onRotate_=(v: jfxe.EventHandler[_ >: jfxsi.RotateEvent]): Unit = { + onRotate() = v + } + + /** + * Defines a function to be called when a rotation gesture ends. + * + * @since 2.2 + */ + def onRotationFinished = delegate.onRotationFinishedProperty() + + def onRotationFinished_=( + v: jfxe.EventHandler[_ >: jfxsi.RotateEvent] + ): Unit = { + onRotationFinished() = v + } + + /** + * Defines a function to be called when a rotation gesture starts. + * + * @since 2.2 + */ + def onRotationStarted = delegate.onRotationFinishedProperty() + + def onRotationStarted_=( + v: jfxe.EventHandler[_ >: jfxsi.RotateEvent] + ): Unit = { + onRotationStarted() = v + } + + /** + * Defines a function to be called when a Scroll gesture ends. + * + * @since 2.2 + */ + def onScrollFinished = delegate.onScrollFinishedProperty() + + def onScrollFinished_=(v: jfxe.EventHandler[_ >: jfxsi.ScrollEvent]): Unit = { + onScrollFinished() = v + } + + /** + * Defines a function to be called when a Scroll gesture starts. + * + * @since 2.2 + */ + def onScrollStarted = delegate.onScrollStartedProperty() + + def onScrollStarted_=(v: jfxe.EventHandler[_ >: jfxsi.ScrollEvent]): Unit = { + onScrollStarted() = v + } + + /** + * Defines a function to be called when a Swipe Down gesture starts. + * + * @since 2.2 + */ + def onSwipeDown = delegate.onSwipeDownProperty() + + def onSwipeDown_=(v: jfxe.EventHandler[_ >: jfxsi.SwipeEvent]): Unit = { + onSwipeDown() = v + } + + /** + * Defines a function to be called when a Swipe Down gesture starts. + * + * @since 2.2 + */ + def onSwipeLeft = delegate.onSwipeLeftProperty() + + def onSwipeLeft_=(v: jfxe.EventHandler[_ >: jfxsi.SwipeEvent]): Unit = { + onSwipeLeft() = v + } + + /** + * Defines a function to be called when a Swipe Up gesture starts. + * + * @since 2.2 + */ + def onSwipeUp = delegate.onSwipeUpProperty() + + def onSwipeUp_=(v: jfxe.EventHandler[_ >: jfxsi.SwipeEvent]): Unit = { + onSwipeUp() = v + } + + /** + * Defines a function to be called when a Swipe Right gesture starts. + * + * @since 2.2 + */ + def onSwipeRight = delegate.onSwipeRightProperty() + + def onSwipeRight_=(v: jfxe.EventHandler[_ >: jfxsi.SwipeEvent]): Unit = { + onSwipeRight() = v + } + + /** + * Defines a function to be called when user performs a Touch action. + * + * @since 2.2 + */ + def onZoom = delegate.onZoomProperty() + + def onZoom_=(v: jfxe.EventHandler[_ >: jfxsi.ZoomEvent]): Unit = { + onZoom() = v + } + + /** + * Defines a function to be called when a Zoom gesture ends. + * + * @since 2.2 + */ + def onZoomFinished = delegate.onZoomFinishedProperty() + + def onZoomFinished_=(v: jfxe.EventHandler[_ >: jfxsi.ZoomEvent]): Unit = { + onZoomFinished() = v + } + + /** + * Defines a function to be called when a Zoom gesture starts. + * + * @since 2.2 + */ + def onZoomStarted = delegate.onZoomStartedProperty() + + def onZoomStarted_=(v: jfxe.EventHandler[_ >: jfxsi.ZoomEvent]): Unit = { + onZoomStarted() = v + } + + /** + * Defines a function to be called when user performs a Touch Moved action. + * + * @since 2.2 + */ + def onTouchMoved = delegate.onTouchMovedProperty() + + def onTouchMoved_=(v: jfxe.EventHandler[_ >: jfxsi.TouchEvent]): Unit = { + onTouchMoved() = v + } + + /** + * Defines a function to be called when user performs a Touch Pressed action. + * + * @since 2.2 + */ + def onTouchPressed = delegate.onTouchPressedProperty() + + def onTouchPressed_=(v: jfxe.EventHandler[_ >: jfxsi.TouchEvent]): Unit = { + onTouchPressed() = v + } + + /** + * Defines a function to be called when user performs a Touch Released action. + * + * @since 2.2 + */ + def onTouchReleased = delegate.onTouchReleasedProperty() + + def onTouchReleased_=(v: jfxe.EventHandler[_ >: jfxsi.TouchEvent]): Unit = { + onTouchReleased() = v + } + + /** + * Defines a function to be called when user performs a Touch Stationary action. + * + * @since 2.2 + */ + def onTouchStationary = delegate.onTouchStationaryProperty() + + def onTouchStationary_=(v: jfxe.EventHandler[_ >: jfxsi.TouchEvent]): Unit = { + onTouchStationary() = v + } + + /** + * Takes a snapshot of this scene and returns the rendered image when it is ready. + * + * @param image The writable image that will be used to hold the rendered scene. + * @return the rendered image + * + * @since 2.2 + */ + def snapshot(image: WritableImage): WritableImage = delegate.snapshot(image) + + /** + * Takes a snapshot of this scene at the next frame and calls the specified callback method when the image is ready. + * + * @param callback A function to be called when the image is ready. + * @param image The writable image that will be used to hold the rendered scene. + * + * @since 2.2 + */ + def snapshot(callback: SnapshotResult => Unit, image: WritableImage): Unit = { + val javaCallback = new jfxu.Callback[jfxs.SnapshotResult, java.lang.Void] { + def call(result: jfxs.SnapshotResult): java.lang.Void = { + callback(new SnapshotResult(result)) + null + } + } + delegate.snapshot(javaCallback, image) + } + +} diff --git a/src/main/scala/wow/doge/mygame/utils/GenericConsoleStream.scala b/src/main/scala/wow/doge/mygame/utils/GenericConsoleStream.scala new file mode 100644 index 0000000..3b1ac28 --- /dev/null +++ b/src/main/scala/wow/doge/mygame/utils/GenericConsoleStream.scala @@ -0,0 +1,104 @@ +package wow.doge.mygame.utils + +import java.io.ByteArrayOutputStream +import java.io.OutputStream +import java.io.PrintStream + +import cats.effect.Resource +import monix.bio.Task +import scalafx.scene.control.TextArea +import scalafx.application.Platform + +trait ConsoleStreamable[T] { + def println(inst: T, text: String): Unit + def print(inst: T, text: String): Unit +} + +class GenericConsoleStream[T]( + outputStream: OutputStream, + val config: GenericConsoleStream.Config = + GenericConsoleStream.Config.default, + private var streamable: Option[T] = None +)(implicit + cs: ConsoleStreamable[T] +) extends PrintStream(outputStream, true) { + private lazy val defaultOut = System.out + + def printToStreamable(stble: Option[T], text: String) = + stble.foreach(s => cs.println(s, text)) + + override def println(text: String): Unit = + if (config.exclusive) { + printToStreamable(streamable, text) + } else { + defaultOut.println(text) + printToStreamable(streamable, text) + } + + override def print(text: String): Unit = + streamable.foreach(s => + if (config.exclusive) { + printToStreamable(streamable, text) + } else { + defaultOut.println(text) + printToStreamable(streamable, text) + } + ) + + def :=(s: T) = { + streamable match { + case Some(value) => + case None => streamable = Some(s) + } + } +} + +object GenericConsoleStream { + + /** + * for future use + */ + case class Config(exclusive: Boolean = false) + object Config { + lazy val default = Config() + } + + implicit val implJFXConsoleStreamForTextArea = + new ConsoleStreamable[scalafx.scene.control.TextArea] { + + override def println( + ta: scalafx.scene.control.TextArea, + text: String + ): Unit = + ta.appendText(text + "\n") + + override def print( + ta: scalafx.scene.control.TextArea, + text: String + ): Unit = + ta.appendText(text) + + } + + // def textAreaStreamResource(ta: TextArea) = + // Resource.make( + // Task( + // new GenericConsoleStream( + // outputStream = new ByteArrayOutputStream(), + // streamable = ta + // ) + // ) + // )(s => Task(s.close())) + + def textAreaStream( + // ta: TextArea + ) = + // Task( + new GenericConsoleStream[TextArea]( + outputStream = new ByteArrayOutputStream() + // streamable = ta + ) + // ) + // (s => Task(s.close())) + +} diff --git a/src/main/scala/wow/doge/mygame/utils/JFXConsoleStream.scala b/src/main/scala/wow/doge/mygame/utils/JFXConsoleStream.scala deleted file mode 100644 index e2c5546..0000000 --- a/src/main/scala/wow/doge/mygame/utils/JFXConsoleStream.scala +++ /dev/null @@ -1,71 +0,0 @@ -package wow.doge.mygame.utils - -import java.io.ByteArrayOutputStream -import java.io.OutputStream -import java.io.PrintStream - -import cats.effect.Resource -import monix.bio.Task -import scalafx.scene.control.TextArea - -trait JFXConsoleStreamable[T] { - def println(inst: T, text: String): Unit - def print(inst: T, text: String): Unit -} - -class JFXConsoleStream[T]( - outputStream: OutputStream, - val config: JFXConsoleStream.Config = JFXConsoleStream.Config.default, - control: T -)(implicit - jcs: JFXConsoleStreamable[T] -) extends PrintStream(outputStream, true) { - private lazy val defaultOut = System.out - override def println(text: String): Unit = - if (config.exclusive) { - jcs.println(control, text + "\n") - } else { - defaultOut.println(text) - jcs.println(control, text + "\n") - } - override def print(text: String): Unit = jcs.println(control, text) -} - -object JFXConsoleStream { - - /** - * for future use - */ - case class Config(exclusive: Boolean = false) - object Config { - lazy val default = Config() - } - - implicit val implJFXConsoleStreamForTextArea = - new JFXConsoleStreamable[scalafx.scene.control.TextArea] { - - override def println( - ta: scalafx.scene.control.TextArea, - text: String - ): Unit = - ta.appendText(text + "\n") - - override def print( - ta: scalafx.scene.control.TextArea, - text: String - ): Unit = - ta.appendText(text) - - } - - def textAreaStream(ta: TextArea) = - Resource.make( - Task( - new JFXConsoleStream( - outputStream = new ByteArrayOutputStream(), - control = ta - ) - ) - )(s => Task(s.close())) - -} diff --git a/src/main/scala/wow/doge/mygame/utils/ResizeHelper.java b/src/main/scala/wow/doge/mygame/utils/ResizeHelper.java new file mode 100644 index 0000000..5bb2086 --- /dev/null +++ b/src/main/scala/wow/doge/mygame/utils/ResizeHelper.java @@ -0,0 +1,130 @@ +package wow.doge.mygame.utils; + +import javafx.event.EventHandler; +import javafx.event.EventType; +import javafx.geometry.Insets; +import javafx.scene.Cursor; +import javafx.scene.Scene; +import javafx.scene.input.MouseEvent; +import javafx.stage.Stage; + +//created by Alexander Berg +public class ResizeHelper { + + public static ResizeListener addResizeListener(Stage stage) { + ResizeListener resizeListener = new ResizeListener(stage); + Scene scene = stage.getScene(); + scene.addEventHandler(MouseEvent.MOUSE_MOVED, resizeListener); + scene.addEventHandler(MouseEvent.MOUSE_PRESSED, resizeListener); + scene.addEventHandler(MouseEvent.MOUSE_DRAGGED, resizeListener); + scene.addEventHandler(MouseEvent.MOUSE_EXITED, resizeListener); + scene.addEventHandler(MouseEvent.MOUSE_EXITED_TARGET, resizeListener); + + return resizeListener; + } + + public static class ResizeListener implements EventHandler { + private Stage stage; + private Scene scene; + private Cursor cursorEvent = Cursor.DEFAULT; + private int border = 4; + private double startX = 0; + private double startY = 0; + private double sceneOffsetX = 0; + private double sceneOffsetY = 0; + private double padTop = 0; + private double padRight = 0; + private double padBottom = 0; + private double padLeft = 0; + + public ResizeListener(Stage stage) { + this.stage = stage; + this.scene = stage.getScene(); + } + + public void setPadding(Insets padding) { + padTop = padding.getTop(); + padRight = padding.getRight(); + padBottom = padding.getBottom(); + padLeft = padding.getLeft(); + } + + @Override + public void handle(MouseEvent mouseEvent) { + EventType mouseEventType = mouseEvent.getEventType(); + + double mouseEventX = mouseEvent.getSceneX(), mouseEventY = mouseEvent.getSceneY(), + viewWidth = stage.getWidth() - padLeft - padRight, + viewHeight = stage.getHeight() - padTop - padBottom; + + if (MouseEvent.MOUSE_MOVED.equals(mouseEventType)) { + if (mouseEventX < border + padLeft && mouseEventY < border + padTop) { + cursorEvent = Cursor.NW_RESIZE; + } else if (mouseEventX < border + padLeft && mouseEventY > viewHeight - border + padTop) { + cursorEvent = Cursor.SW_RESIZE; + } else if (mouseEventX > viewWidth - border + padLeft && mouseEventY < border + padTop) { + cursorEvent = Cursor.NE_RESIZE; + } else if (mouseEventX > viewWidth - border + padLeft && mouseEventY > viewHeight - border + padTop) { + cursorEvent = Cursor.SE_RESIZE; + } else if (mouseEventX < border + padLeft) { + cursorEvent = Cursor.W_RESIZE; + } else if (mouseEventX > viewWidth - border + padLeft) { + cursorEvent = Cursor.E_RESIZE; + } else if (mouseEventY < border + padTop) { + cursorEvent = Cursor.N_RESIZE; + } else if (mouseEventY > viewHeight - border + padTop) { + cursorEvent = Cursor.S_RESIZE; + } else { + cursorEvent = Cursor.DEFAULT; + } + + scene.setCursor(cursorEvent); + } else if (MouseEvent.MOUSE_EXITED.equals(mouseEventType) + || MouseEvent.MOUSE_EXITED_TARGET.equals(mouseEventType)) { + scene.setCursor(Cursor.DEFAULT); + } else if (MouseEvent.MOUSE_PRESSED.equals(mouseEventType)) { + startX = viewWidth - mouseEventX; + startY = viewHeight - mouseEventY; + sceneOffsetX = mouseEvent.getSceneX(); + sceneOffsetY = mouseEvent.getSceneY(); + } else if (MouseEvent.MOUSE_DRAGGED.equals(mouseEventType) && !Cursor.DEFAULT.equals(cursorEvent)) { + if (!Cursor.W_RESIZE.equals(cursorEvent) && !Cursor.E_RESIZE.equals(cursorEvent)) { + double minHeight = stage.getMinHeight() > (border * 2) ? stage.getMinHeight() : (border * 2); + + if (Cursor.NW_RESIZE.equals(cursorEvent) || Cursor.N_RESIZE.equals(cursorEvent) + || Cursor.NE_RESIZE.equals(cursorEvent)) { + if (stage.getHeight() > minHeight || mouseEventY < 0) { + double height = stage.getY() - mouseEvent.getScreenY() + stage.getHeight() + sceneOffsetY; + double y = mouseEvent.getScreenY() - sceneOffsetY; + + stage.setHeight(height); + stage.setY(y); + } + } else { + if (stage.getHeight() > minHeight || mouseEventY + startY - stage.getHeight() > 0) { + stage.setHeight(mouseEventY + startY + padBottom + padTop); + } + } + } + + if (!Cursor.N_RESIZE.equals(cursorEvent) && !Cursor.S_RESIZE.equals(cursorEvent)) { + double minWidth = stage.getMinWidth() > (border * 2) ? stage.getMinWidth() : (border * 2); + if (Cursor.NW_RESIZE.equals(cursorEvent) || Cursor.W_RESIZE.equals(cursorEvent) + || Cursor.SW_RESIZE.equals(cursorEvent)) { + if (stage.getWidth() > minWidth || mouseEventX < 0) { + double width = stage.getX() - mouseEvent.getScreenX() + stage.getWidth() + sceneOffsetX; + double x = mouseEvent.getScreenX() - sceneOffsetX; + + stage.setWidth(width); + stage.setX(x); + } + } else { + if (stage.getWidth() > minWidth || mouseEventX + startX - stage.getWidth() > 0) { + stage.setWidth(mouseEventX + startX + padLeft + padRight); + } + } + } + } + } + } +} \ No newline at end of file diff --git a/src/main/scala/wow/doge/mygame/utils/TreeTest.scala b/src/main/scala/wow/doge/mygame/utils/TreeTest.scala new file mode 100644 index 0000000..2f112ee --- /dev/null +++ b/src/main/scala/wow/doge/mygame/utils/TreeTest.scala @@ -0,0 +1,24 @@ +package wow.doge.mygame.utils + +import monix.execution.atomic.AtomicAny + +/** + * Useless + */ +sealed abstract class Tree[+T] +case class Node[T](data: T, children: AtomicAny[LazyList[Tree[T]]]) + extends Tree[T] { + def add(data: T) = { + children.transform(children => + Node(data, AtomicAny(LazyList[Tree[T]]())) #:: children + ) + } +} +// case object Leaf extends Tree[Nothing] +case class Data(data: Int) + +class TreeManager[T] { +// val root: AtomicAny[Tree[T]] = AtomicAny(Leaf) + + def add(data: T, node: Node[T]) = {} +}