Scenes

While you can create a small application in a few lines, when the application grows in size, you will want to follow some patterns and to be able to split your application effectively.

KorGE includes an asynchronous dependency injector and some tools like the modules and scenes to do so.

Table of contents:

Module

Usually, you have one single module in your application. In the module you can describe some stuff from your module, as well as define a Scene entry point, and configure the asynchronous injector.

object MyModule : Module() {
    override val mainScene: KClass<out Scene> = MyScene::class

    override suspend fun init(injector: AsyncInjector): Unit = injector.run {
        mapInstance(MyDependency("HELLO WORLD"))
        mapPrototype { MyScene(get()) }
    }
}

And you use the module with:

suspend fun main() = Korge(Korge.Config(module = MyModule))

You can create several mains in different classes and packages using different entry point modules.

The Scene class

The Scene class looks like this:

abstract class Scene : InjectorAsyncDependency, ViewsContainer, CoroutineScope {
    // Context stuff
    val coroutineContext: CoroutineContext
    var injector: AsyncInjector
    var views: Views
    val ag: AG
    
    // Resources and lifecycle
    var resourcesRoot: ResourcesRoot
    val cancellables: CancellableGroup
    
    // Related to the scene
    var sceneContainer: SceneContainer
    val sceneView: Container
    
    // Main Entrypoint of the scene
    abstract suspend fun Container.sceneInit(): Unit
    
    // Lifecycle
    open suspend fun sceneAfterInit()
    open suspend fun sceneBeforeLeaving()
    open suspend fun sceneDestroy()
    open suspend fun sceneAfterDestroy()
}

And you usually declare scenes like this:

class MyScene : Scene() {
	override suspend fun Container.sceneInit(): Unit {
	    solidRect(100, 100, Colors.RED)
	}
}

Scenes are like controllers and allow you to split the application by Screens or Scenes. In a Scene you usually create Views and configure/decorate them giving them behaviour.

For each scene you will have to tell the injector how to construct it. Usually you do this mapping in the Module descriptor:

injector.mapPrototype { MyScene(get()) }

Inside the lambda of mapPrototype, you have injected this: AsyncInjector, that’s why you can use the method get(). Usually the get() method won’t require type parameters since it is generic and the type is inferred from the arguments.

The SceneContainer class

The SceneContainer is a View that will contain the view of a Scene.

class SceneContainer(val views: Views) : Container() {
	val transitionView = TransitionView()
	var currentScene: Scene? = null

	suspend inline fun <reified TScene : Scene> changeTo(vararg injects: Any, time: TimeSpan = 0.seconds, transition: Transition = AlphaTransition): TScene
	suspend inline fun <reified TScene : Scene> pushTo(vararg injects: Any, time: TimeSpan = 0.seconds, transition: Transition = AlphaTransition): TScene
	suspend fun back(time: TimeSpan = 0.seconds, transition: Transition = AlphaTransition): Scene
	suspend fun forward(time: TimeSpan = 0.seconds, transition: Transition = AlphaTransition): Scene
	
	suspend fun <TScene : Scene> pushTo(clazz: KClass<TScene>, vararg injects: Any, time: TimeSpan = 0.seconds, transition: Transition = AlphaTransition): TScene
	suspend fun <TScene : Scene> changeTo(clazz: KClass<TScene>, vararg injects: Any, time: TimeSpan = 0.seconds, transition: Transition = AlphaTransition): TScene
}

Like other views, the SceneContainer has a builder method to construct the instance and add it to a container:

inline fun Container.sceneContainer(views: Views, callback: SceneContainer.() -> Unit = {}): SceneContainer = SceneContainer(views).addTo(this).apply(callback)

Scenes have access to its container, so you can change the current scene to another one with:

class MyScene : Scene() {
	override suspend fun Container.sceneInit(): Unit {
	    solidRect(100, 100, Colors.RED).onClick { launchImmediately { sceneContainer.changeTo<OtherScene>() } }
	}
}