2019-06-26 01:52:17 +00:00
|
|
|
package net.shadowfacts.cacao.view.button
|
|
|
|
|
|
|
|
import net.minecraft.util.Identifier
|
|
|
|
import net.shadowfacts.cacao.Window
|
|
|
|
import net.shadowfacts.cacao.geometry.Axis
|
|
|
|
import net.shadowfacts.cacao.geometry.Point
|
|
|
|
import net.shadowfacts.cacao.geometry.Rect
|
|
|
|
import net.shadowfacts.cacao.util.MouseButton
|
2019-06-26 23:32:06 +00:00
|
|
|
import net.shadowfacts.cacao.util.texture.NinePatchTexture
|
|
|
|
import net.shadowfacts.cacao.util.texture.Texture
|
2019-06-26 01:52:17 +00:00
|
|
|
import net.shadowfacts.cacao.util.properties.ResettableLazyProperty
|
|
|
|
import net.shadowfacts.cacao.view.NinePatchView
|
|
|
|
import net.shadowfacts.cacao.view.StackView
|
|
|
|
import net.shadowfacts.cacao.view.TextureView
|
|
|
|
import net.shadowfacts.cacao.view.View
|
|
|
|
import net.shadowfacts.kiwidsl.dsl
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A button that provides a dropdown for the user to select from a list of values.
|
|
|
|
* The button itself shows a [ContentView] representing the currently selected [Value] and an image indicator that serves
|
|
|
|
* as a hint for the ability to click the button and display the dropdown.
|
|
|
|
*
|
|
|
|
* The dropdown list itself is displayed by presenting a new [Window] at the front of the window stack.
|
|
|
|
* Each possible value is represented in the list by a button containing a [ContentView] for that value, with the button
|
|
|
|
* for the current value being disabled.
|
|
|
|
*
|
|
|
|
* @author shadowfacts
|
|
|
|
* @param Value The type of value that the dropdown selects.
|
|
|
|
* @param ContentView The specific type of the [View] that represents selected item in the button and each item in the dropdown list.
|
|
|
|
* @param initialValue The initial value of the dropdown button.
|
|
|
|
* @param allValues List of all allowed values for the dropdown.
|
|
|
|
* @param createView A function that creates a [ContentView] representing the given [Value].
|
|
|
|
* Positioning of content views is handled by the dropdown.
|
|
|
|
* @param updateView A function for updating the view used as the button's 'label' that's visible even when the dropdown isn't.
|
|
|
|
*/
|
|
|
|
class DropdownButton<Value, ContentView: View>(
|
|
|
|
val initialValue: Value,
|
|
|
|
val allValues: Iterable<Value>,
|
|
|
|
val createView: (Value) -> ContentView,
|
|
|
|
val updateView: (newValue: Value, view: ContentView) -> Unit,
|
|
|
|
padding: Double = 4.0
|
|
|
|
): AbstractButton<DropdownButton<Value, ContentView>>(
|
|
|
|
StackView(Axis.HORIZONTAL),
|
|
|
|
padding
|
|
|
|
) {
|
|
|
|
|
|
|
|
companion object {
|
|
|
|
val DROPDOWN_INDICATOR = Texture(Identifier("asmr", "textures/gui/dropdown.png"), 0, 0)
|
|
|
|
}
|
|
|
|
|
|
|
|
private val stackView: StackView
|
|
|
|
get() = content as StackView
|
|
|
|
|
|
|
|
private val contentView: ContentView
|
|
|
|
get() = stackView.arrangedSubviews.first() as ContentView
|
|
|
|
|
|
|
|
private lateinit var dropdownIndicator: TextureView
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The currently selected [Value] of the dropdown.
|
|
|
|
*/
|
|
|
|
var value: Value = initialValue
|
|
|
|
set(value) {
|
|
|
|
field = value
|
|
|
|
updateView(value, contentView)
|
|
|
|
window.layout()
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun wasAdded() {
|
|
|
|
super.wasAdded()
|
|
|
|
|
|
|
|
stackView.addArrangedSubview(createView(initialValue))
|
|
|
|
dropdownIndicator = stackView.addArrangedSubview(TextureView(DROPDOWN_INDICATOR))
|
|
|
|
|
|
|
|
solver.dsl {
|
|
|
|
dropdownIndicator.widthAnchor equalTo 9
|
|
|
|
dropdownIndicator.heightAnchor equalTo 9
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun mouseClicked(point: Point, mouseButton: MouseButton): Boolean {
|
|
|
|
return if (mouseButton == MouseButton.LEFT || mouseButton == MouseButton.RIGHT) {
|
|
|
|
showDropdown()
|
|
|
|
true
|
|
|
|
} else {
|
|
|
|
super.mouseClicked(point, mouseButton)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun showDropdown() {
|
|
|
|
val dropdownWindow = window.screen.addWindow(Window())
|
2019-06-26 23:32:06 +00:00
|
|
|
val dropdownBackground = dropdownWindow.addView(NinePatchView(NinePatchTexture.BUTTON_BG).apply {
|
2019-06-26 01:52:17 +00:00
|
|
|
zIndex = -1.0
|
|
|
|
})
|
2019-06-27 22:53:11 +00:00
|
|
|
val stack = dropdownWindow.addView(StackView(Axis.VERTICAL, StackView.Distribution.FILL))
|
2019-06-26 01:52:17 +00:00
|
|
|
lateinit var selectedButton: View
|
|
|
|
val buttons = mutableListOf<Button>()
|
|
|
|
val last = allValues.count() - 1
|
|
|
|
for ((index, value) in allValues.withIndex()) {
|
|
|
|
val contentView = createView(value)
|
|
|
|
val button = stack.addArrangedSubview(Button(contentView, padding).apply {
|
|
|
|
background = null
|
2019-06-26 23:32:06 +00:00
|
|
|
hoveredBackground = DropdownItemBackgroundView(index == 0, index == last, NinePatchTexture.BUTTON_HOVERED_BG)
|
|
|
|
disabledBackground = DropdownItemBackgroundView(index == 0, index == last, NinePatchTexture.BUTTON_DISABLED_BG)
|
2019-06-26 01:52:17 +00:00
|
|
|
disabled = value == this@DropdownButton.value
|
|
|
|
handler = {
|
|
|
|
dropdownWindow.removeFromScreen()
|
|
|
|
valueSelected(value)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
if (value == this@DropdownButton.value) {
|
|
|
|
selectedButton = button
|
|
|
|
}
|
|
|
|
buttons.add(button)
|
|
|
|
dropdownWindow.solver.dsl {
|
2019-06-27 22:53:11 +00:00
|
|
|
if (button.content.intrinsicContentSize != null) {
|
|
|
|
button.widthAnchor greaterThanOrEqualTo button.content.intrinsicContentSize!!.width + 2 * button.padding
|
|
|
|
}
|
2019-06-26 01:52:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
dropdownWindow.solver.dsl {
|
|
|
|
// constrain to the DropdownButton anchor's value constant, because we're crossing windows and
|
|
|
|
// therefore solvers, which isn't allowed
|
|
|
|
stack.leftAnchor equalTo this@DropdownButton.rightAnchor.value
|
|
|
|
selectedButton.centerYAnchor equalTo this@DropdownButton.centerYAnchor.value
|
|
|
|
|
|
|
|
dropdownBackground.leftAnchor equalTo stack.leftAnchor
|
|
|
|
dropdownBackground.rightAnchor equalTo stack.rightAnchor
|
|
|
|
dropdownBackground.topAnchor equalTo stack.topAnchor
|
|
|
|
dropdownBackground.bottomAnchor equalTo stack.bottomAnchor
|
|
|
|
}
|
|
|
|
dropdownWindow.layout()
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun valueSelected(value: Value) {
|
|
|
|
this.value = value
|
|
|
|
handler?.invoke(this)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
private class DropdownItemBackgroundView(
|
|
|
|
private val first: Boolean,
|
|
|
|
private val last: Boolean,
|
|
|
|
ninePatch: NinePatchTexture
|
|
|
|
): NinePatchView(ninePatch) {
|
|
|
|
|
|
|
|
// Corners
|
|
|
|
private val topLeftDelegate = ResettableLazyProperty {
|
|
|
|
super.topLeft
|
|
|
|
Rect(0.0, 0.0, ninePatch.cornerWidth.toDouble(), if (first) ninePatch.cornerHeight.toDouble() else 0.0)
|
|
|
|
}
|
|
|
|
override val topLeft by topLeftDelegate
|
|
|
|
|
|
|
|
private val topRightDelegate = ResettableLazyProperty {
|
|
|
|
Rect(bounds.width - ninePatch.cornerWidth, 0.0, topLeft.width, topLeft.height)
|
|
|
|
}
|
|
|
|
override val topRight by topRightDelegate
|
|
|
|
|
|
|
|
private val bottomLeftDelegate = ResettableLazyProperty {
|
|
|
|
Rect(topLeft.left, bounds.height - ninePatch.cornerHeight, topLeft.width, if (last) ninePatch.cornerHeight.toDouble() else 0.0)
|
|
|
|
}
|
|
|
|
override val bottomLeft by bottomLeftDelegate
|
|
|
|
|
|
|
|
private val bottomRightDelegate = ResettableLazyProperty {
|
|
|
|
Rect(topRight.left, bottomLeft.top, topLeft.width, bottomLeft.height)
|
|
|
|
}
|
|
|
|
override val bottomRight by bottomRightDelegate
|
|
|
|
|
|
|
|
// Edges
|
|
|
|
private val topMiddleDelegate = ResettableLazyProperty {
|
|
|
|
Rect(ninePatch.cornerWidth.toDouble(), topLeft.top, bounds.width - 2 * ninePatch.cornerWidth, topLeft.height)
|
|
|
|
}
|
|
|
|
override val topMiddle by topMiddleDelegate
|
|
|
|
|
|
|
|
private val bottomMiddleDelegate = ResettableLazyProperty {
|
|
|
|
Rect(topMiddle.left, bottomLeft.top, topMiddle.width, bottomLeft.height)
|
|
|
|
}
|
|
|
|
override val bottomMiddle by bottomMiddleDelegate
|
|
|
|
|
|
|
|
private val leftMiddleDelegate = ResettableLazyProperty {
|
|
|
|
Rect(topLeft.left, topLeft.bottom, topLeft.width, bounds.height - (if (first && last) 2 else if (first || last) 1 else 0) * ninePatch.cornerHeight)
|
|
|
|
}
|
|
|
|
override val leftMiddle by leftMiddleDelegate
|
|
|
|
|
|
|
|
private val rightMiddleDelegate = ResettableLazyProperty {
|
|
|
|
Rect(topRight.left, topRight.bottom, topRight.width, leftMiddle.height)
|
|
|
|
}
|
|
|
|
override val rightMiddle by rightMiddleDelegate
|
|
|
|
|
|
|
|
// Center
|
|
|
|
private val centerDelegate = ResettableLazyProperty {
|
|
|
|
Rect(topLeft.right, topMiddle.bottom, topMiddle.width, leftMiddle.height)
|
|
|
|
}
|
|
|
|
override val center by centerDelegate
|
|
|
|
|
|
|
|
private val delegates = listOf(topLeftDelegate, topRightDelegate, bottomLeftDelegate, bottomRightDelegate, topMiddleDelegate, bottomMiddleDelegate, leftMiddleDelegate, rightMiddleDelegate, centerDelegate)
|
|
|
|
|
|
|
|
override fun didLayout() {
|
|
|
|
super.didLayout()
|
|
|
|
|
|
|
|
delegates.forEach(ResettableLazyProperty<Rect>::reset)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|