Add DropdownButton

This commit is contained in:
Shadowfacts 2019-06-25 21:52:17 -04:00
parent b2499ad247
commit 4d1fb68c89
Signed by: shadowfacts
GPG Key ID: 94A5AB95422746E5
11 changed files with 317 additions and 46 deletions

View File

@ -11,9 +11,7 @@ import net.shadowfacts.cacao.util.Color
import net.shadowfacts.cacao.util.NinePatchTexture import net.shadowfacts.cacao.util.NinePatchTexture
import net.shadowfacts.cacao.util.Texture import net.shadowfacts.cacao.util.Texture
import net.shadowfacts.cacao.view.* import net.shadowfacts.cacao.view.*
import net.shadowfacts.cacao.view.button.Button import net.shadowfacts.cacao.view.button.DropdownButton
import net.shadowfacts.cacao.view.button.EnumButton
import net.shadowfacts.cacao.view.button.ToggleButton
/** /**
* @author shadowfacts * @author shadowfacts
@ -36,9 +34,14 @@ class TestCacaoScreen: CacaoScreen() {
intrinsicContentSize = Size(50.0, 50.0) intrinsicContentSize = Size(50.0, 50.0)
backgroundColor = Color(0x0000ff) backgroundColor = Color(0x0000ff)
}) })
val purple = blue.addSubview(ToggleButton(false).apply { val purple = blue.addSubview(DropdownButton(
initialValue = RedstoneMode.HIGH,
allValues = RedstoneMode.values().asIterable(),
createView = { Label(it.name) },
updateView = { newValue, label -> label.text = newValue.name }
).apply {
handler = { handler = {
println("enum button clicked, new value: ${it.state}") println("dropdown value: ${it.value}")
} }
}) })

View File

@ -31,13 +31,26 @@ open class CacaoScreen: Screen(TextComponent("CacaoScreen")) {
* *
* @param window The Window to add to this screen. * @param window The Window to add to this screen.
* @param index The index to insert the window into the window list at. * @param index The index to insert the window into the window list at.
* @return The window that was added, as a convenience.
*/ */
fun addWindow(window: Window, index: Int = _windows.size) { fun <T: Window> addWindow(window: T, index: Int = _windows.size): T {
_windows.add(index, window) _windows.add(index, window)
window.screen = this
return window
}
/**
* Removes the given window from this screen's window list.
*/
fun removeWindow(window: Window) {
_windows.remove(window)
} }
override fun render(mouseX: Int, mouseY: Int, delta: Float) { override fun render(mouseX: Int, mouseY: Int, delta: Float) {
renderBackground() if (minecraft != null) {
// workaround this.minecraft sometimes being null causing a crash
renderBackground()
}
val mouse = Point(mouseX, mouseY) val mouse = Point(mouseX, mouseY)
windows.forEach { windows.forEach {
@ -47,11 +60,17 @@ open class CacaoScreen: Screen(TextComponent("CacaoScreen")) {
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean { override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
val window = windows.lastOrNull() val window = windows.lastOrNull()
if (window?.mouseClicked(Point(mouseX, mouseY), MouseButton.fromMC(button)) == true) { val result = window?.mouseClicked(Point(mouseX, mouseY), MouseButton.fromMC(button))
RenderHelper.playSound(SoundEvents.UI_BUTTON_CLICK) when (result) {
true ->
RenderHelper.playSound(SoundEvents.UI_BUTTON_CLICK)
false ->
if (windows.size > 1) {
removeWindow(windows.last())
}
} }
return false return result == true
} }
} }

View File

@ -16,6 +16,12 @@ import java.util.*
*/ */
class Window { class Window {
/**
* The screen that this window belongs to.
* Not initialized until this window is added to a screen, using it before that point will throw a runtime exception.
*/
lateinit var screen: CacaoScreen
var solver = Solver() var solver = Solver()
// _views is the internal, mutable object, since we only want it to be mutated by the add/removeView methods // _views is the internal, mutable object, since we only want it to be mutated by the add/removeView methods
@ -28,6 +34,13 @@ class Window {
private var viewsSortedByZIndex: List<View> = listOf() private var viewsSortedByZIndex: List<View> = listOf()
/**
* Convenience method that removes this window from its [screen].
*/
fun removeFromScreen() {
screen.removeWindow(this)
}
/** /**
* Adds the given view as a top-level view in this window. * Adds the given view as a top-level view in this window.
* *
@ -100,7 +113,7 @@ class Window {
* @return Whether the mouse click was handled by a view. * @return Whether the mouse click was handled by a view.
*/ */
fun mouseClicked(point: Point, mouseButton: MouseButton): Boolean { fun mouseClicked(point: Point, mouseButton: MouseButton): Boolean {
val view = viewsAtPoint(point).minBy(View::zIndex) val view = viewsAtPoint(point).maxBy(View::zIndex)
if (view != null) { if (view != null) {
val pointInView = Point(point.x - view.frame.left, point.y - view.frame.top) val pointInView = Point(point.x - view.frame.left, point.y - view.frame.top)
return view.mouseClicked(pointInView, mouseButton) return view.mouseClicked(pointInView, mouseButton)

View File

@ -3,9 +3,12 @@ package net.shadowfacts.cacao.util
import com.mojang.blaze3d.platform.GlStateManager import com.mojang.blaze3d.platform.GlStateManager
import net.minecraft.client.MinecraftClient import net.minecraft.client.MinecraftClient
import net.minecraft.client.gui.DrawableHelper import net.minecraft.client.gui.DrawableHelper
import net.minecraft.client.render.Tessellator
import net.minecraft.client.render.VertexFormats
import net.minecraft.client.sound.PositionedSoundInstance import net.minecraft.client.sound.PositionedSoundInstance
import net.minecraft.sound.SoundEvent import net.minecraft.sound.SoundEvent
import net.shadowfacts.cacao.geometry.Rect import net.shadowfacts.cacao.geometry.Rect
import org.lwjgl.opengl.GL11
/** /**
* Helper methods for rendering using Minecraft's utilities from Cacao views. * Helper methods for rendering using Minecraft's utilities from Cacao views.
@ -46,7 +49,23 @@ object RenderHelper {
*/ */
fun draw(x: Double, y: Double, u: Int, v: Int, width: Double, height: Double, textureWidth: Int, textureHeight: Int) { fun draw(x: Double, y: Double, u: Int, v: Int, width: Double, height: Double, textureWidth: Int, textureHeight: Int) {
if (disabled) return if (disabled) return
DrawableHelper.blit(x.toInt(), y.toInt(), u.toFloat(), v.toFloat(), width.toInt(), height.toInt(), textureWidth, textureHeight) val uStart = u.toDouble() / textureWidth
val uEnd = (u + width) / textureWidth
val vStart = v.toDouble() / textureHeight
val vEnd = (v + height) / textureHeight
innerBlit(x, x + width, y, y + height, 0.0, uStart, uEnd, vStart, vEnd)
}
// Copied from net.minecraft.client.gui.DrawableHelper
private fun innerBlit(xStart: Double, xEnd: Double, yStart: Double, yEnd: Double, z: Double, uStart: Double, uEnd: Double, vStart: Double, vEnd: Double) {
val tessellator = Tessellator.getInstance()
val buffer = tessellator.bufferBuilder
buffer.begin(GL11.GL_QUADS, VertexFormats.POSITION_UV)
buffer.vertex(xStart, yEnd, z).texture(uStart, vEnd).next()
buffer.vertex(xEnd, yEnd, z).texture(uEnd, vEnd).next()
buffer.vertex(xEnd, yStart, z).texture(uEnd, vStart).next()
buffer.vertex(xStart, yStart, z).texture(uStart, vStart).next()
tessellator.draw()
} }
/** /**

View File

@ -1,4 +1,4 @@
package net.shadowfacts.cacao.util package net.shadowfacts.cacao.util.properties
import kotlin.reflect.KProperty import kotlin.reflect.KProperty

View File

@ -1,4 +1,4 @@
package net.shadowfacts.cacao.util package net.shadowfacts.cacao.util.properties
import kotlin.reflect.KProperty import kotlin.reflect.KProperty

View File

@ -4,68 +4,70 @@ import net.shadowfacts.cacao.geometry.Point
import net.shadowfacts.cacao.geometry.Rect import net.shadowfacts.cacao.geometry.Rect
import net.shadowfacts.cacao.util.NinePatchTexture import net.shadowfacts.cacao.util.NinePatchTexture
import net.shadowfacts.cacao.util.RenderHelper import net.shadowfacts.cacao.util.RenderHelper
import net.shadowfacts.cacao.util.ResettableLazyProperty import net.shadowfacts.cacao.util.properties.ResettableLazyProperty
/** /**
* A helper class for drawing a [NinePatchTexture] in a view. * A helper class for drawing a [NinePatchTexture] in a view.
* `NinePatchView` will draw the given nine patch texture filling its bounds. * `NinePatchView` will draw the given nine patch texture filling its bounds.
* *
* This class and the region properties are left open for internal framework use, overriding them is not recommended.
*
* @author shadowfacts * @author shadowfacts
* @param ninePatch The nine patch texture that this view will draw. * @param ninePatch The nine patch texture that this view will draw.
*/ */
class NinePatchView(val ninePatch: NinePatchTexture): View() { open class NinePatchView(val ninePatch: NinePatchTexture): View() {
// Corners // Corners
protected val `$topLeft` = ResettableLazyProperty { private val topLeftDelegate = ResettableLazyProperty {
Rect(0.0, 0.0, ninePatch.cornerWidth.toDouble(), ninePatch.cornerHeight.toDouble()) Rect(0.0, 0.0, ninePatch.cornerWidth.toDouble(), ninePatch.cornerHeight.toDouble())
} }
protected val topLeft by `$topLeft` protected open val topLeft by topLeftDelegate
protected val `$topRight` = ResettableLazyProperty { private val topRightDelegate = ResettableLazyProperty {
Rect(bounds.width - ninePatch.cornerWidth, 0.0, ninePatch.cornerWidth.toDouble(), ninePatch.cornerHeight.toDouble()) Rect(bounds.width - ninePatch.cornerWidth, 0.0, ninePatch.cornerWidth.toDouble(), ninePatch.cornerHeight.toDouble())
} }
protected val topRight by `$topRight` protected open val topRight by topRightDelegate
protected val `$bottomLeft` = ResettableLazyProperty { private val bottomLeftDelegate = ResettableLazyProperty {
Rect(0.0, bounds.height - ninePatch.cornerHeight, ninePatch.cornerWidth.toDouble(), ninePatch.cornerHeight.toDouble()) Rect(0.0, bounds.height - ninePatch.cornerHeight, ninePatch.cornerWidth.toDouble(), ninePatch.cornerHeight.toDouble())
} }
protected val bottomLeft by `$bottomLeft` protected open val bottomLeft by bottomLeftDelegate
protected val `$bottomRight` = ResettableLazyProperty { private val bottomRightDelegate = ResettableLazyProperty {
Rect(bounds.width - ninePatch.cornerWidth, bounds.height - ninePatch.cornerHeight, ninePatch.cornerWidth.toDouble(), ninePatch.cornerHeight.toDouble()) Rect(bounds.width - ninePatch.cornerWidth, bounds.height - ninePatch.cornerHeight, ninePatch.cornerWidth.toDouble(), ninePatch.cornerHeight.toDouble())
} }
protected val bottomRight by `$bottomRight` protected open val bottomRight by bottomRightDelegate
// Edges // Edges
protected val `$topMiddle` = ResettableLazyProperty { private val topMiddleDelegate = ResettableLazyProperty {
Rect(ninePatch.cornerWidth.toDouble(), topLeft.top, bounds.width - 2 * ninePatch.cornerWidth, ninePatch.cornerHeight.toDouble()) Rect(ninePatch.cornerWidth.toDouble(), topLeft.top, bounds.width - 2 * ninePatch.cornerWidth, ninePatch.cornerHeight.toDouble())
} }
protected val topMiddle by `$topMiddle` protected open val topMiddle by topMiddleDelegate
protected val `$bottomMiddle` = ResettableLazyProperty { private val bottomMiddleDelegate = ResettableLazyProperty {
Rect(topMiddle.left, bottomLeft.top, topMiddle.width, topMiddle.height) Rect(topMiddle.left, bottomLeft.top, topMiddle.width, topMiddle.height)
} }
protected val bottomMiddle by `$bottomMiddle` protected open val bottomMiddle by bottomMiddleDelegate
protected val `$leftMiddle` = ResettableLazyProperty { private val leftMiddleDelegate = ResettableLazyProperty {
Rect(topLeft.left, ninePatch.cornerHeight.toDouble(), ninePatch.cornerWidth.toDouble(), bounds.height - 2 * ninePatch.cornerHeight) Rect(topLeft.left, ninePatch.cornerHeight.toDouble(), ninePatch.cornerWidth.toDouble(), bounds.height - 2 * ninePatch.cornerHeight)
} }
protected val leftMiddle by `$leftMiddle` protected open val leftMiddle by leftMiddleDelegate
protected val `$rightMiddle` = ResettableLazyProperty { private val rightMiddleDelegate = ResettableLazyProperty {
Rect(topRight.left, leftMiddle.top, leftMiddle.width, leftMiddle.height) Rect(topRight.left, leftMiddle.top, leftMiddle.width, leftMiddle.height)
} }
protected val rightMiddle by `$rightMiddle` protected open val rightMiddle by rightMiddleDelegate
// Center // Center
protected val `$center` = ResettableLazyProperty { private val centerDelegate = ResettableLazyProperty {
Rect(topLeft.right, topLeft.bottom, topMiddle.width, leftMiddle.height) Rect(topLeft.right, topLeft.bottom, topMiddle.width, leftMiddle.height)
} }
protected val center by `$center` protected open val center by centerDelegate
protected val delegates = listOf(`$topLeft`, `$topRight`, `$bottomLeft`, `$bottomRight`, `$topMiddle`, `$bottomMiddle`, `$leftMiddle`, `$rightMiddle`, `$center`) private val delegates = listOf(topLeftDelegate, topRightDelegate, bottomLeftDelegate, bottomRightDelegate, topMiddleDelegate, bottomMiddleDelegate, leftMiddleDelegate, rightMiddleDelegate, centerDelegate)
override fun didLayout() { override fun didLayout() {
super.didLayout() super.didLayout()
@ -89,8 +91,8 @@ class NinePatchView(val ninePatch: NinePatchTexture): View() {
private fun drawEdges() { private fun drawEdges() {
// Horizontal // Horizontal
for (i in 0 until (topMiddle.width.toInt() / ninePatch.centerWidth)) { for (i in 0 until (topMiddle.width.toInt() / ninePatch.centerWidth)) {
RenderHelper.draw(topMiddle.left + i * ninePatch.centerWidth, topMiddle.top, ninePatch.topMiddle.u, ninePatch.topMiddle.v, ninePatch.centerWidth.toDouble(), ninePatch.cornerHeight.toDouble(), ninePatch.texture.width, ninePatch.texture.height) RenderHelper.draw(topMiddle.left + i * ninePatch.centerWidth, topMiddle.top, ninePatch.topMiddle.u, ninePatch.topMiddle.v, ninePatch.centerWidth.toDouble(), topMiddle.height, ninePatch.texture.width, ninePatch.texture.height)
RenderHelper.draw(bottomMiddle.left + i * ninePatch.centerWidth, bottomMiddle.top, ninePatch.bottomMiddle.u, ninePatch.bottomMiddle.v, ninePatch.centerWidth.toDouble(), ninePatch.cornerHeight.toDouble(), ninePatch.texture.width, ninePatch.texture.height) RenderHelper.draw(bottomMiddle.left + i * ninePatch.centerWidth, bottomMiddle.top, ninePatch.bottomMiddle.u, ninePatch.bottomMiddle.v, ninePatch.centerWidth.toDouble(), bottomMiddle.height, ninePatch.texture.width, ninePatch.texture.height)
} }
val remWidth = topMiddle.width.toInt() % ninePatch.centerWidth val remWidth = topMiddle.width.toInt() % ninePatch.centerWidth
if (remWidth > 0) { if (remWidth > 0) {

View File

@ -2,8 +2,10 @@ package net.shadowfacts.cacao.view
import net.shadowfacts.kiwidsl.dsl import net.shadowfacts.kiwidsl.dsl
import net.shadowfacts.cacao.LayoutVariable import net.shadowfacts.cacao.LayoutVariable
import net.shadowfacts.cacao.Window
import net.shadowfacts.cacao.geometry.* import net.shadowfacts.cacao.geometry.*
import net.shadowfacts.cacao.util.* import net.shadowfacts.cacao.util.*
import net.shadowfacts.cacao.util.properties.ObservableLateInitProperty
import no.birkett.kiwi.Constraint import no.birkett.kiwi.Constraint
import no.birkett.kiwi.Solver import no.birkett.kiwi.Solver
import java.util.* import java.util.*
@ -16,6 +18,13 @@ import java.util.*
*/ */
open class View() { open class View() {
/**
* The window whose view hierarchy this view belongs to.
* Not initialized until the root view in this hierarchy has been added to a hierarchy,
* using it before that will throw a runtime exception.
*/
lateinit var window: Window
/** /**
* The constraint solver used by the [net.shadowfacts.cacao.Window] this view belongs to. * The constraint solver used by the [net.shadowfacts.cacao.Window] this view belongs to.
* Not initialized until [wasAdded] called, using it before that will throw a runtime exception. * Not initialized until [wasAdded] called, using it before that will throw a runtime exception.
@ -156,6 +165,7 @@ open class View() {
view.superview = this view.superview = this
view.solver = solver view.solver = solver
view.window = window
view.wasAdded() view.wasAdded()

View File

@ -1,6 +1,5 @@
package net.shadowfacts.cacao.view.button package net.shadowfacts.cacao.view.button
import net.minecraft.sound.SoundEvents
import net.minecraft.util.Identifier import net.minecraft.util.Identifier
import net.shadowfacts.cacao.geometry.Point import net.shadowfacts.cacao.geometry.Point
import net.shadowfacts.cacao.util.MouseButton import net.shadowfacts.cacao.util.MouseButton
@ -113,14 +112,11 @@ abstract class AbstractButton<Impl: AbstractButton<Impl>>(val content: View, val
override fun mouseClicked(point: Point, mouseButton: MouseButton): Boolean { override fun mouseClicked(point: Point, mouseButton: MouseButton): Boolean {
if (disabled) return false if (disabled) return false
val handler = handler // We can perform an unchecked cast here because we are certain that Impl will be the concrete implementation
if (handler != null) { // of AbstractButton.
// We can perform an unchecked cast here because we are certain that Impl will be the concrete implementation // For example, an implementing class may be defined as such: `class Button: AbstractButton<Button>`
// of AbstractButton. @Suppress("UNCHECKED_CAST")
// For example, an implementing class may be defined as such: `class Button: AbstractButton<Button>` handler?.invoke(this as Impl)
@Suppress("UNCHECKED_CAST")
handler(this as Impl)
}
return true return true
} }

View File

@ -0,0 +1,209 @@
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
import net.shadowfacts.cacao.util.NinePatchTexture
import net.shadowfacts.cacao.util.Texture
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())
val dropdownBackground = dropdownWindow.addView(NinePatchView(DEFAULT_BG).apply {
zIndex = -1.0
})
val stack = dropdownWindow.addView(StackView(Axis.VERTICAL, StackView.Distribution.LEADING))
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
hoveredBackground = DropdownItemBackgroundView(index == 0, index == last, HOVERED_BG)
disabledBackground = DropdownItemBackgroundView(index == 0, index == last, DISABLED_BG)
disabled = value == this@DropdownButton.value
handler = {
dropdownWindow.removeFromScreen()
valueSelected(value)
}
})
if (value == this@DropdownButton.value) {
selectedButton = button
}
buttons.add(button)
dropdownWindow.solver.dsl {
stack.widthAnchor greaterThanOrEqualTo button.widthAnchor
}
}
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()
dropdownWindow.solver.dsl {
buttons.forEach {
it.widthAnchor equalTo stack.frame.width
}
}
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)
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 427 B