package net.shadowfacts.cacao.view.button import net.minecraft.util.Identifier import net.shadowfacts.cacao.geometry.Point import net.shadowfacts.cacao.util.MouseButton import net.shadowfacts.cacao.util.NinePatchTexture import net.shadowfacts.cacao.util.RenderHelper import net.shadowfacts.cacao.util.Texture import net.shadowfacts.cacao.view.NinePatchView import net.shadowfacts.cacao.view.View import net.shadowfacts.kiwidsl.dsl /** * A 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() { companion object { val DEFAULT_BG = NinePatchTexture(Texture(Identifier("textures/gui/widgets.png"), 0, 66), 3, 3, 194, 14) val HOVERED_BG = NinePatchTexture(Texture(Identifier("textures/gui/widgets.png"), 0, 86), 3, 3, 194, 14) val DISABLED_BG = NinePatchTexture(Texture(Identifier("textures/gui/widgets.png"), 0, 46), 3, 3, 194, 14) } /** * 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(DEFAULT_BG) /** * 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(HOVERED_BG) /** * The background to draw when the button is [disabled]. * If `null`, the normal [background] will be used. * @see background */ var disabledBackground: View? = NinePatchView(DISABLED_BG) override fun wasAdded() { solver.dsl { addSubview(content) content.leftAnchor equalTo (leftAnchor + padding) content.rightAnchor equalTo (rightAnchor - padding) content.topAnchor equalTo (topAnchor + padding) content.bottomAnchor equalTo (bottomAnchor - padding) listOfNotNull(background, hoveredBackground, disabledBackground).forEach { addSubview(it) it.leftAnchor equalTo leftAnchor it.rightAnchor equalTo rightAnchor it.topAnchor equalTo topAnchor it.bottomAnchor equalTo bottomAnchor } } super.wasAdded() } override fun draw(mouse: Point, delta: Float) { RenderHelper.pushMatrix() RenderHelper.translate(frame.left, frame.top) RenderHelper.fill(bounds, backgroundColor) var currentBackground: View? = background if (mouse in bounds) { currentBackground = hoveredBackground ?: currentBackground } if (disabled) { currentBackground = disabledBackground ?: currentBackground } // don't need to convert mouse to background coordinate system // the edges are all pinned, so the coordinate space is the same currentBackground?.draw(mouse, delta) val mouseInContent = convert(mouse, to = content) content.draw(mouseInContent, delta) // don't draw subviews, otherwise all background views + content will get drawn RenderHelper.popMatrix() } 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