Start adding ViewControllers

This commit is contained in:
Shadowfacts 2019-06-28 11:12:46 -04:00
parent 2044daec40
commit e4fc677dc1
Signed by: shadowfacts
GPG Key ID: 94A5AB95422746E5
5 changed files with 300 additions and 206 deletions

View File

@ -1,17 +1,9 @@
package net.shadowfacts.asmr
import net.minecraft.util.Identifier
import net.shadowfacts.asmr.util.RedstoneMode
import net.shadowfacts.kiwidsl.dsl
import net.shadowfacts.cacao.CacaoScreen
import net.shadowfacts.cacao.Window
import net.shadowfacts.cacao.geometry.Axis
import net.shadowfacts.cacao.geometry.Size
import net.shadowfacts.cacao.util.Color
import net.shadowfacts.cacao.util.texture.NinePatchTexture
import net.shadowfacts.cacao.util.texture.Texture
import net.shadowfacts.cacao.view.*
import net.shadowfacts.cacao.view.button.DropdownButton
import net.shadowfacts.cacao.viewcontroller.ViewController
/**
* @author shadowfacts
@ -19,103 +11,125 @@ import net.shadowfacts.cacao.view.button.DropdownButton
class TestCacaoScreen: CacaoScreen() {
init {
addWindow(Window().apply {
val stack = addView(StackView(Axis.VERTICAL, StackView.Distribution.CENTER, spacing = 4.0).apply {
backgroundColor = Color.WHITE
})
val red = stack.addArrangedSubview(TextureView(Texture(Identifier("textures/block/birch_log_top.png"), 0, 0, 16, 16)).apply {
intrinsicContentSize = Size(50.0, 50.0)
})
val green = stack.addArrangedSubview(NinePatchView(NinePatchTexture.PANEL_BG).apply {
intrinsicContentSize = Size(75.0, 100.0)
})
val blue = stack.addArrangedSubview(View().apply {
intrinsicContentSize = Size(50.0, 50.0)
backgroundColor = Color(0x0000ff)
})
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 = {
println("dropdown value: ${it.value}")
val dialog = DialogView(
"New redstone mode",
it.value.name,
arrayOf(DialogView.DefaultButtonType.OK),
Texture(Identifier("textures/block/birch_log_top.png"), 0, 0, 16, 16),
buttonCallback = { _, window ->
window.removeFromScreen()
}
)
val dialogWindow = Window()
dialogWindow.addView(dialog)
this@TestCacaoScreen.addWindow(dialogWindow)
}
})
val label = addView(Label("test ".repeat(100), maxLines = 5))
solver.dsl {
stack.topAnchor equalTo 0
stack.centerXAnchor equalTo this@apply.centerXAnchor
stack.widthAnchor equalTo 100
purple.centerXAnchor equalTo blue.centerXAnchor
purple.centerYAnchor equalTo blue.centerYAnchor
// purple.widthAnchor equalTo 50
label.topAnchor equalTo 0
label.leftAnchor equalTo 0
label.widthAnchor equalTo 100
val viewController = object: ViewController() {
override fun loadView() {
this.view = View()
}
// val red = addView(View().apply {
// backgroundColor = Color(0xff0000)
// })
// val green = addView(View().apply {
// backgroundColor = Color(0x00ff00)
// })
// val blue = addView(View().apply {
// backgroundColor = Color(0x0000ff)
// })
// val purple = green.addSubview(View().apply {
// backgroundColor = Color(0x800080)
// })
// purple.intrinsicContentSize = Size(width = 150.0, height = 150.0)
// val label = purple.addSubview(Label("Hello, world!").apply {
// textColor = Color.WHITE
// })
//
// solver.dsl {
// red.leftAnchor equalTo 0
// red.widthAnchor equalTo 200
// red.topAnchor equalTo 0
// red.heightAnchor equalTo 100
//
// green.leftAnchor equalTo (red.leftAnchor + red.widthAnchor + 20)
// green.widthAnchor equalTo red.widthAnchor
// green.topAnchor equalTo 0
// green.heightAnchor equalTo (red.heightAnchor + 100)
//
// blue.leftAnchor equalTo green.leftAnchor
// blue.widthAnchor equalTo green.widthAnchor
// blue.topAnchor equalTo (green.topAnchor + green.heightAnchor)
// blue.heightAnchor equalTo 50
//
//// purple.widthAnchor equalTo 100
//// purple.heightAnchor equalTo 100
// purple.centerXAnchor equalTo green.centerXAnchor
// purple.centerYAnchor equalTo green.centerYAnchor
//
// label.centerXAnchor equalTo purple.centerXAnchor
// label.centerYAnchor equalTo purple.centerYAnchor
// }
//
// layout()
})
override fun viewDidLoad() {
val labelContainer = view.addSubview(View())
createConstraints {
labelContainer.centerXAnchor equalTo view.centerXAnchor
labelContainer.centerYAnchor equalTo view.centerYAnchor
}
embedChild(object: ViewController() {
override fun loadView() {
this.view = Label("test child")
}
}, container = labelContainer)
}
}
addWindow(Window(viewController))
}
// init {
// addWindow(Window().apply {
// val stack = addView(StackView(Axis.VERTICAL, StackView.Distribution.CENTER, spacing = 4.0).apply {
// backgroundColor = Color.WHITE
// })
// val red = stack.addArrangedSubview(TextureView(Texture(Identifier("textures/block/birch_log_top.png"), 0, 0, 16, 16)).apply {
// intrinsicContentSize = Size(50.0, 50.0)
// })
// val green = stack.addArrangedSubview(NinePatchView(NinePatchTexture.PANEL_BG).apply {
// intrinsicContentSize = Size(75.0, 100.0)
// })
// val blue = stack.addArrangedSubview(View().apply {
// intrinsicContentSize = Size(50.0, 50.0)
// backgroundColor = Color(0x0000ff)
// })
// 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 = {
// println("dropdown value: ${it.value}")
// val dialog = DialogView(
// "New redstone mode",
// it.value.name,
// arrayOf(DialogView.DefaultButtonType.OK),
// Texture(Identifier("textures/block/birch_log_top.png"), 0, 0, 16, 16),
// buttonCallback = { _, window ->
// window.removeFromScreen()
// }
// )
// val dialogWindow = Window()
// dialogWindow.addView(dialog)
// this@TestCacaoScreen.addWindow(dialogWindow)
// }
// })
//
// val label = addView(Label("test ".repeat(100), maxLines = 5))
//
// solver.dsl {
// stack.topAnchor equalTo 0
// stack.centerXAnchor equalTo this@apply.centerXAnchor
// stack.widthAnchor equalTo 100
// purple.centerXAnchor equalTo blue.centerXAnchor
// purple.centerYAnchor equalTo blue.centerYAnchor
//// purple.widthAnchor equalTo 50
//
// label.topAnchor equalTo 0
// label.leftAnchor equalTo 0
// label.widthAnchor equalTo 100
// }
//
//
//// val red = addView(View().apply {
//// backgroundColor = Color(0xff0000)
//// })
//// val green = addView(View().apply {
//// backgroundColor = Color(0x00ff00)
//// })
//// val blue = addView(View().apply {
//// backgroundColor = Color(0x0000ff)
//// })
//// val purple = green.addSubview(View().apply {
//// backgroundColor = Color(0x800080)
//// })
//// purple.intrinsicContentSize = Size(width = 150.0, height = 150.0)
//// val label = purple.addSubview(Label("Hello, world!").apply {
//// textColor = Color.WHITE
//// })
////
//// solver.dsl {
//// red.leftAnchor equalTo 0
//// red.widthAnchor equalTo 200
//// red.topAnchor equalTo 0
//// red.heightAnchor equalTo 100
////
//// green.leftAnchor equalTo (red.leftAnchor + red.widthAnchor + 20)
//// green.widthAnchor equalTo red.widthAnchor
//// green.topAnchor equalTo 0
//// green.heightAnchor equalTo (red.heightAnchor + 100)
////
//// blue.leftAnchor equalTo green.leftAnchor
//// blue.widthAnchor equalTo green.widthAnchor
//// blue.topAnchor equalTo (green.topAnchor + green.heightAnchor)
//// blue.heightAnchor equalTo 50
////
////// purple.widthAnchor equalTo 100
////// purple.heightAnchor equalTo 100
//// purple.centerXAnchor equalTo green.centerXAnchor
//// purple.centerYAnchor equalTo green.centerYAnchor
////
//// label.centerXAnchor equalTo purple.centerXAnchor
//// label.centerYAnchor equalTo purple.centerYAnchor
//// }
////
//// layout()
// })
// }
}

View File

@ -3,6 +3,7 @@ package net.shadowfacts.cacao
import net.shadowfacts.cacao.geometry.Point
import net.shadowfacts.cacao.util.MouseButton
import net.shadowfacts.cacao.view.View
import net.shadowfacts.cacao.viewcontroller.ViewController
import net.shadowfacts.kiwidsl.dsl
import no.birkett.kiwi.Constraint
import no.birkett.kiwi.Solver
@ -17,7 +18,7 @@ import java.util.*
*
* @author shadowfacts
*/
class Window {
class Window(val viewController: ViewController) {
/**
* The screen that this window belongs to.
@ -68,18 +69,25 @@ class Window {
private var widthConstraint: Constraint? = null
private var heightConstraint: Constraint? = null
// _views is the internal, mutable object, since we only want it to be mutated by the add/removeView methods
private val _views = LinkedList<View>()
/**
* The list of top-level views in this window.
* This list should never be modified directly, only by calling the [addView]/[removeView] methods.
*/
val views: List<View> = _views
private var viewsSortedByZIndex: List<View> = listOf()
init {
createInternalConstraints()
viewController.window = this
viewController.loadView()
viewController.view.window = this
viewController.view.solver = solver
viewController.view.wasAdded()
viewController.createConstraints {
viewController.view.leftAnchor equalTo leftAnchor
viewController.view.rightAnchor equalTo rightAnchor
viewController.view.topAnchor equalTo topAnchor
viewController.view.bottomAnchor equalTo bottomAnchor
}
viewController.viewDidLoad()
layout()
}
/**
@ -116,47 +124,9 @@ class Window {
* Convenience method that removes this window from its [screen].
*/
fun removeFromScreen() {
viewController.viewWillDisappear()
screen.removeWindow(this)
}
/**
* Adds the given view as a top-level view in this window.
*
* @param view The view to add.
* @return The same view, as a convenience.
*/
fun <T: View> addView(view: T): T {
_views.add(view)
viewsSortedByZIndex = views.sortedBy(View::zIndex)
view.window = this
view.solver = solver
view.wasAdded()
return view
}
/**
* Finds all views in this window at the given point.
*
* @param point The point to find views at, in the coordinate system of the window.
* @return All views that contain the point.
*/
fun viewsAtPoint(point: Point): List<View> {
return views.filter { point in it.frame }
}
/**
* Attempts to find a top level view of this window that contains the given point.
* If there are multiple overlapping views, which one this method returns is undefined.
* [viewsAtPoint] may be used, and the resulting List sorted by [View.zIndex].
*
* @param point The point to find views at, in the coordinate system of the window.
* @return the Veiw, if any, that contain the given point.
*/
fun viewAtPoint(point: Point): View? {
return views.firstOrNull { point in it.frame }
viewController.viewDidDisappear()
}
/**
@ -165,7 +135,9 @@ class Window {
*/
fun layout() {
solver.updateVariables()
views.forEach(View::didLayout)
viewController.viewWillLayoutSubviews()
viewController.view.didLayout()
viewController.viewDidLayoutSubviews()
}
/**
@ -176,10 +148,8 @@ class Window {
* @param delta The time elapsed since the last frame.
*/
fun draw(mouse: Point, delta: Float) {
viewsSortedByZIndex.forEach {
val mouseInView = Point(mouse.x - it.frame.left, mouse.y - it.frame.top)
it.draw(mouseInView, delta)
}
val mouseInView = Point(mouse.x - viewController.view.frame.left, mouse.y - viewController.view.frame.top)
viewController.view.draw(mouseInView, delta)
}
/**
@ -191,10 +161,9 @@ class Window {
* @return Whether the mouse click was handled by a view.
*/
fun mouseClicked(point: Point, mouseButton: MouseButton): Boolean {
val view = viewsAtPoint(point).maxBy(View::zIndex)
if (view != null) {
val pointInView = Point(point.x - view.frame.left, point.y - view.frame.top)
return view.mouseClicked(pointInView, mouseButton)
if (point in viewController.view.frame) {
val mouseInView = Point(point.x - viewController.view.frame.left, point.y - viewController.view.frame.top)
return viewController.view.mouseClicked(mouseInView, mouseButton)
} else {
// remove the window from the screen when the mouse clicks outside the window and this is not the primary window
if (screen.windows.size > 1) {

View File

@ -0,0 +1,20 @@
package net.shadowfacts.cacao.util.properties
import kotlin.reflect.KProperty
/**
* @author shadowfacts
*/
class ObservableLazyProperty<Value>(val create: () -> Value, val onCreate: () -> Unit) {
var storage: Value? = null
operator fun getValue(thisRef: Any, property: KProperty<*>): Value {
if (storage == null) {
storage = create()
onCreate()
}
return storage!!
}
}

View File

@ -88,48 +88,48 @@ class DropdownButton<Value, ContentView: View>(
}
private fun showDropdown() {
val dropdownWindow = window.screen.addWindow(Window())
val dropdownBackground = dropdownWindow.addView(NinePatchView(NinePatchTexture.BUTTON_BG).apply {
zIndex = -1.0
})
val stack = dropdownWindow.addView(StackView(Axis.VERTICAL, StackView.Distribution.FILL))
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, NinePatchTexture.BUTTON_HOVERED_BG)
disabledBackground = DropdownItemBackgroundView(index == 0, index == last, NinePatchTexture.BUTTON_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 {
if (button.content.intrinsicContentSize != null) {
button.widthAnchor greaterThanOrEqualTo button.content.intrinsicContentSize!!.width + 2 * button.padding
}
}
}
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()
// val dropdownWindow = window.screen.addWindow(Window())
// val dropdownBackground = dropdownWindow.addView(NinePatchView(NinePatchTexture.BUTTON_BG).apply {
// zIndex = -1.0
// })
// val stack = dropdownWindow.addView(StackView(Axis.VERTICAL, StackView.Distribution.FILL))
// 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, NinePatchTexture.BUTTON_HOVERED_BG)
// disabledBackground = DropdownItemBackgroundView(index == 0, index == last, NinePatchTexture.BUTTON_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 {
// if (button.content.intrinsicContentSize != null) {
// button.widthAnchor greaterThanOrEqualTo button.content.intrinsicContentSize!!.width + 2 * button.padding
// }
// }
// }
// 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) {

View File

@ -0,0 +1,91 @@
package net.shadowfacts.cacao.viewcontroller
import net.shadowfacts.cacao.Window
import net.shadowfacts.cacao.util.properties.ObservableLazyProperty
import net.shadowfacts.cacao.view.View
import net.shadowfacts.kiwidsl.dsl
import java.util.*
/**
* @author shadowfacts
*/
abstract class ViewController {
lateinit var window: Window
val createConstraints
get() = window.solver::dsl
lateinit var view: View
protected set
var parent: ViewController? = null
set(value) {
willMoveTo(value)
field = value
didMoveTo(value)
}
private var _children = LinkedList<ViewController>()
val children: List<ViewController> = _children
abstract fun loadView()
open fun viewDidLoad() {}
open fun viewWillLayoutSubviews() {
children.forEach(ViewController::viewWillLayoutSubviews)
}
open fun viewDidLayoutSubviews() {
children.forEach(ViewController::viewDidLayoutSubviews)
}
open fun viewWillAppear() {
children.forEach(ViewController::viewWillAppear)
}
open fun viewDidAppear() {
children.forEach(ViewController::viewDidAppear)
}
open fun viewWillDisappear() {
children.forEach(ViewController::viewWillDisappear)
}
open fun viewDidDisappear() {
children.forEach(ViewController::viewDidDisappear)
}
open fun willMoveTo(parent: ViewController?) {}
open fun didMoveTo(parent: ViewController?) {}
fun embedChild(viewController: ViewController, container: View = this.view) {
viewController.parent = this
viewController.window = window
_children.add(viewController)
viewController.loadView()
container.addSubview(viewController.view)
createConstraints {
viewController.view.leftAnchor equalTo container.leftAnchor
viewController.view.rightAnchor equalTo container.rightAnchor
viewController.view.topAnchor equalTo container.topAnchor
viewController.view.bottomAnchor equalTo container.bottomAnchor
}
viewController.viewDidLoad()
}
fun removeChild(viewController: ViewController) {
viewController.parent = null
_children.remove(viewController)
}
fun removeFromParent() {
parent?.removeChild(this)
// todo: remove view from superview
}
}