From ab4db9e0f837529c703f189e532a97aa45663f8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20M=C3=A4ckel?= Date: Wed, 22 Sep 2021 10:14:20 +0200 Subject: [PATCH] Merge Dev for v0.4 deploy (#191) * Fixed NullPointerException due to race condition in Frontend#sizeChanged. (#177) * Fix recursive position search in containers for MovementAnimation#toComponentView (#175) * Fixed recursive child position search for GridPanes. * Fixed FileDialogs returning list of nulls instead of empty optional. * Empty grid columns and rows no longer get rendered size 0.0 in case of fixed dimensions. (#180) * BoardGameScene getting shown blurred if showGameScene gets called after hideMenuScene. (#184) * Removed all non-null assertions replacing them with safe calls or state checks. (#185) * Fixed wrong parent in rollback search for containers after drag and drop. (#189) * Cleanup code fixing detect findings. * Fix toComponentView animation missing layoutFromCenter offset for Grid. (#190) * Fix toComponentView animation missing layoutFromCenter offset for Grid; toComponentView animation now working with scale. * Fix drag and drop for grid. * Fix drag and drop target for custom inter-cell-alignments --- CHANGELOG.md | 11 +++ .../aqua/bgw/animation/MovementAnimation.kt | 42 ++++++-- .../aqua/bgw/builder/AnimationBuilder.kt | 16 ++- .../tools/aqua/bgw/builder/DialogBuilder.kt | 2 +- .../tools/aqua/bgw/builder/FXConverters.kt | 1 + .../kotlin/tools/aqua/bgw/builder/Frontend.kt | 30 +++--- .../aqua/bgw/builder/LayoutNodeBuilder.kt | 99 +++++++++++-------- .../tools/aqua/bgw/builder/NodeBuilder.kt | 53 +++++----- .../tools/aqua/bgw/builder/UINodeBuilder.kt | 12 ++- .../aqua/bgw/components/ComponentView.kt | 14 ++- .../bgw/components/layoutviews/GridPane.kt | 22 ++++- .../bgw/components/uicomponents/TextArea.kt | 10 +- .../bgw/components/uicomponents/TextField.kt | 9 +- .../uicomponents/TextInputUIComponent.kt | 20 ++++ .../main/kotlin/tools/aqua/bgw/core/Scene.kt | 4 +- .../kotlin/tools/aqua/bgw/dialog/Dialog.kt | 4 +- .../tools/aqua/bgw/util/CoordinatePlain.kt | 10 +- .../main/kotlin/tools/aqua/bgw/util/Stack.kt | 12 +-- .../main/kotlin/tools/aqua/bgw/util/Trig.kt | 3 + .../util/coordinates/CoordinatePlainTest.kt | 5 - 20 files changed, 245 insertions(+), 134 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f3a9772e3f..26515c2640 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ +[0.4]: https://github.com/tudo-aqua/bgw/releases/tag/v0.4 [0.3]: https://github.com/tudo-aqua/bgw/releases/tag/v0.3 [0.2]: https://github.com/tudo-aqua/bgw/releases/tag/v0.2 [0.1]: https://github.com/tudo-aqua/bgw/releases/tag/v0.1 @@ -11,6 +12,16 @@ # Changelog All notable changes to this project will be documented in this file. +## [0.4] - To be released + +### Fixed +- Empty grid columns and rows no longer get rendered size 0.0 in case of fixed dimensions. +- Race condition while changing GameScenes caused by slow renderer. +- FileDialogs returning list of nulls instead of empty optional. +- Snap back from Drag and Drop. +- MovementAnimation#toComponentView offset when animating to GridPane. +- BoardGameScene getting shown blurred if showGameScene gets called after hideMenuScene. + ## [0.3] - 09. Sep. 2021 ### Added diff --git a/bgw-core/src/main/kotlin/tools/aqua/bgw/animation/MovementAnimation.kt b/bgw-core/src/main/kotlin/tools/aqua/bgw/animation/MovementAnimation.kt index 4cfa4feab8..95bb62d413 100644 --- a/bgw-core/src/main/kotlin/tools/aqua/bgw/animation/MovementAnimation.kt +++ b/bgw-core/src/main/kotlin/tools/aqua/bgw/animation/MovementAnimation.kt @@ -77,8 +77,10 @@ class MovementAnimation( */ constructor(componentView: T, byX: Number = 0.0, byY: Number = 0.0, duration: Int = 1000) : this( componentView = componentView, - toX = componentView.posX + byX.toDouble(), - toY = componentView.posY + byY.toDouble(), + fromX = componentView.parent?.getChildPosition(componentView)?.xCoord?:componentView.posX, + fromY = componentView.parent?.getChildPosition(componentView)?.yCoord?:componentView.posY, + toX = (componentView.parent?.getChildPosition(componentView)?.xCoord?:componentView.posX) + byX.toDouble(), + toY = (componentView.parent?.getChildPosition(componentView)?.yCoord?:componentView.posY) + byY.toDouble(), duration = duration ) @@ -102,11 +104,37 @@ class MovementAnimation( val pathToComponent = scene.findPathToChild(componentView).dropLast(1) val pathToDestination = scene.findPathToChild(toComponentViewPosition).dropLast(1) - //Sum relative positions - val componentAbsoluteX = pathToComponent.sumOf { it.posX } - val componentAbsoluteY = pathToComponent.sumOf { it.posY } - val destinationAbsoluteX = pathToDestination.sumOf { it.posX } - val destinationAbsoluteY = pathToDestination.sumOf { it.posY } + //Sum relative positions and rotation and multiply scale + var componentAbsoluteX = 0.0 + var componentAbsoluteY = 0.0 + var destinationAbsoluteX = 0.0 + var destinationAbsoluteY = 0.0 + + var componentScaleX = 1.0 + var componentScaleY = 1.0 + var destinationScaleX = 1.0 + var destinationScaleY = 1.0 + + var componentRotation = 0.0 + var destinationRotation = 0.0 + + pathToComponent.forEach { + val pos = it.parent?.getActualChildPosition(it) + componentAbsoluteX += pos?.xCoord?:it.actualPosX + componentAbsoluteY += pos?.yCoord?:it.actualPosY + componentScaleX *= it.scaleX + componentScaleY *= it.scaleY + componentRotation += it.rotation + } + + pathToDestination.forEach { + val pos = it.parent?.getActualChildPosition(it) + destinationAbsoluteX += pos?.xCoord?:it.actualPosX + destinationAbsoluteY += pos?.yCoord?:it.actualPosY + destinationScaleX *= it.scaleX + destinationScaleY *= it.scaleY + destinationRotation += it.rotation + } return MovementAnimation( componentView = componentView, diff --git a/bgw-core/src/main/kotlin/tools/aqua/bgw/builder/AnimationBuilder.kt b/bgw-core/src/main/kotlin/tools/aqua/bgw/builder/AnimationBuilder.kt index 4dae728e06..aaa4665b3d 100644 --- a/bgw-core/src/main/kotlin/tools/aqua/bgw/builder/AnimationBuilder.kt +++ b/bgw-core/src/main/kotlin/tools/aqua/bgw/builder/AnimationBuilder.kt @@ -55,7 +55,7 @@ internal class AnimationBuilder { scene: Scene, anim: MovementAnimation<*> ): javafx.animation.Animation { - val node = scene.componentsMap[anim.componentView]!! + val node = mapNode(scene, anim.componentView) //Move node to initial position node.layoutX = anim.fromX @@ -84,7 +84,7 @@ internal class AnimationBuilder { scene: Scene, anim: RotationAnimation<*> ): javafx.animation.Animation { - val node = scene.componentsMap[anim.componentView]!! + val node = mapNode(scene, anim.componentView) //Move node to initial position node.rotate = anim.fromAngle @@ -110,7 +110,7 @@ internal class AnimationBuilder { scene: Scene, anim: ScaleAnimation<*> ): javafx.animation.Animation { - val node = scene.componentsMap[anim.componentView]!! + val node = mapNode(scene, anim.componentView) //Set initial scale node.scaleX = anim.fromScaleX @@ -134,7 +134,7 @@ internal class AnimationBuilder { scene: Scene, anim: FadeAnimation<*> ): javafx.animation.Animation { - val node = scene.componentsMap[anim.componentView]!! + val node = mapNode(scene, anim.componentView) //Set initial opacity node.opacity = anim.fromOpacity @@ -156,7 +156,7 @@ internal class AnimationBuilder { scene: Scene, anim: FlipAnimation<*> ): javafx.animation.Animation { - val node = scene.componentsMap[anim.componentView]!! + val node = mapNode(scene, anim.componentView) val fromVisual = VisualBuilder.buildVisual(anim.fromVisual) val toVisual = VisualBuilder.buildVisual(anim.toVisual).apply { scaleX = 0.0 } @@ -258,5 +258,11 @@ internal class AnimationBuilder { anim.onFinished?.invoke(AnimationFinishedEvent()) } } + + /** + * Maps [ComponentView] to FX node. + */ + private fun mapNode(scene: Scene, componentView : ComponentView) = + checkNotNull(scene.componentsMap[componentView]) { "Creating animation for node that is not in scene." } } } \ No newline at end of file diff --git a/bgw-core/src/main/kotlin/tools/aqua/bgw/builder/DialogBuilder.kt b/bgw-core/src/main/kotlin/tools/aqua/bgw/builder/DialogBuilder.kt index 062dab7fc5..d183c61141 100644 --- a/bgw-core/src/main/kotlin/tools/aqua/bgw/builder/DialogBuilder.kt +++ b/bgw-core/src/main/kotlin/tools/aqua/bgw/builder/DialogBuilder.kt @@ -53,7 +53,7 @@ internal class DialogBuilder { //Add expandable content for exception stack trace in case of AlertType.EXCEPTION if (dialog.dialogType == DialogType.EXCEPTION) { - dialogPane.expandableContent = TextArea(dialog.exception!!.stackTraceToString()).apply { + dialogPane.expandableContent = TextArea(dialog.exception.stackTraceToString()).apply { isEditable = false isWrapText = true maxWidth = Double.MAX_VALUE diff --git a/bgw-core/src/main/kotlin/tools/aqua/bgw/builder/FXConverters.kt b/bgw-core/src/main/kotlin/tools/aqua/bgw/builder/FXConverters.kt index 419b602ec4..37af660c0a 100644 --- a/bgw-core/src/main/kotlin/tools/aqua/bgw/builder/FXConverters.kt +++ b/bgw-core/src/main/kotlin/tools/aqua/bgw/builder/FXConverters.kt @@ -103,6 +103,7 @@ internal class FXConverters private constructor() { /** * Converts the [javafx.scene.input.KeyCode] to [KeyCode]. */ + @Suppress("LongMethod") internal fun FXKeyCode.toKeyCode(): KeyCode = when (this) { FXKeyCode.SHIFT -> KeyCode.SHIFT FXKeyCode.CONTROL -> KeyCode.CONTROL diff --git a/bgw-core/src/main/kotlin/tools/aqua/bgw/builder/Frontend.kt b/bgw-core/src/main/kotlin/tools/aqua/bgw/builder/Frontend.kt index ade7ce80f9..02b797d8a5 100644 --- a/bgw-core/src/main/kotlin/tools/aqua/bgw/builder/Frontend.kt +++ b/bgw-core/src/main/kotlin/tools/aqua/bgw/builder/Frontend.kt @@ -315,11 +315,11 @@ internal class Frontend : Application() { * @return [gamePane] for [boardGameScene], [menuPane] for [menuScene] and `null` for other parameters. */ internal fun tools.aqua.bgw.core.Scene<*>.mapToPane(): Pane = - when (this) { + checkNotNull(when (this) { boardGameScene -> gamePane menuScene -> menuPane else -> null - }?:throw IllegalStateException() + }) /** * Returns scene associated to pane. @@ -339,7 +339,7 @@ internal class Frontend : Application() { * @param stage application stage. */ internal fun startApplication(stage: Stage) { - primaryStage = stage.apply { + val primaryStage = stage.apply { //Initialize default DECORATED stage style allowing minimizing initStyle(StageStyle.DECORATED) @@ -367,6 +367,7 @@ internal class Frontend : Application() { if (!isFullScreen && !isMaximized) height = nV } + titleProperty.setGUIListenerAndInvoke(titleProperty.value) { _, nV -> title = nV } maximizedProperty().addListener { _, _, nV -> maximizedProperty.setSilent(nV) } fullScreenProperty().addListener { _, _, nV -> fullscreenProperty.setSilent(nV) } @@ -386,18 +387,15 @@ internal class Frontend : Application() { menuScene?.let { menuPane = buildMenu(it) } boardGameScene?.let { gamePane = buildGame(it) } - titleProperty.setGUIListenerAndInvoke(titleProperty.value) { _, nV -> - primaryStage?.title = nV - } - backgroundProperty.setGUIListenerAndInvoke(backgroundProperty.value) { _, nV -> backgroundPane.children.clear() backgroundPane.children.add(VisualBuilder.buildVisual(nV).apply { - prefWidthProperty().bind(primaryStage!!.widthProperty()) - prefHeightProperty().bind(primaryStage!!.heightProperty()) + prefWidthProperty().bind(primaryStage.widthProperty()) + prefHeightProperty().bind(primaryStage.heightProperty()) }) } + this.primaryStage = primaryStage updateScene() } @@ -422,13 +420,13 @@ internal class Frontend : Application() { Optional.ofNullable( when (dialog.mode) { OPEN_FILE -> - listOf(FileChooserBuilder.buildFileChooser(dialog).showOpenDialog(primaryStage)) + FileChooserBuilder.buildFileChooser(dialog).showOpenDialog(primaryStage)?.let { listOf(it) } OPEN_MULTIPLE_FILES -> FileChooserBuilder.buildFileChooser(dialog).showOpenMultipleDialog(primaryStage) SAVE_FILE -> - listOf(FileChooserBuilder.buildFileChooser(dialog).showSaveDialog(primaryStage)) + FileChooserBuilder.buildFileChooser(dialog).showSaveDialog(primaryStage)?.let { listOf(it) } CHOOSE_DIRECTORY -> - listOf(FileChooserBuilder.buildDirectoryChooser(dialog).showDialog(primaryStage)) + FileChooserBuilder.buildDirectoryChooser(dialog).showDialog(primaryStage)?.let { listOf(it) } } ) @@ -456,14 +454,16 @@ internal class Frontend : Application() { */ private fun fadeMenu(fadeIn: Boolean, fadeTime: Double) { menuPane?.apply { - FadeTransition(Duration.millis(fadeTime / 2), menuPane).apply { + if (!fadeIn) + menuPane = null + + FadeTransition(Duration.millis(fadeTime / 2), this).apply { fromValue = if (fadeIn) 0.0 else 1.0 toValue = if (fadeIn) 1.0 else 0.0 interpolator = Interpolator.EASE_OUT onFinished = EventHandler { if (!fadeIn) { - menuPane = null - if (boardGameScene != null) boardGameScene!!.unlock() + boardGameScene?.unlock() updateScene() } } diff --git a/bgw-core/src/main/kotlin/tools/aqua/bgw/builder/LayoutNodeBuilder.kt b/bgw-core/src/main/kotlin/tools/aqua/bgw/builder/LayoutNodeBuilder.kt index a2719962ce..97cef1465c 100644 --- a/bgw-core/src/main/kotlin/tools/aqua/bgw/builder/LayoutNodeBuilder.kt +++ b/bgw-core/src/main/kotlin/tools/aqua/bgw/builder/LayoutNodeBuilder.kt @@ -89,23 +89,30 @@ internal class LayoutNodeBuilder { } } + //Calculate row heights gridView.renderedRowHeights = DoubleArray(grid.rows) { - grid.getRow(it).maxOf { entry -> entry?.let { t -> - val fixedHeight = grid.getRowHeight(it) - if(fixedHeight == ROW_HEIGHT_AUTO) - t.layoutBounds.height + t.posY - else - fixedHeight - } ?: 0.0 } + var height = grid.getRowHeight(it) + + if(height == ROW_HEIGHT_AUTO) { + height = grid.getRow(it).filterNotNull().maxOfOrNull { entry -> + entry.let { t -> t.layoutBounds.height + t.posY } + }?:0.0 + } + + height } + + //Calculate column widths gridView.renderedColWidths = DoubleArray(grid.columns) { - grid.getColumn(it).maxOf { entry -> entry?.let { t -> - val fixedWidth = grid.getColumnWidth(it) - if(fixedWidth == COLUMN_WIDTH_AUTO) - t.layoutBounds.width + t.posX - else - fixedWidth - } ?: 0.0 } + var width = grid.getColumnWidth(it) + + if(width == COLUMN_WIDTH_AUTO) { + width = grid.getColumn(it).filterNotNull().maxOfOrNull { entry -> + entry.let { t -> t.layoutBounds.width + t.posX } + }?:0.0 + } + + width } gridView.width = @@ -113,35 +120,43 @@ internal class LayoutNodeBuilder { gridView.height = gridView.renderedRowHeights.sum() + (gridView.renderedRowHeights.size - 1) * gridView.spacing - nodes.forEach { triple -> - val colIndex = triple.first.first - val rowIndex = triple.first.second - val component = triple.second - val node = triple.third - val posX = (0 until colIndex).sumOf { gridView.renderedColWidths[it] } + colIndex * gridView.spacing - val posY = (0 until rowIndex).sumOf { gridView.renderedRowHeights[it] } + rowIndex * gridView.spacing + nodes.forEach { triple -> refreshGridNode(gridView, triple) } + } + + /** + * Refreshes grid node. + */ + private fun FXPane.refreshGridNode( + gridView: GridPane, + triple: Triple, ComponentView, Node>) { + + val colIndex = triple.first.first + val rowIndex = triple.first.second + val component = triple.second + val node = triple.third + val posX = (0 until colIndex).sumOf { gridView.renderedColWidths[it] } + colIndex * gridView.spacing + val posY = (0 until rowIndex).sumOf { gridView.renderedRowHeights[it] } + rowIndex * gridView.spacing + + children.add(node.apply { + val nodeWidth = component.layoutBounds.width + val nodeHeight = component.layoutBounds.height - children.add(node.apply { - val nodeWidth = component.layoutBounds.width - val nodeHeight = component.layoutBounds.height - - //Calculate delta due to scale and rotation - val deltaX = (nodeWidth - component.width) / 2 - val deltaY = (nodeHeight - component.height) / 2 - - //Calculate anchor point for flush TOP_LEFT placement - val anchorX = posX + component.posX + deltaX - val anchorY = posY + component.posY + deltaY - - //Account for centering - val centerMode = gridView.getCellCenterMode(columnIndex = colIndex, rowIndex = rowIndex) - val remainingSpaceX = gridView.renderedColWidths[colIndex] - nodeWidth - component.posX - val remainingSpaceY = gridView.renderedRowHeights[rowIndex] - nodeHeight - component.posY - - layoutX = anchorX + remainingSpaceX * centerMode.horizontalAlignment.positionMultiplier - layoutY = anchorY + remainingSpaceY * centerMode.verticalAlignment.positionMultiplier - }) - } + //Calculate delta due to scale and rotation + val deltaX = (nodeWidth - component.width) / 2 + val deltaY = (nodeHeight - component.height) / 2 + + //Calculate anchor point for flush TOP_LEFT placement + val anchorX = posX + component.posX + deltaX + val anchorY = posY + component.posY + deltaY + + //Account for centering + val centerMode = gridView.getCellCenterMode(columnIndex = colIndex, rowIndex = rowIndex) + val remainingSpaceX = gridView.renderedColWidths[colIndex] - nodeWidth - component.posX + val remainingSpaceY = gridView.renderedRowHeights[rowIndex] - nodeHeight - component.posY + + layoutX = anchorX + remainingSpaceX * centerMode.horizontalAlignment.positionMultiplier + layoutY = anchorY + remainingSpaceY * centerMode.verticalAlignment.positionMultiplier + }) } } } \ No newline at end of file diff --git a/bgw-core/src/main/kotlin/tools/aqua/bgw/builder/NodeBuilder.kt b/bgw-core/src/main/kotlin/tools/aqua/bgw/builder/NodeBuilder.kt index c218f4654a..f4aaaa6f79 100644 --- a/bgw-core/src/main/kotlin/tools/aqua/bgw/builder/NodeBuilder.kt +++ b/bgw-core/src/main/kotlin/tools/aqua/bgw/builder/NodeBuilder.kt @@ -142,18 +142,13 @@ internal class NodeBuilder { yCoord = pathToChild.sumOf { t -> t.posY } ) - val rollback: (() -> Unit) = when (val parent = pathToChild[1]) { + val rollback: (() -> Unit) = when (val parent = parent) { is GameComponentContainer<*> -> { parent.findRollback(this as GameComponentView) } is GridPane<*> -> { //calculate position in grid - posStartCoord += parent.getChildPosition(this)!! - - //add layout from center bias - if (parent.layoutFromCenter) { - posStartCoord -= Coordinate(parent.width / 2, parent.height / 2) - } + posStartCoord += parent.getActualChildPosition(this)?: Coordinate() parent.findRollback(this) } @@ -175,7 +170,7 @@ internal class NodeBuilder { val dragDataObject = DragDataObject( this, - scene.componentsMap[this]!!, + checkNotNull(scene.componentsMap[this]), mouseStartCoord, posStartCoord, relativeParentRotation, @@ -200,17 +195,15 @@ internal class NodeBuilder { */ private fun GameComponentContainer<*>.findRollback(component: GameComponentView): (() -> Unit) { val index = observableComponents.indexOf(component) - val initialX = posX - val initialY = posY + val initialX = component.posX + val initialY = component.posY return { - posX = initialX - posY = initialY + component.posX = initialX + component.posY = initialY @Suppress("UNCHECKED_CAST") - (this as GameComponentContainer).add( - component, - min(observableComponents.size, index) - ) + (this as GameComponentContainer) + .add(component, min(observableComponents.size, index)) } } @@ -222,17 +215,19 @@ internal class NodeBuilder { iteratorElement.component == component } ?: return {} - val initialX = element.component!!.posX + if(element.component == null) + throw ConcurrentModificationException("Grid was modified while calculating drag drop rollback.") + + val initialX = element.component.posX val initialY = element.component.posY val initialColumnIndex = element.columnIndex val initialRowIndex = element.rowIndex return { - posX = initialX - posY = initialY + component.posX = initialX + component.posY = initialY @Suppress("UNCHECKED_CAST") - (parent as GridPane)[initialColumnIndex, initialRowIndex] = - this@findRollback as ComponentView + (this as GridPane)[initialColumnIndex, initialRowIndex] = component } } @@ -241,21 +236,21 @@ internal class NodeBuilder { */ private fun Pane<*>.findRollback(component: ComponentView): (() -> Unit) { val index = observableComponents.indexOf(component) - val initialX = posX - val initialY = posY + val initialX = component.posX + val initialY = component.posY return { - posX = initialX - posY = initialY + component.posX = initialX + component.posY = initialY @Suppress("UNCHECKED_CAST") - (parent as Pane) - .add(this as ComponentView, min(observableComponents.size, index)) + (this as Pane).add(this as ComponentView, min(observableComponents.size, index)) } } private fun DynamicComponentView.findRollbackOnRoot(scene: BoardGameScene): (() -> Unit) { val initialX = posX val initialY = posY + return { posX = initialX posY = initialY @@ -338,7 +333,7 @@ internal class NodeBuilder { cached: Set ) { children.clear() - (cached - components).forEach { scene.componentsMap.remove(it) } + (cached - components.toSet()).forEach { scene.componentsMap.remove(it) } components.forEach { if (it in cached) { children.add(scene.componentsMap[it]) @@ -348,4 +343,4 @@ internal class NodeBuilder { } } } -} \ No newline at end of file +} diff --git a/bgw-core/src/main/kotlin/tools/aqua/bgw/builder/UINodeBuilder.kt b/bgw-core/src/main/kotlin/tools/aqua/bgw/builder/UINodeBuilder.kt index 50a3df0d48..f65b6ce3fd 100644 --- a/bgw-core/src/main/kotlin/tools/aqua/bgw/builder/UINodeBuilder.kt +++ b/bgw-core/src/main/kotlin/tools/aqua/bgw/builder/UINodeBuilder.kt @@ -100,7 +100,7 @@ internal class UINodeBuilder { val node = javafx.scene.control.TextArea(textArea.textProperty.value) node.textProperty().bindTextProperty(textArea) - node.promptText = textArea.prompt + node.promptTextProperty().bindPromptProperty(textArea) return node } @@ -111,7 +111,7 @@ internal class UINodeBuilder { val node = javafx.scene.control.TextField(textField.textProperty.value) node.textProperty().bindTextProperty(textField) - node.promptText = textField.prompt + node.promptTextProperty().bindPromptProperty(textField) return node } @@ -306,6 +306,14 @@ internal class UINodeBuilder { //JavaFX -> Framework addListener { _, _, new -> labeled.text = new } } + + /** + * Binds [TextInputUIComponent.promptProperty]. + */ + private fun javafx.beans.property.StringProperty.bindPromptProperty(labeled: TextInputUIComponent) { + //Framework -> JavaFX + labeled.promptProperty.setGUIListenerAndInvoke(labeled.prompt) { _, nV -> value = nV } + } /** * Binds [LabeledUIComponent.alignmentProperty]. Framework -> JavaFX only. diff --git a/bgw-core/src/main/kotlin/tools/aqua/bgw/components/ComponentView.kt b/bgw-core/src/main/kotlin/tools/aqua/bgw/components/ComponentView.kt index 113f8cff30..41ff883775 100644 --- a/bgw-core/src/main/kotlin/tools/aqua/bgw/components/ComponentView.kt +++ b/bgw-core/src/main/kotlin/tools/aqua/bgw/components/ComponentView.kt @@ -112,7 +112,12 @@ abstract class ComponentView internal constructor( * @see posXProperty */ var actualPosX: Double - get() = posX + (width - actualWidth) / 2 + get() { + val parent = parent + val offset = if(parent != null && parent.layoutFromCenter) parent.actualWidth/2 else 0.0 + + return posX + (width - actualWidth) / 2 - offset + } private set(_) {} /** @@ -142,7 +147,12 @@ abstract class ComponentView internal constructor( * @see posYProperty */ var actualPosY: Double - get() = posY + (height - actualHeight) / 2 + get() { + val parent = parent + val offset = if(parent != null && parent.layoutFromCenter) parent.actualHeight/2 else 0.0 + + return posY + (height - actualHeight) / 2 - offset + } private set(_) {} /** diff --git a/bgw-core/src/main/kotlin/tools/aqua/bgw/components/layoutviews/GridPane.kt b/bgw-core/src/main/kotlin/tools/aqua/bgw/components/layoutviews/GridPane.kt index 158858b441..4915b0bba1 100644 --- a/bgw-core/src/main/kotlin/tools/aqua/bgw/components/layoutviews/GridPane.kt +++ b/bgw-core/src/main/kotlin/tools/aqua/bgw/components/layoutviews/GridPane.kt @@ -513,7 +513,7 @@ open class GridPane( */ override fun getChildPosition(child: ComponentView): Coordinate? = grid.filter { it.component == child }.map { - getRelativeChildOffset(it) + Coordinate(xCoord = child.posX, yCoord = child.posY) + getRelativeChildOffset(it) }.firstOrNull() /** @@ -526,16 +526,30 @@ open class GridPane( */ override fun getActualChildPosition(child: ComponentView): Coordinate? = grid.filter { it.component == child }.map { - getRelativeChildOffset(it) + Coordinate(xCoord = child.actualPosX, yCoord = child.actualPosY) + val offset = getRelativeChildOffset(it) + val parentOffsetX = if(child.layoutFromCenter) child.actualWidth/2 else 0.0 + val parentOffsetY = if(child.layoutFromCenter) child.actualHeight/2 else 0.0 + + Coordinate( + xCoord = offset.xCoord * scaleX - actualWidth/2 + parentOffsetX, + yCoord = offset.yCoord * scaleY - actualHeight/2 + parentOffsetY, + ) }.firstOrNull() private fun getRelativeChildOffset(it : GridIteratorElement): Coordinate { val cols = renderedColWidths.toMutableList().subList(0, it.columnIndex) val rows = renderedRowHeights.toMutableList().subList(0, it.rowIndex) + val cellOffsetX = (renderedColWidths[it.columnIndex] - (it.component?.actualWidth?:0.0)) + val cellOffsetY = (renderedRowHeights[it.rowIndex] - (it.component?.actualHeight?:0.0)) + + val cellAlignment = getCellCenterMode(columnIndex = it.columnIndex, rowIndex = it.rowIndex) + val cellAlignmentX = cellAlignment.horizontalAlignment.positionMultiplier + val cellAlignmentY = cellAlignment.verticalAlignment.positionMultiplier + return Coordinate( - xCoord = cols.sum() + cols.size * spacing, - yCoord = rows.sum() + rows.size * spacing + xCoord = cols.sum() + cols.size * spacing + cellOffsetX * cellAlignmentX, + yCoord = rows.sum() + rows.size * spacing + cellOffsetY * cellAlignmentY ) } diff --git a/bgw-core/src/main/kotlin/tools/aqua/bgw/components/uicomponents/TextArea.kt b/bgw-core/src/main/kotlin/tools/aqua/bgw/components/uicomponents/TextArea.kt index 27d59c4da9..484357dc00 100644 --- a/bgw-core/src/main/kotlin/tools/aqua/bgw/components/uicomponents/TextArea.kt +++ b/bgw-core/src/main/kotlin/tools/aqua/bgw/components/uicomponents/TextArea.kt @@ -34,10 +34,9 @@ import tools.aqua.bgw.util.Font * @param posY Vertical coordinate for this [TextArea]. Default: 0. * @param width Width for this [TextArea]. Default: [DEFAULT_TEXT_AREA_WIDTH]. * @param height Height for this [TextArea]. Default: [DEFAULT_TEXT_AREA_HEIGHT]. + * @param prompt Prompt for this [TextArea]. This gets displayed as a prompt to the user whenever the label is an empty + * string. Default: empty string. * @param text Initial text for this [TextArea]. Default: empty String. - * @param prompt Prompt for this [TextArea]. - * This gets displayed as a prompt to the user whenever the label is an empty string. - * Default: empty string. * * @see TextField */ @@ -47,12 +46,13 @@ open class TextArea( width: Number = DEFAULT_TEXT_AREA_WIDTH, height: Number = DEFAULT_TEXT_AREA_HEIGHT, text: String = "", - font: Font = Font(), - val prompt: String = "", + prompt: String = "", + font: Font = Font() ) : TextInputUIComponent( posX = posX, posY = posY, width = width, height = height, text = text, + prompt = prompt, font = font) \ No newline at end of file diff --git a/bgw-core/src/main/kotlin/tools/aqua/bgw/components/uicomponents/TextField.kt b/bgw-core/src/main/kotlin/tools/aqua/bgw/components/uicomponents/TextField.kt index 1262f1d3f1..2ab750f423 100644 --- a/bgw-core/src/main/kotlin/tools/aqua/bgw/components/uicomponents/TextField.kt +++ b/bgw-core/src/main/kotlin/tools/aqua/bgw/components/uicomponents/TextField.kt @@ -32,9 +32,9 @@ import tools.aqua.bgw.util.Font * @param posY Vertical coordinate for this [TextField]. Default: 0. * @param width Width for this [TextField]. Default: [DEFAULT_TEXT_FIELD_WIDTH]. * @param height Height for this [TextField]. Default: [DEFAULT_TEXT_FIELD_HEIGHT]. + * @param prompt Prompt for this [TextField]. This gets displayed as a prompt to the user whenever the label is an empty + * string. Default: empty string. * @param text Initial text for this [TextField]. Default: empty String. - * @param prompt Prompt for this [TextField]. This gets displayed as a prompt to the user whenever the label is an - * empty string. Default: empty string. * * @see TextArea */ @@ -44,12 +44,13 @@ open class TextField( width: Number = DEFAULT_TEXT_FIELD_WIDTH, height: Number = DEFAULT_TEXT_FIELD_HEIGHT, text: String = "", - font: Font = Font(), - val prompt: String = "", + prompt: String = "", + font: Font = Font() ) : TextInputUIComponent( posX = posX, posY = posY, width = width, height = height, text = text, + prompt = prompt, font = font) \ No newline at end of file diff --git a/bgw-core/src/main/kotlin/tools/aqua/bgw/components/uicomponents/TextInputUIComponent.kt b/bgw-core/src/main/kotlin/tools/aqua/bgw/components/uicomponents/TextInputUIComponent.kt index 849d10c98e..177e4b7c22 100644 --- a/bgw-core/src/main/kotlin/tools/aqua/bgw/components/uicomponents/TextInputUIComponent.kt +++ b/bgw-core/src/main/kotlin/tools/aqua/bgw/components/uicomponents/TextInputUIComponent.kt @@ -32,6 +32,7 @@ import tools.aqua.bgw.visual.Visual * @param width Width for this [TextInputUIComponent]. * @param height Height for this [TextInputUIComponent]. * @param text Text for this [TextInputUIComponent]. + * @param prompt Prompt for this [TextInputUIComponent]. * @param font Font to be used for the [text]. */ sealed class TextInputUIComponent( @@ -40,6 +41,7 @@ sealed class TextInputUIComponent( width: Number, height: Number, text: String, + prompt: String, font: Font, ) : UIComponent( posX = posX, @@ -66,4 +68,22 @@ sealed class TextInputUIComponent( set(value) { textProperty.value = value } + + /** + * [Property] for the prompt of this [TextInputUIComponent]. + * + * @see prompt + */ + val promptProperty: StringProperty = StringProperty(prompt) + + /** + * Prompt of this [TextInputUIComponent]. + * + * @see promptProperty + */ + var prompt: String + get() = promptProperty.value + set(value) { + promptProperty.value = value + } } diff --git a/bgw-core/src/main/kotlin/tools/aqua/bgw/core/Scene.kt b/bgw-core/src/main/kotlin/tools/aqua/bgw/core/Scene.kt index 6db53d3146..3793787e98 100644 --- a/bgw-core/src/main/kotlin/tools/aqua/bgw/core/Scene.kt +++ b/bgw-core/src/main/kotlin/tools/aqua/bgw/core/Scene.kt @@ -279,9 +279,9 @@ sealed class Scene(width: Number, height: Number, background: return listOf(rootNode) } - checkNotNull(node.parent) { "Encountered component $node that is not contained in a scene." } + val parent = checkNotNull(node.parent) { "Encountered component $node that is not contained in a scene." } - return mutableListOf(node) + findPathToChild(node.parent!!) + return mutableListOf(node) + findPathToChild(parent) } /** diff --git a/bgw-core/src/main/kotlin/tools/aqua/bgw/dialog/Dialog.kt b/bgw-core/src/main/kotlin/tools/aqua/bgw/dialog/Dialog.kt index 4e6d5f83b6..2b55087a46 100644 --- a/bgw-core/src/main/kotlin/tools/aqua/bgw/dialog/Dialog.kt +++ b/bgw-core/src/main/kotlin/tools/aqua/bgw/dialog/Dialog.kt @@ -37,7 +37,7 @@ class Dialog private constructor( val title: String, val header: String, val message: String, - val exception: Throwable? = null, + val exception: Throwable, vararg val buttons: ButtonType ) { /** @@ -53,7 +53,7 @@ class Dialog private constructor( * any [ButtonType]s. */ constructor(dialogType: DialogType, title: String, header: String, message: String, vararg buttons: ButtonType) : - this(dialogType, title, header, message, null, *buttons) { + this(dialogType, title, header, message, Exception(), *buttons) { require(dialogType != DialogType.EXCEPTION) { "To create an Exception dialog use exception dialog constructor." } diff --git a/bgw-core/src/main/kotlin/tools/aqua/bgw/util/CoordinatePlain.kt b/bgw-core/src/main/kotlin/tools/aqua/bgw/util/CoordinatePlain.kt index e3a51c1c4a..f52d43aafa 100644 --- a/bgw-core/src/main/kotlin/tools/aqua/bgw/util/CoordinatePlain.kt +++ b/bgw-core/src/main/kotlin/tools/aqua/bgw/util/CoordinatePlain.kt @@ -149,7 +149,11 @@ open class CoordinatePlain private constructor( bottomRight = bottomRight.rotated(angle, center), ) - override fun toString(): String { - return "CoordinatePlain(topLeft=$topLeft, topRight=$topRight, bottomLeft=$bottomLeft, bottomRight=$bottomRight, width=$width, height=$height)" - } + override fun toString(): String = + "CoordinatePlain(topLeft=$topLeft," + + " topRight=$topRight," + + " bottomLeft=$bottomLeft," + + " bottomRight=$bottomRight," + + " width=$width," + + " height=$height)" } \ No newline at end of file diff --git a/bgw-core/src/main/kotlin/tools/aqua/bgw/util/Stack.kt b/bgw-core/src/main/kotlin/tools/aqua/bgw/util/Stack.kt index e3b074f633..d732111ff8 100644 --- a/bgw-core/src/main/kotlin/tools/aqua/bgw/util/Stack.kt +++ b/bgw-core/src/main/kotlin/tools/aqua/bgw/util/Stack.kt @@ -28,6 +28,12 @@ package tools.aqua.bgw.util open class Stack(elements: Collection) { private val data: ArrayDeque = ArrayDeque(elements) + + /** + * Size of this [Stack]. + */ + val size: Int + get() = data.size /** * Creates a [Stack] with vararg initial elements. @@ -39,12 +45,6 @@ open class Stack(elements: Collection) { */ constructor() : this(listOf()) - /** - * Size of this [Stack]. - */ - val size: Int - get() = data.size - /** * Pops the topmost element in this [Stack]. * diff --git a/bgw-core/src/main/kotlin/tools/aqua/bgw/util/Trig.kt b/bgw-core/src/main/kotlin/tools/aqua/bgw/util/Trig.kt index 7e390f4916..1104a68f70 100644 --- a/bgw-core/src/main/kotlin/tools/aqua/bgw/util/Trig.kt +++ b/bgw-core/src/main/kotlin/tools/aqua/bgw/util/Trig.kt @@ -17,6 +17,9 @@ package tools.aqua.bgw.util +/** + * Utility class for trigonometry functions. + */ class Trig { companion object{ /** diff --git a/bgw-core/src/test/kotlin/util/coordinates/CoordinatePlainTest.kt b/bgw-core/src/test/kotlin/util/coordinates/CoordinatePlainTest.kt index 31dc1223b3..1b2769a597 100644 --- a/bgw-core/src/test/kotlin/util/coordinates/CoordinatePlainTest.kt +++ b/bgw-core/src/test/kotlin/util/coordinates/CoordinatePlainTest.kt @@ -191,11 +191,6 @@ class CoordinatePlainTest { val expectedLowY = plainPos - (plainWidth - plainHeight)/2 val expectedHighY = plainPos + plainHeight + (plainWidth - plainHeight)/2 - println(expectedLowX) - println(expectedHighX) - println(expectedLowY) - println(expectedHighY) - assertEquals(expectedHighX, rotated.topLeft.xCoord, DOUBLE_TOLERANCE) assertEquals(expectedLowY, rotated.topLeft.yCoord, DOUBLE_TOLERANCE)