package net.shadowfacts.cacao.view.button import net.minecraft.client.util.math.MatrixStack import net.minecraft.text.Text import net.shadowfacts.cacao.geometry.Point import net.shadowfacts.cacao.util.MouseButton import net.shadowfacts.cacao.util.texture.NinePatchTexture import net.shadowfacts.cacao.util.RenderHelper import net.shadowfacts.cacao.view.NinePatchView import net.shadowfacts.cacao.view.View import net.shadowfacts.kiwidsl.dsl import kotlin.math.floor /** * An abstract button class. Cannot be constructed directly, used for creating button implementations with their own * logic. Use [Button] for a generic no-frills button. * * @author shadowfacts * @param Impl The type of the concrete implementation of the button. * Used to allow the [handler] to receive the exact button type that was constructed. * @param content The [View] that provides the content of this button. * Will be added as a subview of the button and laid out using constraints. * @param padding The padding between the [content] and the edges of the button. */ abstract class AbstractButton>(val content: View, val padding: Double = 4.0): View() { /** * The function that handles when this button is clicked. * The parameter is the type of the concrete button implementation that was used. */ var handler: ((Impl) -> Unit)? = null /** * Whether the button is disabled. * Disabled buttons have a different background ([disabledBackground]) and do not receive click events. */ var disabled = false /** * The normal background view to draw behind the button content. It will be added as a subview during [wasAdded], * so all background view properties must be specified prior to the button being added to a view hierarchy. * * The background will fill the entire button (going beneath the content [padding]). * There are also [hoveredBackground] and [disabledBackground] for those states. * If a [backgroundColor] is specified, it will be drawn behind the background View and thus not visible * unless the background view is not fully opaque. */ var background: View? = NinePatchView(NinePatchTexture.BUTTON_BG) set(value) { field?.removeFromSuperview() field = value value?.also(::addBackground) } /** * The background to draw when the button is hovered over by the mouse. * If `null`, the normal [background] will be used. * @see background */ var hoveredBackground: View? = NinePatchView(NinePatchTexture.BUTTON_HOVERED_BG) set(value) { field?.removeFromSuperview() field = value value?.also(::addBackground) } /** * The background to draw when the button is [disabled]. * If `null`, the normal [background] will be used. * @see background */ var disabledBackground: View? = NinePatchView(NinePatchTexture.BUTTON_DISABLED_BG) set(value) { field?.removeFromSuperview() field = value value?.also(::addBackground) } /** * The tooltip text shown when this button is hovered. */ var tooltip: Text? = null override fun wasAdded() { solver.dsl { addSubview(content) content.centerXAnchor equalTo centerXAnchor content.centerYAnchor equalTo centerYAnchor content.leftAnchor.lessThanOrEqualTo((leftAnchor + padding), WEAK) content.rightAnchor.greaterThanOrEqualTo(rightAnchor - padding, WEAK) content.topAnchor.lessThanOrEqualTo(topAnchor + padding, WEAK) content.bottomAnchor.greaterThanOrEqualTo(bottomAnchor - padding, WEAK) } listOfNotNull(background, hoveredBackground, disabledBackground).forEach(::addBackground) super.wasAdded() } private fun addBackground(view: View) { if (superview != null && hasSolver) { addSubview(view) solver.dsl { view.leftAnchor equalTo leftAnchor view.rightAnchor equalTo rightAnchor view.topAnchor equalTo topAnchor view.bottomAnchor equalTo bottomAnchor } } } override fun draw(matrixStack: MatrixStack, mouse: Point, delta: Float) { matrixStack.push() matrixStack.translate(frame.left, frame.top, 0.0) RenderHelper.fill(matrixStack, bounds, backgroundColor) // don't need to convert mouse to background coordinate system // the edges are all pinned, so the coordinate space is the same getCurrentBackground(mouse)?.draw(matrixStack, mouse, delta) val mouseInContent = convert(mouse, to = content) content.draw(matrixStack, mouseInContent, delta) // don't draw subviews, otherwise all background views + content will get drawn matrixStack.pop() if (tooltip != null && mouse in bounds) { window!!.drawTooltip(listOf(tooltip!!)) } } override fun mouseClicked(point: Point, mouseButton: MouseButton): Boolean { if (disabled) return false // We can perform an unchecked cast here because we are certain that Impl will be the concrete implementation // of AbstractButton. // For example, an implementing class may be defined as such: `class Button: AbstractButton