diff --git a/src/main/kotlin/net/shadowfacts/asmr/ASMR.kt b/src/main/kotlin/net/shadowfacts/asmr/ASMR.kt index ead2b15..0e37be4 100644 --- a/src/main/kotlin/net/shadowfacts/asmr/ASMR.kt +++ b/src/main/kotlin/net/shadowfacts/asmr/ASMR.kt @@ -16,7 +16,7 @@ object ASMR: ModInitializer { CommandRegistry.INSTANCE.register(false) { dispatcher -> val command = CommandManager.literal("uitest").executes { try { - MinecraftClient.getInstance().openScreen(TestScreen()) + MinecraftClient.getInstance().openScreen(TestCacaoScreen()) } catch (e: Throwable) { e.printStackTrace() } diff --git a/src/main/kotlin/net/shadowfacts/asmr/TestScreen.kt b/src/main/kotlin/net/shadowfacts/asmr/TestCacaoScreen.kt similarity index 95% rename from src/main/kotlin/net/shadowfacts/asmr/TestScreen.kt rename to src/main/kotlin/net/shadowfacts/asmr/TestCacaoScreen.kt index 86b3092..cedde1c 100644 --- a/src/main/kotlin/net/shadowfacts/asmr/TestScreen.kt +++ b/src/main/kotlin/net/shadowfacts/asmr/TestCacaoScreen.kt @@ -1,7 +1,7 @@ package net.shadowfacts.asmr import net.shadowfacts.kiwidsl.dsl -import net.shadowfacts.cacao.Screen +import net.shadowfacts.cacao.CacaoScreen import net.shadowfacts.cacao.view.View import net.shadowfacts.cacao.Window import net.shadowfacts.cacao.geometry.Axis @@ -12,10 +12,10 @@ import net.shadowfacts.cacao.view.StackView /** * @author shadowfacts */ -class TestScreen: Screen() { +class TestCacaoScreen: CacaoScreen() { init { - windows.add(Window().apply { + addWindow(Window().apply { val stack = addView(StackView(Axis.VERTICAL, StackView.Distribution.CENTER).apply { backgroundColor = Color.WHITE }) diff --git a/src/main/kotlin/net/shadowfacts/cacao/CacaoScreen.kt b/src/main/kotlin/net/shadowfacts/cacao/CacaoScreen.kt new file mode 100644 index 0000000..93b066c --- /dev/null +++ b/src/main/kotlin/net/shadowfacts/cacao/CacaoScreen.kt @@ -0,0 +1,43 @@ +package net.shadowfacts.cacao + +import net.minecraft.client.gui.screen.Screen +import net.minecraft.network.chat.TextComponent +import net.shadowfacts.cacao.geometry.Point +import java.util.* + +/** + * This class serves as the bridge between Cacao and a Minecraft [Screen]. It renders Cacao [Window]s in Minecraft and + * sends input events from Minecraft back to Cacao objects. + * + * @author shadowfacts + */ +open class CacaoScreen: Screen(TextComponent("CacaoScreen")) { + + // _windows is the internal, mutable object, since we only want it to by mutated by the add/removeWindow methods. + private val _windows = LinkedList() + /** + * The list of windows that belong to this screen. + * This list should never be modified directly, only by using the [addWindow]/[removeWindow] methods. + */ + val windows: List = _windows + + /** + * Adds the given window to this screen's window list. + * By default, the new window is added at the tail of the window list, making it the active window. + * Only the active window will receive input events. + * + * @param window The Window to add to this screen. + * @param index The index to insert the window into the window list at. + */ + fun addWindow(window: Window, index: Int = _windows.size) { + _windows.add(index, window) + } + + override fun render(mouseX: Int, mouseY: Int, delta: Float) { + val mouse = Point(mouseX, mouseY) + windows.forEach { + it.draw(mouse, delta) + } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/net/shadowfacts/cacao/LayoutVariable.kt b/src/main/kotlin/net/shadowfacts/cacao/LayoutVariable.kt index fa7f5b1..3161853 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/LayoutVariable.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/LayoutVariable.kt @@ -4,6 +4,9 @@ import net.shadowfacts.cacao.view.View import no.birkett.kiwi.Variable /** + * A Kiwi variable that belongs to a Cacao view. + * This class generally isn't used directly, but via the anchor *Anchor properties on [View]. + * * @author shadowfacts */ class LayoutVariable(val owner: View, val property: String): Variable("LayoutVariable") { diff --git a/src/main/kotlin/net/shadowfacts/cacao/Screen.kt b/src/main/kotlin/net/shadowfacts/cacao/Screen.kt deleted file mode 100644 index 43dfb39..0000000 --- a/src/main/kotlin/net/shadowfacts/cacao/Screen.kt +++ /dev/null @@ -1,29 +0,0 @@ -package net.shadowfacts.cacao - -import net.minecraft.client.gui.screen.Screen -import net.minecraft.network.chat.TextComponent -import net.shadowfacts.cacao.geometry.Point - -/** - * @author shadowfacts - */ -open class Screen: Screen(TextComponent("Screen")) { - - val windows = mutableListOf() - - fun addWindow(window: Window, index: Int? = null) { - if (index != null) { - windows.add(index, window) - } else { - windows.add(window) - } - } - - override fun render(mouseX: Int, mouseY: Int, delta: Float) { - val mouse = Point(mouseX, mouseY) - windows.forEach { - it.draw(mouse, delta) - } - } - -} \ No newline at end of file diff --git a/src/main/kotlin/net/shadowfacts/cacao/Window.kt b/src/main/kotlin/net/shadowfacts/cacao/Window.kt index 476b475..8167513 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/Window.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/Window.kt @@ -3,18 +3,36 @@ package net.shadowfacts.cacao import net.shadowfacts.cacao.geometry.Point import net.shadowfacts.cacao.view.View import no.birkett.kiwi.Solver +import java.util.* /** + * A Window is the object at the top of a Cacao view hierarchy. It occupies the entirety of the Minecraft screen size + * and provides the base coordinate system for its view hierarchy. + * + * The Window owns the Kiwi [Solver] object used for layout by all of its views. + * * @author shadowfacts */ class Window { var solver = Solver() - val views = mutableListOf() + // _views is the internal, mutable object, since we only want it to be mutated by the add/removeView methods + private val _views = LinkedList() + /** + * 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 = _views + /** + * 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 addView(view: T): T { - views.add(view) + _views.add(view) view.solver = solver view.wasAdded() @@ -22,11 +40,22 @@ class Window { return view } + /** + * Instructs the solver to solve all of the provided constraints. + * Should be called after the view hierarchy is setup. + */ fun layout() { solver.updateVariables() views.forEach(View::didLayout) } + /** + * Draws this window and all of its views. + * This method is called by [CacaoScreen] and generally shouldn't be called directly. + * + * @param mouse The point in the coordinate system of the window. + * @param delta The time elapsed since the last frame. + */ fun draw(mouse: Point, delta: Float) { views.forEach(View::draw) } diff --git a/src/main/kotlin/net/shadowfacts/cacao/geometry/Axis.kt b/src/main/kotlin/net/shadowfacts/cacao/geometry/Axis.kt index 69a4634..b77a31f 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/geometry/Axis.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/geometry/Axis.kt @@ -1,12 +1,17 @@ package net.shadowfacts.cacao.geometry /** + * An axis in a 2D coordinate plane. + * * @author shadowfacts */ enum class Axis { HORIZONTAL, VERTICAL; - val other: Axis + /** + * Gets the axis that is perpendicular to this one. + */ + val perpendicular: Axis get() = when (this) { HORIZONTAL -> VERTICAL VERTICAL -> HORIZONTAL diff --git a/src/main/kotlin/net/shadowfacts/cacao/geometry/AxisPosition.kt b/src/main/kotlin/net/shadowfacts/cacao/geometry/AxisPosition.kt index b0666b3..5d3a7f5 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/geometry/AxisPosition.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/geometry/AxisPosition.kt @@ -1,8 +1,21 @@ package net.shadowfacts.cacao.geometry /** + * A relative position on a line along an axis. + * * @author shadowfacts */ enum class AxisPosition { - LEADING, CENTER, TRAILING; + /** + * Top for vertical, left for horizontal. + */ + LEADING, + /** + * Center X/Y. + */ + CENTER, + /** + * Bottom for vertical, right for horizontal. + */ + TRAILING; } \ No newline at end of file diff --git a/src/main/kotlin/net/shadowfacts/cacao/geometry/Point.kt b/src/main/kotlin/net/shadowfacts/cacao/geometry/Point.kt index 9678816..754e2f5 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/geometry/Point.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/geometry/Point.kt @@ -1,6 +1,8 @@ package net.shadowfacts.cacao.geometry /** + * Helper class for defining 2D points. + * * @author shadowfacts */ data class Point(val x: Double, val y: Double) { diff --git a/src/main/kotlin/net/shadowfacts/cacao/geometry/Rect.kt b/src/main/kotlin/net/shadowfacts/cacao/geometry/Rect.kt index e2561c6..3935e4a 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/geometry/Rect.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/geometry/Rect.kt @@ -1,6 +1,8 @@ package net.shadowfacts.cacao.geometry /** + * Helper class for defining rectangles. Provides helper values for calculating perpendicular components of a rectangle based on X/Y/W/H. + * * @author shadowfacts */ data class Rect(val left: Double, val top: Double, val width: Double, val height: Double) { diff --git a/src/main/kotlin/net/shadowfacts/cacao/geometry/Size.kt b/src/main/kotlin/net/shadowfacts/cacao/geometry/Size.kt index efcd304..3ae4cc1 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/geometry/Size.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/geometry/Size.kt @@ -1,6 +1,8 @@ package net.shadowfacts.cacao.geometry /** + * Helper class for specifying the size of objects. + * * @author shadowfacts */ data class Size(val width: Double, val height: Double) \ No newline at end of file diff --git a/src/main/kotlin/net/shadowfacts/cacao/util/Color.kt b/src/main/kotlin/net/shadowfacts/cacao/util/Color.kt index d5314b5..6a113bd 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/util/Color.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/util/Color.kt @@ -1,12 +1,24 @@ package net.shadowfacts.cacao.util /** + * Helper class for Cacao colors. + * * @author shadowfacts + * @param red The red component, from 0-255. + * @param green The green component, from 0-255. + * @param blue The blue component, from 0-255. + * @param alpha The alpha (i.e. transparency) component, from 0-255. (0 is transparent, 255 is opaque) */ data class Color(val red: Int, val green: Int, val blue: Int, val alpha: Int = 255) { + /** + * Constructs a color from the packed RGB color. + */ constructor(rgb: Int, alpha: Int = 255): this(rgb shr 16, (rgb shr 8) and 255, rgb and 255, alpha) + /** + * The ARGB packed representation of this color. + */ val argb: Int get() = ((alpha and 255) shl 24) or ((red and 255) shl 16) or ((green and 255) shl 8) or (blue and 255) diff --git a/src/main/kotlin/net/shadowfacts/cacao/util/RenderHelper.kt b/src/main/kotlin/net/shadowfacts/cacao/util/RenderHelper.kt index a8787d4..557746e 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/util/RenderHelper.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/util/RenderHelper.kt @@ -4,10 +4,15 @@ import net.minecraft.client.gui.DrawableHelper import net.shadowfacts.cacao.geometry.Rect /** + * Helper methods for rendering using Minecraft's utilities from Cacao views. + * * @author shadowfacts */ object RenderHelper { + /** + * Draws a solid [rect] filled with the given [color]. + */ fun fill(rect: Rect, color: Color) { DrawableHelper.fill(rect.left.toInt(), rect.top.toInt(), rect.right.toInt(), rect.bottom.toInt(), color.argb) } diff --git a/src/main/kotlin/net/shadowfacts/cacao/view/Label.kt b/src/main/kotlin/net/shadowfacts/cacao/view/Label.kt index 798e930..d8e3cf1 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/view/Label.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/view/Label.kt @@ -6,15 +6,22 @@ import net.shadowfacts.cacao.geometry.Size import net.shadowfacts.cacao.util.Color /** + * A simple View that displays text. Allows for controlling the color and shadow of the text. Label cannot be used + * for multi-line text, instead use [TextView]. + * * @author shadowfacts + * @param text The text of this label. */ class Label(text: String): View() { companion object { - val textRenderer: TextRenderer + private val textRenderer: TextRenderer get() = MinecraftClient.getInstance().textRenderer } + /** + * The text of this label. Mutating this field will update the intrinsic content size and trigger a layout. + */ var text: String = text set(value) { field = value diff --git a/src/main/kotlin/net/shadowfacts/cacao/view/StackView.kt b/src/main/kotlin/net/shadowfacts/cacao/view/StackView.kt index dad2c46..ce27be9 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/view/StackView.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/view/StackView.kt @@ -6,29 +6,54 @@ import net.shadowfacts.cacao.geometry.Axis import net.shadowfacts.cacao.geometry.AxisPosition import net.shadowfacts.cacao.geometry.AxisPosition.* import no.birkett.kiwi.Constraint +import java.util.* /** + * A view that lays out its children in a stack along either the horizontal for vertical axes. + * This view does not have any content of its own. + * + * Only arranged subviews will be laid out in the stack mode, normal subviews must perform their own layout. + * * @author shadowfacts + * @param axis The primary axis that this stack lays out its children along. + * @param distribution The mode by which this stack lays out its children along the axis perpendicular to the + * primary [axis]. */ class StackView(val axis: Axis, val distribution: Distribution = Distribution.FILL): View() { - val arrangedSubviews = mutableListOf() + // the internal mutable, list of arranged subviews + private val _arrangedSubviews = LinkedList() + /** + * The list of arranged subviews belonging to this stack view. + * This list should never be mutated directly, only be calling the [addArrangedSubview]/[removeArrangedSubview] + * methods. + */ + val arrangedSubviews: List = _arrangedSubviews private var leadingConnection: Constraint? = null private var trailingConnection: Constraint? = null private var arrangedSubviewConnections = mutableListOf() + /** + * Adds an arranged subview to this view. + * Arranged subviews are laid out according to the stack. If you wish to add a subview that is laid out separately, + * use the normal [addSubview] method. + * + * @param view The view to add. + * @param index The index in this stack to add the view at. + * By default, adds the view to the end of the stack. + * @return The view that was added, as a convenience. + */ fun addArrangedSubview(view: T, index: Int = arrangedSubviews.size): T { addSubview(view) - - arrangedSubviews.add(index, view) + _arrangedSubviews.add(index, view) addConstraintsForArrangedView(view, index) return view } - fun addConstraintsForArrangedView(view: View, index: Int) { + private fun addConstraintsForArrangedView(view: View, index: Int) { if (index == 0) { if (leadingConnection != null) { solver.removeConstraint(leadingConnection) @@ -77,10 +102,107 @@ class StackView(val axis: Axis, val distribution: Distribution = Distribution.FI return view.getAnchor(axis, position) } private fun perpAnchor(position: AxisPosition, view: View = this): LayoutVariable { - return view.getAnchor(axis.other, position) + return view.getAnchor(axis.perpendicular, position) } + /** + * Defines the modes of how content is distributed in a stack view along the perpendicular axis (i.e. the + * non-primary axis). + * + * ASCII-art examples are shown below in a stack view with the primary axis [Axis.VERTICAL]. + */ enum class Distribution { - FILL, LEADING, CENTER, TRAILING + /** + * The leading edges of arranged subviews are pinned to the leading edge of the stack view. + * ``` + * ┌─────────────────────────────┐ + * │┌─────────────┐ │ + * ││ │ │ + * ││ │ │ + * ││ │ │ + * │└─────────────┘ │ + * │┌─────────┐ │ + * ││ │ │ + * ││ │ │ + * ││ │ │ + * │└─────────┘ │ + * │┌───────────────────────────┐│ + * ││ ││ + * ││ ││ + * ││ ││ + * │└───────────────────────────┘│ + * └─────────────────────────────┘ + * ``` + */ + LEADING, + /** + * The centers of the arranged subviews are pinned to the center of the stack view. + * ``` + * ┌─────────────────────────────┐ + * │ ┌─────────────┐ │ + * │ │ │ │ + * │ │ │ │ + * │ │ │ │ + * │ └─────────────┘ │ + * │ ┌─────────┐ │ + * │ │ │ │ + * │ │ │ │ + * │ │ │ │ + * │ └─────────┘ │ + * │┌───────────────────────────┐│ + * ││ ││ + * ││ ││ + * ││ ││ + * │└───────────────────────────┘│ + * └─────────────────────────────┘ + * ``` + */ + CENTER, + /** + * The trailing edges of arranged subviews are pinned to the leading edge of the stack view. + * ``` + * ┌─────────────────────────────┐ + * │ ┌─────────────┐│ + * │ │ ││ + * │ │ ││ + * │ │ ││ + * │ └─────────────┘│ + * │ ┌─────────┐│ + * │ │ ││ + * │ │ ││ + * │ │ ││ + * │ └─────────┘│ + * │┌───────────────────────────┐│ + * ││ ││ + * ││ ││ + * ││ ││ + * │└───────────────────────────┘│ + * └─────────────────────────────┘ + * ``` + */ + TRAILING, + /** + * The arranged subviews fill the perpendicular axis of the stack view. + * ``` + * ┌─────────────────────────────┐ + * │┌───────────────────────────┐│ + * ││ ││ + * ││ ││ + * ││ ││ + * │└───────────────────────────┘│ + * │┌───────────────────────────┐│ + * ││ ││ + * ││ ││ + * ││ ││ + * │└───────────────────────────┘│ + * │┌───────────────────────────┐│ + * ││ ││ + * ││ ││ + * ││ ││ + * │└───────────────────────────┘│ + * └─────────────────────────────┘ + * ``` + */ + FILL } } \ No newline at end of file diff --git a/src/main/kotlin/net/shadowfacts/cacao/view/View.kt b/src/main/kotlin/net/shadowfacts/cacao/view/View.kt index d488d4c..e82b96f 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/view/View.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/view/View.kt @@ -11,29 +11,73 @@ import net.shadowfacts.cacao.util.Color import net.shadowfacts.cacao.util.RenderHelper import no.birkett.kiwi.Constraint import no.birkett.kiwi.Solver +import java.util.* /** + * The base Cacao View class. Provides layout anchors, properties, and helper methods. + * Doesn't draw anything itself (unless [backgroundColor] is specified), but may be used for encapsulation/grouping. + * * @author shadowfacts */ open class View { + /** + * 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. + */ lateinit var solver: Solver - // in the coordinate system of the screen + /** + * Layout anchor for the left edge of this view in the window's coordinate system. + */ val leftAnchor = LayoutVariable(this, "left") + /** + * Layout anchor for the right edge of this view in the window's coordinate system. + */ val rightAnchor = LayoutVariable(this, "right") + /** + * Layout anchor for the top edge of this view in the window's coordinate system. + */ val topAnchor = LayoutVariable(this, "top") + /** + * Layout anchor for the bottom edge of this view in the window's coordinate system. + */ val bottomAnchor = LayoutVariable(this, "bottom") + /** + * Layout anchor for the width of this view in the window's coordinate system. + */ val widthAnchor = LayoutVariable(this, "width") + /** + * Layout anchor for the height of this view in the window's coordinate system. + */ val heightAnchor = LayoutVariable(this, "height") + /** + * Layout anchor for the center X position of this view in the window's coordinate system. + */ val centerXAnchor = LayoutVariable(this, "centerX") + /** + * Layout anchor for the center Y position of this view in the window's coordinate system. + */ val centerYAnchor = LayoutVariable(this, "centerY") - // The rectangle for this view in the coordinate system of the parent view. + /** + * The rectangle for this view in the coordinate system of its superview view (or the window, if there is no superview). + * Not initialized until [didLayout] called. + */ lateinit var frame: Rect - // The rectangle for this view in its own coordinate system. + /** + * The rectangle for this view in its own coordinate system. + * Not initialized until [didLayout] called. + */ lateinit var bounds: Rect + /** + * The intrinsic size of this view's content. May be null if the view doesn't have any content or there is no + * intrinsic size. + * + * Setting this creates/updates [no.birkett.kiwi.Strength.WEAK] constraints on this view's width/height using + * the size. + */ var intrinsicContentSize: Size? = null set(value) { updateIntrinsicContentSizeConstraints(intrinsicContentSize, value) @@ -42,11 +86,26 @@ open class View { private var intrinsicContentSizeWidthConstraint: Constraint? = null private var intrinsicContentSizeHeightConstraint: Constraint? = null + /** + * The background color of this view. + */ var backgroundColor = Color.CLEAR - var parent: View? = null - val subviews = mutableListOf() + /** + * This view's parent view. If `null`, this view is a top-level view in the [Window]. + */ + var superview: View? = null + // _subviews is the internal, mutable object since we only want it to by mutated by the add/removeSubview methods + private val _subviews = LinkedList() + /** + * The list of all the subviews of this view. + * This list should never by mutated directly, only by the [addSubview]/[removeSubview] methods. + */ + val subviews: List = _subviews + /** + * Helper method for retrieve the anchor for a specific position on the given axis. + */ fun getAnchor(axis: Axis, position: AxisPosition): LayoutVariable { return when (axis) { Axis.HORIZONTAL -> @@ -64,9 +123,15 @@ open class View { } } - fun addSubview(view: View): View { - subviews.add(view) - view.parent = this + /** + * Adds the given subview as a child of this view. + * + * @param view The view to add. + * @return The view that was added, as a convenience. + */ + fun addSubview(view: T): T { + _subviews.add(view) + view.superview = this view.solver = solver view.wasAdded() @@ -74,11 +139,19 @@ open class View { return view } + /** + * Called when this view was added to a view hierarchy. + * If overridden, the super-class method must be called. + */ open fun wasAdded() { createInternalConstraints() updateIntrinsicContentSizeConstraints(null, intrinsicContentSize) } + /** + * Called during [wasAdded] to add any constraints to the [solver] that are internal to this view. + * If overridden, the super-class method must be called. + */ open fun createInternalConstraints() { solver.dsl { rightAnchor equalTo (leftAnchor + widthAnchor) @@ -103,15 +176,24 @@ open class View { } } + /** + * Called after this view has been laid-out. + * If overridden, the super-class method must be called. + */ open fun didLayout() { subviews.forEach(View::didLayout) - val parentLeft = parent?.leftAnchor?.value ?: 0.0 - val parentTop = parent?.topAnchor?.value ?: 0.0 - frame = Rect(leftAnchor.value - parentLeft, topAnchor.value - parentTop, widthAnchor.value, heightAnchor.value) + val superviewLeft = superview?.leftAnchor?.value ?: 0.0 + val superviewTop = superview?.topAnchor?.value ?: 0.0 + frame = Rect(leftAnchor.value - superviewLeft, topAnchor.value - superviewTop, widthAnchor.value, heightAnchor.value) bounds = Rect(0.0, 0.0, widthAnchor.value, heightAnchor.value) } + /** + * 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. + */ fun draw() { GlStateManager.pushMatrix() GlStateManager.translated(frame.left, frame.top, 0.0) @@ -125,6 +207,11 @@ open class View { GlStateManager.popMatrix() } + /** + * Called during [draw] to draw content that's part of this view. + * During this method, the OpenGL coordinate system has been translated so the origin is at the top left corner + * of this view. Be careful not to translate additionally, and not to draw outside the [bounds] of the view. + */ open fun drawContent() {} } \ No newline at end of file