Skip to content

Commit

Permalink
Added new BGW hexagon game component view. (#369)
Browse files Browse the repository at this point in the history
* Added hexagon view component.

* Added sample builder for hexagon view.

* Display hexagons according to given size.

* Refactored building hexagons. Added new hexagon builder.

* Build compound visuals for hexagon view.

* Added safe element indexing in observable lists.

* Refactored hexagon view constructor parameter to take radius instead of diameter.

* Added empty hexagon view animation to animation builder.

* Fixed mouse picking fo hexagon views with overlapping layout bounds.

* Reformatted ComponentNodeBuilder.kt

* 🎉 Added hexagon grid game component container 🎉

* Added new coordinate system enum.

* Added CoordinateSystem to HexagonGrid.

* Wrapped hexagon views in region for proper layouting.

* Added FlipAnimation support for Hexagons.

* Added documentation for hexagons.

* Applied spotless.

* Moved hexagon grid to own branch.

* Updated CHANGELOG.md

---------

Co-authored-by: Amin Bouzerda <amin.bouzerda@tu-dortmund.de>
  • Loading branch information
abouzerda and Amin Bouzerda authored May 17, 2023
1 parent c6ce44e commit 7796440
Show file tree
Hide file tree
Showing 9 changed files with 212 additions and 14 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
[0.8]: https://github.com/tudo-aqua/bgw/releases/tag/v0.8
[0.7.3]: https://github.com/tudo-aqua/bgw/releases/tag/v0.7.3
[0.7.2]: https://github.com/tudo-aqua/bgw/releases/tag/v0.7.2
[0.7.1]: https://github.com/tudo-aqua/bgw/releases/tag/v0.7.1
Expand All @@ -18,6 +19,11 @@
# Changelog
All notable changes to this project will be documented in this file.

## [0.8] - 22.05.2023

### Added
- `HexagonView` component.

## [0.7.3] - 31.08.2022

### Added
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ nav_order: 3
[LinearLayoutDoc]: ../../bgw-gui-kdoc/bgw-gui/tools.aqua.bgw.components.container/-linear-layout/index.html
[DiceDoc]: ../../bgw-gui-kdoc/bgw-gui/tools.aqua.bgw.components.gamecomponentviews/-dice-view/index.html
[DiceAnimationDoc]: ../../bgw-gui-kdoc/bgw-gui/tools.aqua.bgw.animation/-dice-animation/index.html
[HexagonDoc]: ../../bgw-gui-kdoc/bgw-gui/tools.aqua.bgw.components.gamecomponentviews/-hexagon-view/index.html

<!-- GH-Pages Doc -->
[ComponentViewDoc]: ../../components/componentview/componentview.md
Expand Down Expand Up @@ -62,4 +63,10 @@ It is not limited to a D6 and has the exact amount of sides as it has visuals, w
side e.g. the visual at index 0 is side 1.
If the list of visuals gets altered the amount of sides changes too.

For a dice roll there exists a dedicated [DiceAnimation][DiceAnimationDoc].
For a dice roll there exists a dedicated [DiceAnimation][DiceAnimationDoc].

## Hexagons
The [HexagonView][HexagonDoc] component can also be utilized for hexagonal tokens,
providing a versatile solution for displaying various hexagonal-shaped elements.

Hexagons only have one visual.
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,7 @@ import kotlin.random.Random
import tools.aqua.bgw.animation.*
import tools.aqua.bgw.animation.Animation
import tools.aqua.bgw.components.ComponentView
import tools.aqua.bgw.components.gamecomponentviews.CardView
import tools.aqua.bgw.components.gamecomponentviews.DiceView
import tools.aqua.bgw.components.gamecomponentviews.GameComponentView
import tools.aqua.bgw.components.gamecomponentviews.TokenView
import tools.aqua.bgw.components.gamecomponentviews.*
import tools.aqua.bgw.core.Scene
import tools.aqua.bgw.event.AnimationFinishedEvent

Expand Down Expand Up @@ -188,13 +185,15 @@ object AnimationBuilder {
if (componentView.currentSide == CardView.CardSide.FRONT) anim.fromVisual
else anim.toVisual
}
is HexagonView -> componentView.visual = anim.fromVisual
}

animation1.setOnFinished {
when (componentView) {
is TokenView -> componentView.visual = anim.toVisual
is DiceView -> componentView.visuals[componentView.currentSide] = anim.toVisual
is CardView -> componentView.flip()
is HexagonView -> componentView.visual = anim.toVisual
}

animation2.play()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,8 @@ package tools.aqua.bgw.builder

import javafx.scene.layout.Pane
import javafx.scene.layout.Region
import tools.aqua.bgw.components.gamecomponentviews.CardView
import tools.aqua.bgw.components.gamecomponentviews.DiceView
import tools.aqua.bgw.components.gamecomponentviews.GameComponentView
import tools.aqua.bgw.components.gamecomponentviews.TokenView
import javafx.scene.paint.*
import tools.aqua.bgw.components.gamecomponentviews.*

/** [ComponentNodeBuilder]. Factory for all BGW components. */
object ComponentNodeBuilder {
Expand All @@ -31,7 +29,8 @@ object ComponentNodeBuilder {
when (gameComponentView) {
is CardView -> buildCardView(gameComponentView)
is DiceView -> buildDiceView(gameComponentView)
is TokenView -> buildToken(gameComponentView)
is TokenView -> buildTokenView(gameComponentView)
is HexagonView -> buildHexagonView(gameComponentView)
}

/** Builds [CardView]. */
Expand All @@ -41,5 +40,9 @@ object ComponentNodeBuilder {
@Suppress("UNUSED_PARAMETER") private fun buildDiceView(diceView: DiceView): Region = Pane()

/** Builds [TokenView]. */
@Suppress("UNUSED_PARAMETER") private fun buildToken(tokenView: TokenView): Region = Pane()
@Suppress("UNUSED_PARAMETER") private fun buildTokenView(tokenView: TokenView): Region = Pane()

/** Builds [HexagonView]. */
private fun buildHexagonView(hexagonView: HexagonView): Region =
HexagonBuilder.buildHexagonView(hexagonView)
}
117 changes: 117 additions & 0 deletions bgw-gui/src/main/kotlin/tools/aqua/bgw/builder/HexagonBuilder.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*
* Copyright 2023 The BoardGameWork Authors
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package tools.aqua.bgw.builder

import javafx.scene.layout.Pane
import javafx.scene.layout.Region
import javafx.scene.layout.StackPane
import javafx.scene.paint.Color
import javafx.scene.paint.ImagePattern
import javafx.scene.paint.Paint
import javafx.scene.shape.Polygon
import javafx.scene.shape.StrokeLineCap
import javafx.scene.shape.StrokeLineJoin
import javafx.scene.shape.StrokeType
import kotlin.math.cos
import kotlin.math.sin
import tools.aqua.bgw.builder.FXConverters.toFXColor
import tools.aqua.bgw.builder.FXConverters.toFXImage
import tools.aqua.bgw.components.gamecomponentviews.HexagonView
import tools.aqua.bgw.visual.*

object HexagonBuilder {
/** Degrees in a circle. */
private const val FULL_CIRCLE_DEGREES = 360

/** Amount of sides in a hexagon. */
private const val HEXAGON_SIDES = 6

private const val BORDER_WIDTH = 50.0

/** Builds [HexagonView]. */
internal fun buildHexagonView(hexagonView: HexagonView): Region {
val points = generatePoints(hexagonView.size.toDouble())
return when (val visual = hexagonView.visual) {
is TextVisual -> buildPolygon(points, visual)
is SingleLayerVisual -> buildPolygon(points, visual)
is CompoundVisual -> buildPolygon(points, visual)
}.also { hexagonView.visual = Visual.EMPTY }
}

private fun buildPolygon(points: DoubleArray, compoundVisual: CompoundVisual): Region =
StackPane(
*compoundVisual.children
.map {
when (it) {
is TextVisual -> buildPolygon(points, it)
else -> buildPolygon(points, it)
}
}
.toTypedArray())
.apply { isPickOnBounds = false }

private fun buildPolygon(points: DoubleArray, visual: SingleLayerVisual): Region =
Pane(
Polygon(*points).apply {
val paint = buildPaint(visual)
fill = paint
visual.transparencyProperty.addListenerAndInvoke(visual.transparency) { _, nV ->
opacity = nV
}
stroke = Color.BLACK
strokeType = StrokeType.INSIDE
// roundCorners(paint)
})
.apply { isPickOnBounds = false }

private fun buildPolygon(points: DoubleArray, visual: TextVisual): Region =
StackPane(
buildPolygon(points, visual as SingleLayerVisual),
VisualBuilder.buildVisual(visual).apply { isPickOnBounds = false })
.apply { isPickOnBounds = false }

private fun Polygon.roundCorners(paint: Paint) {
stroke = paint
strokeWidth = BORDER_WIDTH
strokeType = StrokeType.CENTERED
strokeLineJoin = StrokeLineJoin.ROUND
strokeLineCap = StrokeLineCap.ROUND
strokeMiterLimit = BORDER_WIDTH
}

private fun generatePoints(size: Double): DoubleArray {
val points = mutableListOf<Double>()
var angle = 90.0
for (i in 0 until HEXAGON_SIDES) {
val x = size * cos(Math.toRadians(angle)) + size
val y = size * sin(Math.toRadians(angle)) + size
angle += FULL_CIRCLE_DEGREES / HEXAGON_SIDES
points.add(x)
points.add(y)
}
return points.toDoubleArray()
}

private fun buildPaint(visual: SingleLayerVisual): Paint {
return when (visual) {
is ColorVisual -> visual.color.toFXColor()
is ImageVisual -> ImagePattern(visual.image.toFXImage())
is TextVisual -> Color.TRANSPARENT
}
}
}
11 changes: 8 additions & 3 deletions bgw-gui/src/main/kotlin/tools/aqua/bgw/builder/NodeBuilder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

package tools.aqua.bgw.builder

import javafx.scene.Node
import javafx.scene.layout.Region
import javafx.scene.layout.StackPane
import tools.aqua.bgw.builder.DragDropBuilder.registerDragEvents
Expand All @@ -29,6 +30,7 @@ import tools.aqua.bgw.components.DynamicComponentView
import tools.aqua.bgw.components.StaticComponentView
import tools.aqua.bgw.components.container.GameComponentContainer
import tools.aqua.bgw.components.gamecomponentviews.GameComponentView
import tools.aqua.bgw.components.gamecomponentviews.HexagonView
import tools.aqua.bgw.components.layoutviews.LayoutView
import tools.aqua.bgw.components.layoutviews.Pane
import tools.aqua.bgw.components.uicomponents.UIComponent
Expand All @@ -41,7 +43,7 @@ import tools.aqua.bgw.exception.IllegalInheritanceException
object NodeBuilder {
/** Switches between top level component types. */
internal fun build(scene: Scene<out ComponentView>, componentView: ComponentView): Region {
val node =
val node: Region =
when (componentView) {
is GameComponentContainer<out GameComponentView> ->
ContainerNodeBuilder.buildContainer(scene, componentView)
Expand All @@ -56,7 +58,10 @@ object NodeBuilder {
else -> throw IllegalInheritanceException(componentView, ComponentView::class.java)
}
val background = VisualBuilder.build(componentView)
val stackPane = StackPane(background, node).apply { isPickOnBounds = false }
var stackPane = StackPane(background, node).apply { isPickOnBounds = false }
if (componentView is HexagonView) {
stackPane = StackPane(node).apply { isPickOnBounds = false }
}

// JavaFX -> Framework
componentView.registerEvents(stackPane, node, scene)
Expand Down Expand Up @@ -170,7 +175,7 @@ object NodeBuilder {
}

/** Updates nodes style property. */
private fun UIComponent.updateStyle(node: Region) {
private fun UIComponent.updateStyle(node: Node) {
node.style = this.internalCSS + this.font.toFXFontCSS() + componentStyle
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright 2023 The BoardGameWork Authors
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package tools.aqua.bgw.components.gamecomponentviews

import kotlin.math.sqrt
import tools.aqua.bgw.core.DEFAULT_HEXAGON_SIZE
import tools.aqua.bgw.visual.Visual

/**
* A [HexagonView] represents a hexagonal shaped game component view.
*
* @constructor Creates a [HexagonView] with a given [Visual].
*
* @param posX Horizontal coordinate for this [HexagonView]. Default: 0.
* @param posY Vertical coordinate for this [HexagonView]. Default: 0.
* @param size Represents the radius of the outer circle of the [HexagonView] all six points lie on.
* Default: [DEFAULT_HEXAGON_SIZE].
* @param visual Visual for this [HexagonView].
*/
open class HexagonView(
posX: Number = 0,
posY: Number = 0,
val size: Number = DEFAULT_HEXAGON_SIZE,
visual: Visual
) :
GameComponentView(
posX = posX,
posY = posY,
width = sqrt(3.0) * size.toDouble(),
height = 2 * size.toDouble(),
visual = visual)
3 changes: 3 additions & 0 deletions bgw-gui/src/main/kotlin/tools/aqua/bgw/core/Constants.kt
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ const val DEFAULT_ANIMATION_SPEED: Int = 50
// endregion

// region Components
/** Default [DEFAULT_HEXAGON_SIZE] size. */
const val DEFAULT_HEXAGON_SIZE: Double = 100.0

/** Default [CardView] width. */
const val DEFAULT_CARD_WIDTH: Double = 130.0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,18 @@ abstract class ReadonlyObservableList<T> : ValueObservable<List<T>>(), Iterable<
*/
operator fun get(index: Int): T = list[index]

/**
* May return the element at the specified position in this list or null.
*
* @param index Index of the element to return.
*
* @return The element at the specified position in this list or null if the index exceeds the
* list's bounds.
*/
fun getOrNull(index: Int): T? {
return if (index >= 0 && index <= list.size) get(index) else null
}

/**
* Creates a *fail-fast* [Spliterator] over the elements in this list.
*
Expand Down

0 comments on commit 7796440

Please sign in to comment.