mirror of
https://github.com/jlengrand/korge-samples.git
synced 2026-03-10 08:31:18 +00:00
More work on UI
This commit is contained in:
@@ -1,21 +1,6 @@
|
||||
import com.soywiz.kds.*
|
||||
import com.soywiz.korag.*
|
||||
import com.soywiz.korge.component.*
|
||||
import com.soywiz.korge.html.*
|
||||
import com.soywiz.korge.input.*
|
||||
import com.soywiz.korge.render.*
|
||||
import com.soywiz.korge.view.*
|
||||
import com.soywiz.korim.bitmap.*
|
||||
import com.soywiz.korim.format.*
|
||||
import com.soywiz.korio.util.encoding.*
|
||||
import com.soywiz.korma.geom.*
|
||||
import kotlin.properties.*
|
||||
|
||||
open class UISkin(
|
||||
val normal: BmpSlice,
|
||||
val hover: BmpSlice,
|
||||
val down: BmpSlice
|
||||
)
|
||||
|
||||
private val DEFAULT_UI_SKIN_IMG by lazy {
|
||||
PNG.decode(
|
||||
@@ -29,89 +14,3 @@ object DefaultUISkin : UISkin(
|
||||
down = DEFAULT_UI_SKIN_IMG.sliceWithSize(127, 0, 64, 64)
|
||||
)
|
||||
|
||||
open class UIButton(
|
||||
width: Double = 128.0,
|
||||
height: Double = 64.0,
|
||||
label: String = "Button",
|
||||
skin: UISkin = DefaultUISkin
|
||||
) : UIView() {
|
||||
var skin: UISkin by Delegates.observable(skin) { _, _, _ -> updateState() }
|
||||
var label by Delegates.observable(label) { _, _, _ -> updateState() }
|
||||
private val rect = ninePatch(skin.normal, width, height, 16.0 / 64.0, 16.0 / 64.0, (64.0 - 16.0) / 64.0, (64.0 - 16.0) / 64.0) {}
|
||||
private val text = text(label)
|
||||
private var bover by Delegates.observable(false) { _, _, _ -> updateState() }
|
||||
private var bpressing by Delegates.observable(false) { _, _, _ -> updateState() }
|
||||
init {
|
||||
this.width = width
|
||||
this.height = height
|
||||
mouse {
|
||||
onOver {
|
||||
bover = true
|
||||
}
|
||||
onOut {
|
||||
bover = false
|
||||
}
|
||||
onDown {
|
||||
bpressing = true
|
||||
}
|
||||
onUpAnywhere {
|
||||
bpressing = false
|
||||
}
|
||||
}
|
||||
updateState()
|
||||
}
|
||||
|
||||
private fun updateState() {
|
||||
when {
|
||||
bpressing -> {
|
||||
rect.tex = skin.down
|
||||
}
|
||||
bover -> {
|
||||
rect.tex = skin.hover
|
||||
}
|
||||
else -> {
|
||||
rect.tex = skin.normal
|
||||
}
|
||||
}
|
||||
text.format = Html.Format(align = Html.Alignment.MIDDLE_CENTER)
|
||||
text.setTextBounds(Rectangle(0, 0, width, height))
|
||||
text.setText(label)
|
||||
}
|
||||
|
||||
override fun updatedSize() {
|
||||
super.updatedSize()
|
||||
rect.width = width
|
||||
rect.height = height
|
||||
}
|
||||
}
|
||||
|
||||
open class UIView : Container() {
|
||||
override var width: Double by Delegates.observable(96.0) { prop, old, new -> updatedSize() }
|
||||
override var height: Double by Delegates.observable(32.0) { prop, old, new -> updatedSize() }
|
||||
|
||||
protected open fun updatedSize() {
|
||||
}
|
||||
|
||||
override fun renderInternal(ctx: RenderContext) {
|
||||
registerUISupportOnce(stage)
|
||||
super.renderInternal(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
fun registerUISupportOnce(stage: Stage?) {
|
||||
if (stage == null) return
|
||||
if (stage.getExtra("uiSupport") == true) return
|
||||
stage.setExtra("uiSupport", true)
|
||||
stage.keys {
|
||||
onKeyDown {
|
||||
|
||||
}
|
||||
}
|
||||
stage?.getOrCreateComponent { stage ->
|
||||
object : UpdateComponentWithViews {
|
||||
override val view: View = stage
|
||||
override fun update(views: Views, ms: Double) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
85
sample-ui/src/commonMain/kotlin/MouseDrag.kt
Normal file
85
sample-ui/src/commonMain/kotlin/MouseDrag.kt
Normal file
@@ -0,0 +1,85 @@
|
||||
import com.soywiz.korge.input.*
|
||||
import com.soywiz.korge.view.*
|
||||
import com.soywiz.korma.geom.*
|
||||
|
||||
// @TODO: Move to Korge
|
||||
data class MouseDragInfo(
|
||||
var dx: Double = 0.0,
|
||||
var dy: Double = 0.0,
|
||||
var start: Boolean = false,
|
||||
var end: Boolean = false
|
||||
) {
|
||||
fun set(dx: Double, dy: Double, start: Boolean, end: Boolean) = this.apply {
|
||||
this.dx = dx
|
||||
this.dy = dy
|
||||
this.start = start
|
||||
this.end = end
|
||||
}
|
||||
}
|
||||
|
||||
fun <T : View> T.onMouseDrag(callback: Views.(MouseDragInfo) -> Unit): T {
|
||||
var dragging = false
|
||||
var sx = 0.0
|
||||
var sy = 0.0
|
||||
var cx = 0.0
|
||||
var cy = 0.0
|
||||
val view = this
|
||||
|
||||
val info = MouseDragInfo()
|
||||
val mousePos = Point()
|
||||
|
||||
fun views() = view.stage!!.views
|
||||
|
||||
fun updateMouse() {
|
||||
val views = views()
|
||||
mousePos.setTo(
|
||||
views.nativeMouseX,
|
||||
views.nativeMouseY
|
||||
)
|
||||
}
|
||||
|
||||
this.mouse {
|
||||
onDown {
|
||||
updateMouse()
|
||||
dragging = true
|
||||
sx = mousePos.x
|
||||
sy = mousePos.y
|
||||
info.dx = 0.0
|
||||
info.dy = 0.0
|
||||
callback(views(), info.set(0.0, 0.0, true, false))
|
||||
}
|
||||
onUpAnywhere {
|
||||
if (dragging) {
|
||||
updateMouse()
|
||||
dragging = false
|
||||
cx = mousePos.x
|
||||
cy = mousePos.y
|
||||
callback(views(), info.set(cx - sx, cy - sy, false, true))
|
||||
}
|
||||
}
|
||||
onMoveAnywhere {
|
||||
if (dragging) {
|
||||
updateMouse()
|
||||
cx = mousePos.x
|
||||
cy = mousePos.y
|
||||
callback(views(), info.set(cx - sx, cy - sy, false, false))
|
||||
}
|
||||
}
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
fun <T : View> T.draggable(): T = this.apply {
|
||||
val view = this
|
||||
var sx = 0.0
|
||||
var sy = 0.0
|
||||
onMouseDrag { info ->
|
||||
if (info.start) {
|
||||
sx = view.x
|
||||
sy = view.y
|
||||
}
|
||||
view.x = sx + info.dx
|
||||
view.y = sy + info.dy
|
||||
//println("DRAG: $dx, $dy, $start, $end")
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,141 @@
|
||||
import com.soywiz.kmem.*
|
||||
import com.soywiz.korge.*
|
||||
import com.soywiz.korge.input.*
|
||||
import com.soywiz.korge.view.*
|
||||
import com.soywiz.korgw.*
|
||||
import kotlin.jvm.*
|
||||
import com.soywiz.korim.color.*
|
||||
import com.soywiz.korio.async.*
|
||||
import com.soywiz.korma.geom.*
|
||||
import kotlin.math.*
|
||||
import kotlin.properties.*
|
||||
|
||||
suspend fun main() = Korge(quality = GameWindow.Quality.PERFORMANCE, title = "UI") {
|
||||
addChild(UIButton(256.0, 32.0).apply {
|
||||
x = 64.0
|
||||
y = 64.0
|
||||
/*
|
||||
uiButton(256.0, 32.0) {
|
||||
position(64, 64)
|
||||
onClick {
|
||||
println("CLICKED!")
|
||||
}
|
||||
draggable()
|
||||
}
|
||||
*/
|
||||
addChild(UIScrollBar(256.0, 32.0, 0.0, 16.0, 64.0).position(64, 64).apply {
|
||||
onChange {
|
||||
println(it.ratio)
|
||||
}
|
||||
})
|
||||
addChild(UIScrollBar(32.0, 256.0, 0.0, 16.0, 64.0).position(64, 128).apply {
|
||||
onChange {
|
||||
println(it.ratio)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
open class UIScrollBar(
|
||||
width: Double,
|
||||
height: Double,
|
||||
current: Double,
|
||||
pageSize: Double,
|
||||
totalSize: Double,
|
||||
buttonSize: Double = 32.0,
|
||||
direction: Direction = if (width > height) Direction.Horizontal else Direction.Vertical,
|
||||
var stepSize: Double = pageSize / 10.0
|
||||
) : UIView() {
|
||||
val onChange = Signal<UIScrollBar>()
|
||||
enum class Direction { Vertical, Horizontal }
|
||||
var current by Delegates.observable(current) { _, _, _ -> updatedPos() }
|
||||
var pageSize by Delegates.observable(pageSize) { _, _, _ -> updatedPos() }
|
||||
var totalSize by Delegates.observable(totalSize) { _, _, _ -> updatedPos() }
|
||||
var direction by Delegates.observable(direction) { _, _, _ -> reshape() }
|
||||
val isHorizontal get() = direction == Direction.Horizontal
|
||||
val isVertical get() = direction == Direction.Vertical
|
||||
override var ratio: Double
|
||||
set(value) = run { current = value.clamp01() * (totalSize - pageSize) }
|
||||
get() = (current / (totalSize - pageSize)).clamp(0.0, 1.0)
|
||||
override var width: Double by Delegates.observable(width) { _, _, _ -> reshape() }
|
||||
override var height: Double by Delegates.observable(height) { _, _, _ -> reshape() }
|
||||
var buttonSize by Delegates.observable(buttonSize) { _, _, _ -> reshape() }
|
||||
val buttonWidth get() = if (isHorizontal) buttonSize else width
|
||||
val buttonHeight get() = if (isHorizontal) height else buttonSize
|
||||
val clientWidth get() = if (isHorizontal) width - buttonWidth * 2 else width
|
||||
val clientHeight get() = if (isHorizontal) height else height - buttonHeight * 2
|
||||
|
||||
protected val background = solidRect(100, 100, Colors.DARKGREY)
|
||||
protected val lessButton = uiButton(16, 16, "-")
|
||||
protected val moreButton = uiButton(16, 16, "+")
|
||||
protected val caretButton = uiButton(16, 16, "")
|
||||
|
||||
protected val views get() = stage?.views
|
||||
|
||||
init {
|
||||
reshape()
|
||||
|
||||
var slx: Double = 0.0
|
||||
var sly: Double = 0.0
|
||||
var iratio: Double = 0.0
|
||||
var sratio: Double = 0.0
|
||||
val tempP = Point()
|
||||
|
||||
lessButton.onDown {
|
||||
deltaCurrent(-stepSize)
|
||||
reshape()
|
||||
}
|
||||
moreButton.onDown {
|
||||
deltaCurrent(+stepSize)
|
||||
reshape()
|
||||
}
|
||||
background.onClick {
|
||||
val pos = if (isHorizontal) caretButton.localMouseX(views!!) else caretButton.localMouseY(views!!)
|
||||
deltaCurrent(pageSize * pos.sign)
|
||||
}
|
||||
caretButton.onMouseDrag {
|
||||
val lmouse = background.localMouseXY(views, tempP)
|
||||
val lx = lmouse.x
|
||||
val ly = lmouse.y
|
||||
val cratio = if (isHorizontal) lmouse.x / background.width else lmouse.y / background.height
|
||||
if (it.start) {
|
||||
slx = lx
|
||||
sly = ly
|
||||
iratio = ratio
|
||||
sratio = cratio
|
||||
}
|
||||
val dratio = cratio - sratio
|
||||
ratio = iratio + dratio
|
||||
reshape()
|
||||
}
|
||||
}
|
||||
|
||||
private fun deltaCurrent(value: Double) {
|
||||
current = (current + value).clamp(0.0, totalSize)
|
||||
}
|
||||
|
||||
private fun reshape() {
|
||||
if (isHorizontal) {
|
||||
background.position(buttonWidth, 0).size(clientWidth, clientHeight)
|
||||
lessButton.position(0, 0).size(buttonWidth, buttonHeight)
|
||||
moreButton.position(width - buttonWidth, 0).size(buttonWidth, buttonHeight)
|
||||
val caretWidth = clientWidth * (pageSize / totalSize)
|
||||
caretButton.position(buttonWidth + (clientWidth - caretWidth) * ratio, 0).size(caretWidth, buttonHeight)
|
||||
} else {
|
||||
background.position(0, buttonHeight).size(clientWidth, clientHeight)
|
||||
lessButton.position(0, 0).size(buttonWidth, buttonHeight)
|
||||
moreButton.position(0, height - buttonHeight).size(buttonWidth, buttonHeight)
|
||||
val caretHeight = clientHeight * (pageSize / totalSize)
|
||||
caretButton.position(0, buttonHeight + (clientHeight - caretHeight) * ratio).size(buttonWidth, caretHeight)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updatedPos() {
|
||||
reshape()
|
||||
onChange(this)
|
||||
}
|
||||
}
|
||||
|
||||
// @TODO: Move to Korge
|
||||
inline fun <T : View> T.size(width: Number, height: Number): T = this.apply {
|
||||
this.width = width.toDouble()
|
||||
this.height = height.toDouble()
|
||||
}
|
||||
|
||||
// @TODO: Move to Korge
|
||||
inline fun <T : View> T.localMouseXY(views: Views, target: Point = Point()): Point = target.setTo(localMouseX(views), localMouseY(views))
|
||||
|
||||
75
sample-ui/src/commonMain/kotlin/UIButton.kt
Normal file
75
sample-ui/src/commonMain/kotlin/UIButton.kt
Normal file
@@ -0,0 +1,75 @@
|
||||
import com.soywiz.korge.html.*
|
||||
import com.soywiz.korge.input.*
|
||||
import com.soywiz.korge.view.*
|
||||
import com.soywiz.korim.color.*
|
||||
import com.soywiz.korma.geom.*
|
||||
import kotlin.properties.*
|
||||
|
||||
inline fun Container.uiButton(
|
||||
width: Number = 128,
|
||||
height: Number = 64,
|
||||
label: String = "Button",
|
||||
skin: UISkin = DefaultUISkin,
|
||||
block: UIButton.() -> Unit = {}
|
||||
): UIButton = UIButton(width.toDouble(), height.toDouble(), label, skin).also { addChild(it) }.apply(block)
|
||||
|
||||
open class UIButton(
|
||||
width: Double = 128.0,
|
||||
height: Double = 64.0,
|
||||
label: String = "Button",
|
||||
skin: UISkin = DefaultUISkin
|
||||
) : UIView() {
|
||||
var skin: UISkin by Delegates.observable(skin) { _, _, _ -> updateState() }
|
||||
var label by Delegates.observable(label) { _, _, _ -> updateState() }
|
||||
private val rect = ninePatch(skin.normal, width, height, 16.0 / 64.0, 16.0 / 64.0, (64.0 - 16.0) / 64.0, (64.0 - 16.0) / 64.0) {}
|
||||
private val textShadow = text(label).also { it.position(1, 1) }
|
||||
private val text = text(label)
|
||||
private var bover by Delegates.observable(false) { _, _, _ -> updateState() }
|
||||
private var bpressing by Delegates.observable(false) { _, _, _ -> updateState() }
|
||||
init {
|
||||
this.width = width
|
||||
this.height = height
|
||||
mouse {
|
||||
onOver {
|
||||
bover = true
|
||||
}
|
||||
onOut {
|
||||
bover = false
|
||||
}
|
||||
onDown {
|
||||
bpressing = true
|
||||
}
|
||||
onUpAnywhere {
|
||||
bpressing = false
|
||||
}
|
||||
}
|
||||
updateState()
|
||||
}
|
||||
|
||||
private fun updateState() {
|
||||
when {
|
||||
bpressing -> {
|
||||
rect.tex = skin.down
|
||||
}
|
||||
bover -> {
|
||||
rect.tex = skin.hover
|
||||
}
|
||||
else -> {
|
||||
rect.tex = skin.normal
|
||||
}
|
||||
}
|
||||
text.format = Html.Format(align = Html.Alignment.MIDDLE_CENTER)
|
||||
text.setTextBounds(Rectangle(0, 0, width, height))
|
||||
text.setText(label)
|
||||
textShadow.format = Html.Format(align = Html.Alignment.MIDDLE_CENTER, color = Colors.BLACK.withA(64))
|
||||
textShadow.setTextBounds(Rectangle(0, 0, width, height))
|
||||
textShadow.setText(label)
|
||||
}
|
||||
|
||||
override fun updatedSize() {
|
||||
super.updatedSize()
|
||||
rect.width = width
|
||||
rect.height = height
|
||||
updateState()
|
||||
}
|
||||
}
|
||||
7
sample-ui/src/commonMain/kotlin/UISkin.kt
Normal file
7
sample-ui/src/commonMain/kotlin/UISkin.kt
Normal file
@@ -0,0 +1,7 @@
|
||||
import com.soywiz.korim.bitmap.*
|
||||
|
||||
open class UISkin(
|
||||
val normal: BmpSlice,
|
||||
val hover: BmpSlice,
|
||||
val down: BmpSlice
|
||||
)
|
||||
41
sample-ui/src/commonMain/kotlin/UIView.kt
Normal file
41
sample-ui/src/commonMain/kotlin/UIView.kt
Normal file
@@ -0,0 +1,41 @@
|
||||
import com.soywiz.kds.*
|
||||
import com.soywiz.korge.component.*
|
||||
import com.soywiz.korge.input.*
|
||||
import com.soywiz.korge.render.*
|
||||
import com.soywiz.korge.view.*
|
||||
import kotlin.properties.*
|
||||
|
||||
open class UIView : Container() {
|
||||
override var width: Double by Delegates.observable(96.0) { prop, old, new -> updatedSize() }
|
||||
override var height: Double by Delegates.observable(32.0) { prop, old, new -> updatedSize() }
|
||||
|
||||
protected open fun updatedSize() {
|
||||
}
|
||||
|
||||
override fun renderInternal(ctx: RenderContext) {
|
||||
registerUISupportOnce()
|
||||
super.renderInternal(ctx)
|
||||
}
|
||||
|
||||
private var registered = false
|
||||
private fun registerUISupportOnce() {
|
||||
if (registered) return
|
||||
val stage = stage ?: return
|
||||
registered = true
|
||||
if (stage.getExtra("uiSupport") == true) return
|
||||
stage.setExtra("uiSupport", true)
|
||||
stage.keys {
|
||||
onKeyDown {
|
||||
|
||||
}
|
||||
}
|
||||
stage?.getOrCreateComponent { stage ->
|
||||
object : UpdateComponentWithViews {
|
||||
override val view: View = stage
|
||||
override fun update(views: Views, ms: Double) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user