All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jvmMain.io.nacular.doodle.application.Application.kt Maven / Gradle / Ivy

There is a newer version: 0.10.4
Show newest version
package io.nacular.doodle.application

import io.nacular.doodle.core.Internal
import io.nacular.doodle.core.WindowGroup
import io.nacular.doodle.core.WindowGroupImpl
import io.nacular.doodle.core.WindowImpl
import io.nacular.doodle.datatransport.dragdrop.DragManager
import io.nacular.doodle.deviceinput.KeyboardFocusManager
import io.nacular.doodle.drawing.TextMetrics
import io.nacular.doodle.drawing.impl.DesktopRenderManagerImpl
import io.nacular.doodle.drawing.impl.RealGraphicsDevice
import io.nacular.doodle.drawing.impl.RealGraphicsSurfaceFactory
import io.nacular.doodle.drawing.impl.TextMetricsImpl
import io.nacular.doodle.geometry.Size
import io.nacular.doodle.scheduler.AnimationScheduler
import io.nacular.doodle.scheduler.Scheduler
import io.nacular.doodle.scheduler.Strand
import io.nacular.doodle.scheduler.impl.AnimationSchedulerImpl
import io.nacular.doodle.scheduler.impl.SchedulerImpl
import io.nacular.doodle.scheduler.impl.StrandImpl
import io.nacular.doodle.system.impl.DesktopPointerInputManagers
import io.nacular.doodle.theme.InternalThemeManager
import io.nacular.doodle.theme.Scene
import io.nacular.doodle.time.Timer
import io.nacular.doodle.time.impl.TimerImpl
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import kotlinx.coroutines.swing.Swing
import kotlinx.datetime.Clock
import org.jetbrains.skia.Canvas
import org.jetbrains.skia.Font
import org.jetbrains.skia.FontMgr
import org.jetbrains.skia.FontSlant.UPRIGHT
import org.jetbrains.skia.FontStyle
import org.jetbrains.skia.PathMeasure
import org.jetbrains.skia.Typeface
import org.jetbrains.skia.paragraph.FontCollection
import org.jetbrains.skiko.SkikoGestureEvent
import org.jetbrains.skiko.SkikoInputEvent
import org.jetbrains.skiko.SkikoKeyboardEvent
import org.jetbrains.skiko.SkikoPointerEvent
import org.jetbrains.skiko.SkikoView
import org.kodein.di.Copy.All
import org.kodein.di.DI.Companion.direct
import org.kodein.di.DI.Module
import org.kodein.di.DirectDI
import org.kodein.di.bind
import org.kodein.di.bindFactory
import org.kodein.di.bindInstance
import org.kodein.di.bindProvider
import org.kodein.di.bindSingleton
import org.kodein.di.bindings.NoArgBindingDI
import org.kodein.di.instance
import org.kodein.di.instanceOrNull
import org.kodein.di.provider
import org.kodein.di.singleton
import java.awt.Toolkit
import javax.swing.UIManager


/**
 * Created by Nicholas Eddy on 5/14/21.
 */
public inline fun  application(
        allowDefaultDarkMode: Boolean     = false,
        modules             : List = emptyList(),
        noinline creator    : NoArgBindingDI<*>.() -> T): Application = createApplication(direct {
    bind { singleton(creator = creator) }
}, allowDefaultDarkMode, modules)

/** @suppress */
@Internal
public fun createApplication(
        injector            : DirectDI,
        allowDefaultDarkMode: Boolean,
        modules             : List): Application = ApplicationHolderImpl(injector, allowDefaultDarkMode = allowDefaultDarkMode, modules = modules)

internal class CustomSkikoView: SkikoView {
    internal var onRender       : (canvas: Canvas, width: Int, height: Int, nanoTime: Long) -> Unit = { _,_,_,_ -> }
    internal var onKeyboardEvent: (SkikoKeyboardEvent) -> Unit = {}
    internal var onPointerEvent : (SkikoPointerEvent ) -> Unit = {}
    internal var onInputEvent   : (SkikoInputEvent   ) -> Unit = {}
    internal var onGestureEvent : (SkikoGestureEvent ) -> Unit = {}

    override fun onKeyboardEvent(event: SkikoKeyboardEvent) = onKeyboardEvent.invoke(event)
    override fun onPointerEvent (event: SkikoPointerEvent ) = onPointerEvent.invoke (event)
    override fun onInputEvent   (event: SkikoInputEvent   ) = onInputEvent.invoke   (event)
    override fun onGestureEvent (event: SkikoGestureEvent ) = onGestureEvent.invoke (event)

    override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) = onRender.invoke(canvas, width, height, nanoTime)
}

private open class ApplicationHolderImpl protected constructor(
    previousInjector    : DirectDI,
    allowDefaultDarkMode: Boolean      = false,
    modules             : List = emptyList()
): Application {

    private inner class ShutdownHook: Thread() {
        override fun run() {
            appScope.launch(Dispatchers.Swing) {
                shutdown()
            }
        }
    }

    private val defaultFontName = UIManager.getDefaults().getFont("defaultFont")?.fontName ?: "Courier"

    private val appScope       = CoroutineScope(SupervisorJob() + Dispatchers.Default)
    private val defaultFont    = Font(Typeface.makeFromName(defaultFontName, FontStyle(300, 5, UPRIGHT)), 13f)
    private val fontCollection = FontCollection().apply {
        setDefaultFontManager(FontMgr.default)
    }

    private var injector = direct {
        extend(previousInjector, copy = All)

        bindInstance                      { appScope          }
        bindInstance                      { Clock.System      }
        bindInstance { Dispatchers.Swing }

        bindInstance                      { defaultFont    }
        bindInstance                      { fontCollection }
        bindFactory    { PathMeasure()  }

        bind             () with singleton { TimerImpl             (instance()                        ) }
        bind             () with singleton { MultiDisplayScene     (provider()                        ) }
        bind            () with singleton { StrandImpl            (instance(), instance()            ) }
        bind         () with singleton { SchedulerImpl         (instance(), instance()            ) }
        bind       () with singleton { TextMetricsImpl       (instance(), instance()            ) }
        bind() with singleton { AnimationSchedulerImpl(instance(), instance(), instance()) }

        bind() with singleton {
            WindowGroupImpl { undecorated ->
                WindowImpl(
                    appScope       = instance(),
                    defaultFont    = instance(),
                    uiDispatcher   = instance(),
                    fontCollection = instance(),
                    graphicsDevices = { layer ->
                        RealGraphicsDevice(RealGraphicsSurfaceFactory(layer, instance(), instance()))
                    },
                    renderManagers = { display ->
                        DesktopRenderManagerImpl(display, instance(), instanceOrNull(), instanceOrNull())
                    },
                    undecorated = undecorated,
                    size = Toolkit.getDefaultToolkit().screenSize.run { Size(width, height) }
                )
            }
        }

        bindSingleton { instance().main.display }

        bindProvider { instance() as WindowGroupImpl }

        // TODO: Can this be handled better?
        bindSingleton { instance() as AnimationSchedulerImpl }
        bindSingleton { instance       () as TextMetricsImpl        }

        modules.forEach {
            import(it, allowOverride = true)
        }
    }

    private var isShutdown  = false
    private var application = null as Application?

    init {
        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName())

        System.setProperty("skiko.vsync.enabled", "false")
//        System.setProperty("skiko.fps.enabled",   "true" )
//        System.setProperty("skiko.renderApi", "OPENGL")

        Runtime.getRuntime().addShutdownHook(ShutdownHook())
    }

    protected fun run() {
        injector.instanceOrNull()
        injector.instanceOrNull       ()
        injector.instanceOrNull                ()
        injector.instanceOrNull()?.also {
            injector.instanceOrNull()

            it.start()
        }

        application = injector.instance()
    }

    override fun shutdown() {
        if (isShutdown) {
            return
        }

        application?.shutdown()

        injector.instanceOrNull            ()?.shutdown()
        (injector.instance                       () as? SchedulerImpl)?.shutdown()
        injector.instance           ().shutdown()
        injector.instanceOrNull                ()?.shutdown()
        injector.instanceOrNull()?.shutdown()
        injector.instanceOrNull       ()?.shutdown()

        injector = direct {}

        isShutdown = true
    }

    companion object {
        operator fun invoke(previousInjector    : DirectDI,
                            allowDefaultDarkMode: Boolean      = false,
                            modules             : List = emptyList()) = ApplicationHolderImpl(
            previousInjector,
            allowDefaultDarkMode,
            modules
        ).apply {
            appScope.launch(Dispatchers.Swing) {
                run()
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy