Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
c8149fc
[RED] IDE-217: Add tests and boilerplate for resize/rotate visual pla…
harshsomankar123-tech Apr 6, 2026
7125602
[GREEN] IDE-217: Implement resize/rotate logic and visual overlay in …
harshsomankar123-tech Apr 6, 2026
1cddac9
Fix: SonarCloud reliability and Android Lint issues in Resize and rot…
harshsomankar123-tech Apr 7, 2026
2aabacc
Fix: detekt unused imports, missing braces and SonarCloud reliability…
harshsomankar123-tech Apr 7, 2026
85cdf69
Fix: All SonarCloud reliability and detekt issues in Visual Placement…
harshsomankar123-tech Apr 7, 2026
9d954a9
Refactor: Final SonarCloud reliability and maintainability fixes (IDE…
harshsomankar123-tech Apr 7, 2026
e881f71
Fix: Rename rotation field in VisualPlacementActivity to resolve Sona…
harshsomankar123-tech Apr 7, 2026
4fdde17
Fix bugs in ProjectActivity.kt, SpriteActivity.java, and VisualPlacem…
harshsomankar123-tech Apr 10, 2026
abfe6d3
Address PR #5189 review comments: fix angle normalization, touch base…
harshsomankar123-tech Apr 12, 2026
32292e8
Fix detekt bot warning and SonarQube code smell
harshsomankar123-tech Apr 12, 2026
bf8eeee
Fix touch interaction jumps and cleanup ProjectActivity constants
harshsomankar123-tech Apr 12, 2026
d0ae7e5
Remove unused local variable in VisualPlacementTouchListener
harshsomankar123-tech Apr 12, 2026
170bec1
Add regression unit tests for visual placement in ProjectActivity (ID…
harshsomankar123-tech Apr 14, 2026
57d5096
Fix visual placement rotation bug, add panning/snapping and reset but…
harshsomankar123-tech Apr 14, 2026
c59065d
Add Rotate 90° toolbar button and update tests (IDE-217)
harshsomankar123-tech Apr 14, 2026
a0fd2f4
Fix: Prevent resize from changing position and orientation (IDE-217)
harshsomankar123-tech Apr 16, 2026
e152f09
Refactor: Reduce cognitive complexity of onCreate in VisualPlacementA…
harshsomankar123-tech Apr 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 40 additions & 5 deletions catroid/src/main/java/org/catrobat/catroid/ui/ProjectActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import android.os.Bundle
import android.preference.PreferenceManager
import android.view.Menu
import android.view.MenuItem
import androidx.annotation.VisibleForTesting
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
Expand All @@ -41,9 +42,12 @@ import org.catrobat.catroid.common.Constants.DEFAULT_IMAGE_EXTENSION
import org.catrobat.catroid.common.Constants.TMP_IMAGE_FILE_NAME
import org.catrobat.catroid.common.FlavoredConstants
import org.catrobat.catroid.content.Project
import org.catrobat.catroid.content.Sprite
import org.catrobat.catroid.content.StartScript
import org.catrobat.catroid.content.bricks.Brick
import org.catrobat.catroid.content.bricks.PlaceAtBrick
import org.catrobat.catroid.content.bricks.PointInDirectionBrick
import org.catrobat.catroid.content.bricks.SetSizeToBrick
import org.catrobat.catroid.databinding.ActivityRecyclerBinding
import org.catrobat.catroid.databinding.DialogNewActorBinding
import org.catrobat.catroid.databinding.ProgressBarBinding
Expand Down Expand Up @@ -72,6 +76,7 @@ import org.catrobat.catroid.utils.ToastUtil
import org.catrobat.catroid.utils.Utils
import org.catrobat.catroid.utils.setVisibleOrGone
import org.catrobat.catroid.visualplacement.VisualPlacementActivity
import org.catrobat.catroid.content.Look.DEGREE_UI_OFFSET
import org.koin.android.ext.android.inject
import java.io.File

Expand All @@ -89,6 +94,32 @@ class ProjectActivity : BaseCastActivity() {
const val SPRITE_CAMERA = 3
const val SPRITE_OBJECT = 4
const val SPRITE_FROM_LOCAL = 5

const val DEFAULT_SCALE = 1.0f
const val PERCENTAGE_MULTIPLIER = 100.0

@JvmStatic
@VisibleForTesting
internal fun applyVisualPlacementBricks(
sprite: Sprite,
xCoordinate: Int,
yCoordinate: Int,
placementScale: Float,
placementRotation: Float
) {
val startScript = StartScript()
sprite.prependScript(startScript)
startScript.addBrick(PlaceAtBrick(xCoordinate, yCoordinate))
if (placementScale != DEFAULT_SCALE) {
val sizePercent = placementScale.toDouble() *
PERCENTAGE_MULTIPLIER
startScript.addBrick(SetSizeToBrick(sizePercent))
}
if (placementRotation != DEGREE_UI_OFFSET) {
val direction = placementRotation.toDouble()
startScript.addBrick(PointInDirectionBrick(direction))
}
}
}

private lateinit var binding: ActivityRecyclerBinding
Expand Down Expand Up @@ -258,11 +289,15 @@ class ProjectActivity : BaseCastActivity() {
extras.getInt(VisualPlacementActivity.X_COORDINATE_BUNDLE_ARGUMENT)
val yCoordinate =
extras.getInt(VisualPlacementActivity.Y_COORDINATE_BUNDLE_ARGUMENT)
val placeAtBrick = PlaceAtBrick(xCoordinate, yCoordinate)
val currentSprite = projectManager.currentSprite
val startScript = StartScript()
currentSprite.prependScript(startScript)
startScript.addBrick(placeAtBrick)
val placementScale =
extras.getFloat(VisualPlacementActivity.SCALE_BUNDLE_ARGUMENT, 1.0f)
val placementRotation =
extras.getFloat(VisualPlacementActivity.ROTATION_BUNDLE_ARGUMENT, DEGREE_UI_OFFSET)
val currentSprite = projectManager.currentSprite ?: return
applyVisualPlacementBricks(
currentSprite, xCoordinate, yCoordinate,
placementScale, placementRotation
)
}

SPRITE_FROM_LOCAL -> if (data != null && data.hasExtra(ProjectListActivity.IMPORT_LOCAL_INTENT)) {
Expand Down
27 changes: 24 additions & 3 deletions catroid/src/main/java/org/catrobat/catroid/ui/SpriteActivity.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Catroid: An on-device visual programming system for Android devices
* Copyright (C) 2010-2025 The Catrobat Team
* Copyright (C) 2010-2026 The Catrobat Team
* (<http://developer.catrobat.org/credits>)
*
* This program is free software: you can redistribute it and/or modify
Expand Down Expand Up @@ -96,6 +96,7 @@
import static org.catrobat.catroid.common.FlavoredConstants.CATROBAT_CONTENT_LOOKS_URL;
import static org.catrobat.catroid.common.FlavoredConstants.CATROBAT_CONTENT_SOUNDS_URL;
import static org.catrobat.catroid.common.SharedPreferenceKeys.INDEXING_VARIABLE_PREFERENCE_KEY;
import static org.catrobat.catroid.content.Look.DEGREE_UI_OFFSET;
import static org.catrobat.catroid.stage.TestResult.TEST_RESULT_MESSAGE;
import static org.catrobat.catroid.ui.SpriteActivityOnTabSelectedListenerKt.addTabLayout;
import static org.catrobat.catroid.ui.SpriteActivityOnTabSelectedListenerKt.getTabPositionInSpriteActivity;
Expand All @@ -104,6 +105,8 @@
import static org.catrobat.catroid.ui.SpriteActivityOnTabSelectedListenerKt.removeTabLayout;
import static org.catrobat.catroid.ui.WebViewActivity.MEDIA_FILE_PATH;
import static org.catrobat.catroid.visualplacement.VisualPlacementActivity.CHANGED_COORDINATES;
import static org.catrobat.catroid.visualplacement.VisualPlacementActivity.ROTATION_BUNDLE_ARGUMENT;
Comment thread
harshsomankar123-tech marked this conversation as resolved.
import static org.catrobat.catroid.visualplacement.VisualPlacementActivity.SCALE_BUNDLE_ARGUMENT;
import static org.catrobat.catroid.visualplacement.VisualPlacementActivity.X_COORDINATE_BUNDLE_ARGUMENT;
import static org.catrobat.catroid.visualplacement.VisualPlacementActivity.Y_COORDINATE_BUNDLE_ARGUMENT;

Expand Down Expand Up @@ -349,8 +352,9 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) {
}

if (resultCode != RESULT_OK) {
if (SettingsFragment.isCastSharedPreferenceEnabled(this)
&& projectManager.getCurrentProject().isCastProject()
Project project = projectManager.getCurrentProject();
if (project != null && SettingsFragment.isCastSharedPreferenceEnabled(this)
&& project.isCastProject()
&& !CastManager.getInstance().isConnected()) {

CastManager.getInstance().openDeviceSelectorOrDisconnectDialog(this);
Expand Down Expand Up @@ -430,6 +434,8 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) {

int xCoordinate = extras.getInt(X_COORDINATE_BUNDLE_ARGUMENT);
int yCoordinate = extras.getInt(Y_COORDINATE_BUNDLE_ARGUMENT);
float placementScale = extras.getFloat(SCALE_BUNDLE_ARGUMENT, 1.0f);
float placementRotation = extras.getFloat(ROTATION_BUNDLE_ARGUMENT, DEGREE_UI_OFFSET);
int brickHash = extras.getInt(EXTRA_BRICK_HASH);

Fragment fragment = getCurrentFragment();
Expand All @@ -448,6 +454,21 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) {
}
}

if (placementScale != 1.0f) {
Sprite sprite = projectManager.getCurrentSprite();
if (sprite != null && sprite.look != null) {
sprite.look.setScaleX(placementScale);
sprite.look.setScaleY(placementScale);
}
}

if (placementRotation != DEGREE_UI_OFFSET) {
Sprite rotSprite = projectManager.getCurrentSprite();
if (rotSprite != null && rotSprite.look != null) {
rotSprite.look.setMotionDirectionInUserInterfaceDimensionUnit(placementRotation);
}
}

setUndoMenuItemVisibility(extras.getBoolean(CHANGED_COORDINATES));

break;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/*
* Catroid: An on-device visual programming system for Android devices
* Copyright (C) 2010-2026 The Catrobat Team
* (<http://developer.catrobat.org/credits>)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* An additional term exception under section 7 of the GNU Affero
* General Public License, version 3, is available at
* http://developer.catrobat.org/license_additional_term
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package org.catrobat.catroid.visualplacement

import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.RectF
import android.view.View
import android.widget.ImageView
import androidx.core.content.ContextCompat
import androidx.core.graphics.toColorInt
import androidx.core.graphics.withRotation
import androidx.core.graphics.withTranslation
import org.catrobat.catroid.R

class BoundingBoxOverlay(context: Context) : View(context) {

private val boundingBoxPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = BOUNDING_BOX_COLOR
style = Paint.Style.STROKE
strokeWidth = BOUNDING_BOX_STROKE_WIDTH
}

private val cornerHandleDrawable = ContextCompat.getDrawable(context, R.drawable.ic_corner_handle)

private val handleSizePx: Int =
(HANDLE_SIZE_DP * context.resources.displayMetrics.density).toInt()

private val rect = RectF()

var trackedImageView: ImageView? = null

fun updateOverlay() {
invalidate()
}

override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)

val imageView = trackedImageView ?: return
val drawable = imageView.drawable ?: return

val imgWidth = drawable.intrinsicWidth.toFloat()
val imgHeight = drawable.intrinsicHeight.toFloat()

val scaleX = imageView.scaleX
val scaleY = imageView.scaleY

val scaledWidth = imgWidth * scaleX
val scaledHeight = imgHeight * scaleY

val viewCenterX = imageView.x + imageView.width / 2f
val viewCenterY = imageView.y + imageView.height / 2f

val left = viewCenterX - scaledWidth / 2f
val top = viewCenterY - scaledHeight / 2f
val right = viewCenterX + scaledWidth / 2f
val bottom = viewCenterY + scaledHeight / 2f

rect.left = left
rect.top = top
rect.right = right
rect.bottom = bottom

canvas.withRotation(imageView.rotation, viewCenterX, viewCenterY) {
drawRect(rect, boundingBoxPaint)

drawCornerHandle(this, left, top, ROTATION_0)
drawCornerHandle(this, right, top, ROTATION_90)
drawCornerHandle(this, right, bottom, ROTATION_180)
drawCornerHandle(this, left, bottom, ROTATION_270)
}
}

private fun drawCornerHandle(canvas: Canvas, cx: Float, cy: Float, rotationDegrees: Float) {
val handle = cornerHandleDrawable ?: return

canvas.withTranslation(cx, cy) {
withRotation(rotationDegrees) {
val halfSize = handleSizePx / 2
handle.setBounds(-halfSize, -halfSize, halfSize, halfSize)
handle.draw(this)
}
}
}

companion object {
private const val BOUNDING_BOX_STROKE_WIDTH = 3f
private val BOUNDING_BOX_COLOR = "#4FC3F7".toColorInt()
private const val HANDLE_SIZE_DP = 32
private const val ROTATION_0 = 0f
private const val ROTATION_90 = 90f
private const val ROTATION_180 = 180f
private const val ROTATION_270 = 270f
}
}
Loading
Loading