diff --git a/.gitignore b/.gitignore index 7761783..c475639 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .DS_Store +logs/ # gradle diff --git a/src/main/kotlin/net/shadowfacts/asmr/TestCacaoScreen.kt b/src/main/kotlin/net/shadowfacts/asmr/TestCacaoScreen.kt index 35d84f7..6c5e6d1 100644 --- a/src/main/kotlin/net/shadowfacts/asmr/TestCacaoScreen.kt +++ b/src/main/kotlin/net/shadowfacts/asmr/TestCacaoScreen.kt @@ -5,12 +5,11 @@ import net.shadowfacts.cacao.CacaoScreen import net.shadowfacts.cacao.view.View import net.shadowfacts.cacao.Window import net.shadowfacts.cacao.geometry.Axis -import net.shadowfacts.cacao.geometry.Point import net.shadowfacts.cacao.geometry.Size import net.shadowfacts.cacao.util.Color -import net.shadowfacts.cacao.util.MouseButton -import net.shadowfacts.cacao.util.RenderHelper +import net.shadowfacts.cacao.view.Label import net.shadowfacts.cacao.view.StackView +import net.shadowfacts.cacao.view.button.Button /** * @author shadowfacts @@ -34,20 +33,12 @@ class TestCacaoScreen: CacaoScreen() { intrinsicContentSize = Size(50.0, 50.0) backgroundColor = Color(0x0000ff) }) - val purple = blue.addSubview(object: View() { - init { - intrinsicContentSize = Size(25.0, 25.0) - backgroundColor = Color(0x800080) + val purple = blue.addSubview(Button(Label("Hello, button!")).apply { + handler = { + println("$it clicked!") } - - override fun mouseClicked(point: Point, mouseButton: MouseButton) { - println("hello world") - } - - override fun drawContent(mouse: Point, delta: Float) { - if (mouse in bounds) { - RenderHelper.fill(bounds, Color.WHITE) - } + background = View().apply { + backgroundColor = Color(0xebfc00) } }) diff --git a/src/main/kotlin/net/shadowfacts/cacao/view/View.kt b/src/main/kotlin/net/shadowfacts/cacao/view/View.kt index 3b8a7ff..6cdcc5c 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/view/View.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/view/View.kt @@ -219,14 +219,15 @@ open class View() { /** * Called to draw this view. * This method should not be called directly, it is called by the parent view/window. - * This method may not be overridden, use [drawContent] to draw any custom content. + * This method generally should not be overridden, but it is left open for internal framework use. + * Use [drawContent] to draw any custom content. * * @param mouse The position of the mouse in the coordinate system of this view. * @param delta The time since the last frame. */ - fun draw(mouse: Point, delta: Float) { + open fun draw(mouse: Point, delta: Float) { RenderHelper.pushMatrix() - RenderHelper.translate(frame.left, frame.top, 0.0) + RenderHelper.translate(frame.left, frame.top) RenderHelper.fill(bounds, backgroundColor) diff --git a/src/main/kotlin/net/shadowfacts/cacao/view/button/AbstractButton.kt b/src/main/kotlin/net/shadowfacts/cacao/view/button/AbstractButton.kt new file mode 100644 index 0000000..0a0f102 --- /dev/null +++ b/src/main/kotlin/net/shadowfacts/cacao/view/button/AbstractButton.kt @@ -0,0 +1,115 @@ +package net.shadowfacts.cacao.view.button + +import net.shadowfacts.cacao.geometry.Point +import net.shadowfacts.cacao.util.MouseButton +import net.shadowfacts.cacao.util.RenderHelper +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() { + + /** + * 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? = null + /** + * 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? = null + /** + * The background to draw when the button is [disabled]. + * If `null`, the normal [background] will be used. + * @see background + */ + var disabledBackground: View? = null + + 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) { + if (disabled) return + + val handler = handler + if (handler != null) { + // 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