Skip to content

Commit

Permalink
New Camera API (experimental) (#239)
Browse files Browse the repository at this point in the history
Initial implementation of new Camera API

Co-authored-by: soywiz <soywiz@gmail.com>
  • Loading branch information
RezMike and soywiz authored Jul 11, 2020
1 parent e46b263 commit abc43c4
Show file tree
Hide file tree
Showing 6 changed files with 422 additions and 9 deletions.
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ kryptoVersion=1.11.1
kloggerVersion=1.10.1
korinjectVersion=1.10.2
kbox2dVersion=0.6.1
kdsVersion=1.10.13

# bintray location
project.bintray.org=korlibs
Expand All @@ -33,7 +34,6 @@ systemProp.org.gradle.internal.repository.max.retries=7
systemProp.org.gradle.internal.repository.initial.backoff=500

kotlin.incremental.multiplatform=true
kotlin.native.disableCompilerDaemon=true
#org.gradle.parallel=true
#org.gradle.parallel.intra=true
org.gradle.configureondemand=true
Expand Down
2 changes: 2 additions & 0 deletions korge/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ val korgwVersion: String by project
val kryptoVersion: String by project
val korinjectVersion: String by project
val kloggerVersion: String by project
val kdsVersion: String by project

dependencies {
add("commonMainApi", "com.soywiz.korlibs.klock:klock:$klockVersion")
Expand All @@ -32,4 +33,5 @@ dependencies {
add("commonMainApi", "com.soywiz.korlibs.krypto:krypto:$kryptoVersion")
add("commonMainApi", "com.soywiz.korlibs.korinject:korinject:$korinjectVersion")
add("commonMainApi", "com.soywiz.korlibs.klogger:klogger:$kloggerVersion")
add("commonMainApi", "com.soywiz.korlibs.kds:kds:$kdsVersion")
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,38 @@ import com.soywiz.korge.render.*
import com.soywiz.korio.util.*
import com.soywiz.korma.geom.*

inline fun Container.fixedSizeContainer(width: Double, height: Double, clip: Boolean = false, callback: @ViewDslMarker FixedSizeContainer.() -> Unit = {}) =
FixedSizeContainer(width, height, clip).addTo(this, callback)

inline fun Container.fixedSizeContainer(width: Int, height: Int, clip: Boolean = false, callback: @ViewDslMarker FixedSizeContainer.() -> Unit = {}) =
FixedSizeContainer(width.toDouble(), height.toDouble(), clip).addTo(this, callback)
inline fun Container.fixedSizeContainer(
width: Double,
height: Double,
clip: Boolean = false,
callback: @ViewDslMarker FixedSizeContainer.() -> Unit = {}
) = FixedSizeContainer(width, height, clip).addTo(this, callback)

inline fun Container.fixedSizeContainer(
width: Int,
height: Int,
clip: Boolean = false,
callback: @ViewDslMarker FixedSizeContainer.() -> Unit = {}
) = FixedSizeContainer(width.toDouble(), height.toDouble(), clip).addTo(this, callback)

@Deprecated("Kotlin/Native boxes inline+Number")
inline fun Container.fixedSizeContainer(width: Number, height: Number, clip: Boolean = false, callback: @ViewDslMarker FixedSizeContainer.() -> Unit = {}) =
fixedSizeContainer(width.toDouble(), height.toDouble(), clip, callback)
inline fun Container.fixedSizeContainer(
width: Number,
height: Number,
clip: Boolean = false,
callback: @ViewDslMarker FixedSizeContainer.() -> Unit = {}
) = fixedSizeContainer(width.toDouble(), height.toDouble(), clip, callback)


open class FixedSizeContainer(
override var width: Double = 100.0,
override var height: Double = 100.0,
var clip: Boolean = false
) : Container(), View.Reference {
override fun getLocalBoundsInternal(out: Rectangle): Unit = Unit.run { out.setTo(0, 0, width, height) }

override fun getLocalBoundsInternal(out: Rectangle) {
out.setTo(0, 0, width, height)
}

override fun toString(): String {
var out = super.toString()
Expand Down
224 changes: 224 additions & 0 deletions korge/src/commonMain/kotlin/com/soywiz/korge/view/camera/Camera.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
package com.soywiz.korge.view.camera

import com.soywiz.kds.*
import com.soywiz.klock.*
import com.soywiz.korge.annotations.*
import com.soywiz.korge.tween.*
import com.soywiz.korge.view.*
import com.soywiz.korio.lang.*
import com.soywiz.korma.geom.*
import com.soywiz.korma.interpolation.*
import kotlin.math.*

@KorgeExperimental
class Camera(
x: Double = 0.0,
y: Double = 0.0,
width: Double = 100.0,
height: Double = 100.0,
zoom: Double = 1.0,
angle: Angle = 0.degrees,
override var anchorX: Double = 0.0,
override var anchorY: Double = 0.0
) : MutableInterpolable<Camera>, Interpolable<Camera>, Anchorable {

var x: Double by Observable(x, before = { if (withUpdate) setTo(x = it) })
var y: Double by Observable(y, before = { if (withUpdate) setTo(y = it) })
var width: Double by Observable(width, before = { if (withUpdate) setTo(width = it) })
var height: Double by Observable(height, before = { if (withUpdate) setTo(height = it) })
var zoom: Double by Observable(zoom, before = { if (withUpdate) setTo(zoom = it) })
var angle: Angle by Observable(angle, before = { if (withUpdate) setTo(angle = it) })

internal var container: CameraContainer? = null
private var cancelFollowing: Cancellable? = null
private var withUpdate: Boolean = true

internal fun attach(container: CameraContainer) {
this.container = container
}

internal fun detach() {
container = null
}

// when we don't want to update CameraContainer
private inline fun withoutUpdate(callback: Camera.() -> Unit) {
val prev = withUpdate
withUpdate = false
callback()
withUpdate = prev
}

fun setTo(other: Camera) = setTo(
other.x,
other.y,
other.width,
other.height,
other.zoom,
other.angle,
other.anchorX,
other.anchorY
)

fun setTo(
x: Double = this.x,
y: Double = this.y,
width: Double = this.width,
height: Double = this.height,
zoom: Double = this.zoom,
angle: Angle = this.angle,
anchorX: Double = this.anchorX,
anchorY: Double = this.anchorY
): Camera {
if (withUpdate) {
container?.setTo(x, y, width, height, zoom, angle, anchorX, anchorY)
}
withoutUpdate {
this.x = x
this.y = y
this.width = width
this.height = height
this.zoom = zoom
this.angle = angle
this.anchorX = anchorX
this.anchorY = anchorY
}
return this
}

suspend fun tweenTo(other: Camera, time: TimeSpan, easing: Easing = Easing.LINEAR) = tweenTo(
x = other.x,
y = other.y,
width = other.width,
height = other.height,
zoom = other.zoom,
angle = other.angle,
anchorX = other.anchorX,
anchorY = other.anchorY,
time = time,
easing = easing
)

suspend fun tweenTo(
x: Double = this.x,
y: Double = this.y,
width: Double = this.width,
height: Double = this.height,
zoom: Double = this.zoom,
angle: Angle = this.angle,
anchorX: Double = this.anchorX,
anchorY: Double = this.anchorY,
time: TimeSpan,
easing: Easing = Easing.LINEAR
) {
val initialX = this.x
val initialY = this.y
val initialWidth = this.width
val initialHeight = this.height
val initialZoom = this.zoom
val initialAngle = this.angle
val initialAnchorX = this.anchorX
val initialAnchorY = this.anchorY
container?.tween(time = time, easing = easing) { ratio ->
setTo(
ratio.interpolate(initialX, x),
ratio.interpolate(initialY, y),
ratio.interpolate(initialWidth, width),
ratio.interpolate(initialHeight, height),
ratio.interpolate(initialZoom, zoom),
ratio.interpolate(initialAngle, angle),
ratio.interpolate(initialAnchorX, anchorX),
ratio.interpolate(initialAnchorY, anchorY)
)
}
}

fun follow(view: View, threshold: Double = 0.0) {
val inside = container?.content?.let { view.hasAncestor(it) } ?: false
if (!inside) throw IllegalStateException("Can't follow view that is not in the content of CameraContainer")
cancelFollowing?.cancel()
cancelFollowing = view.addUpdater {
val camera = this@Camera
val x = view.x + view.width / 2
val y = view.y + view.height / 2
val dx = x - camera.x
val dy = y - camera.y
if (abs(dx) > threshold || abs(dy) > threshold) {
camera.xy(x - dx.sign * threshold, y - dy.sign * threshold)
}
}
}

fun unfollow() {
cancelFollowing?.cancel()
}

fun copy(
x: Double = this.x,
y: Double = this.y,
width: Double = this.width,
height: Double = this.height,
zoom: Double = this.zoom,
angle: Angle = this.angle,
anchorX: Double = this.anchorX,
anchorY: Double = this.anchorY
) = Camera(
x = x,
y = y,
width = width,
height = height,
zoom = zoom,
angle = angle,
anchorX = anchorX,
anchorY = anchorY
)

override fun setToInterpolated(ratio: Double, l: Camera, r: Camera) = setTo(
x = ratio.interpolate(l.x, r.x),
y = ratio.interpolate(l.y, r.y),
width = ratio.interpolate(l.width, r.width),
height = ratio.interpolate(l.height, r.height),
zoom = ratio.interpolate(l.zoom, r.zoom),
angle = ratio.interpolate(l.angle, r.angle),
anchorX = ratio.interpolate(l.anchorX, r.anchorX),
anchorY = ratio.interpolate(l.anchorY, r.anchorY)
)

override fun interpolateWith(ratio: Double, other: Camera) = Camera().setToInterpolated(ratio, this, other)

override fun toString() = "Camera(" +
"x=$x, y=$y, width=$width, height=$height, " +
"zoom=$zoom, angle=$angle, anchorX=$anchorX, anchorY=$anchorY)"
}

fun Camera.xy(x: Double, y: Double): Camera {
return setTo(x = x, y = y)
}

fun Camera.size(width: Double, height: Double): Camera {
return setTo(width = width, height = height)
}

fun Camera.anchor(anchorX: Double, anchorY: Double): Camera {
return setTo(anchorX = anchorX, anchorY = anchorY)
}

suspend fun Camera.moveTo(x: Double, y: Double, time: TimeSpan, easing: Easing = Easing.LINEAR) {
tweenTo(x = x, y = y, time = time, easing = easing)
}

suspend fun Camera.moveBy(dx: Double, dy: Double, time: TimeSpan, easing: Easing = Easing.LINEAR) {
tweenTo(x = x + dx, y = y + dy, time = time, easing = easing)
}

suspend fun Camera.resizeTo(width: Double, height: Double, time: TimeSpan, easing: Easing = Easing.LINEAR) {
tweenTo(width = width, height = height, time = time, easing = easing)
}

suspend fun Camera.rotate(angle: Angle, time: TimeSpan, easing: Easing = Easing.LINEAR) {
tweenTo(angle = this.angle + angle, time = time, easing = easing)
}

suspend fun Camera.zoom(value: Double, time: TimeSpan, easing: Easing = Easing.LINEAR) {
tweenTo(zoom = value, time = time, easing = easing)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package com.soywiz.korge.view.camera

import com.soywiz.korge.annotations.*
import com.soywiz.korge.view.*
import com.soywiz.korma.geom.*

@KorgeExperimental
inline fun Container.cameraContainer(
width: Double,
height: Double,
camera: Camera = Camera(0.0, 0.0, width, height),
noinline decoration: @ViewDslMarker CameraContainer.() -> Unit = {},
content: @ViewDslMarker Container.() -> Unit = {}
) = CameraContainer(width, height, camera, decoration).addTo(this).also { content(it.content) }

@KorgeExperimental
class CameraContainer(
override var width: Double = 100.0,
override var height: Double = 100.0,
camera: Camera = Camera(0.0, 0.0, width, height),
decoration: @ViewDslMarker CameraContainer.() -> Unit = {}
) : FixedSizeContainer(width, height, true), View.Reference {

private val contentContainer = Container()

val content: Container = object : Container(), Reference {
override fun getLocalBoundsInternal(out: Rectangle) {
out.setTo(0, 0, this@CameraContainer.width, this@CameraContainer.height)
}
}

var camera: Camera = camera
set(newCamera) {
if (newCamera.container != null) throw IllegalStateException("You can't use Camera that is already attached")
camera.detach()
newCamera.attach(this)
setTo(newCamera)
field = newCamera
}

init {
decoration(this)
contentContainer.addTo(this)
content.addTo(contentContainer)
}

inline fun updateContent(action: Container.() -> Unit) {
content.action()
}

internal fun setTo(
x: Double,
y: Double,
width: Double,
height: Double,
zoom: Double,
angle: Angle,
anchorX: Double,
anchorY: Double
) {
val realScaleX = (content.unscaledWidth / width) * zoom
val realScaleY = (content.unscaledHeight / height) * zoom

val contentContainerX = width * anchorX
val contentContainerY = height * anchorY

content.x = -x
content.y = -y
contentContainer.x = contentContainerX
contentContainer.y = contentContainerY
contentContainer.rotation = angle
contentContainer.scaleX = realScaleX
contentContainer.scaleY = realScaleY
}

internal fun setTo(camera: Camera) = setTo(
camera.x,
camera.y,
camera.width,
camera.height,
camera.zoom,
camera.angle,
camera.anchorX,
camera.anchorY
)
}
Loading

0 comments on commit abc43c4

Please sign in to comment.