diff --git a/Paintroid/build.gradle b/Paintroid/build.gradle index 58cf97e97f..79c0c004e5 100644 --- a/Paintroid/build.gradle +++ b/Paintroid/build.gradle @@ -127,6 +127,7 @@ dependencies { implementation 'androidx.exifinterface:exifinterface:1.3.2' implementation 'com.esotericsoftware:kryo:5.1.1' implementation 'id.zelory:compressor:2.1.1' + androidTestImplementation project(path: ':Paintroid') implementation 'androidx.constraintlayout:constraintlayout:2.1.4' debugImplementation 'androidx.multidex:multidex:2.0.0' diff --git a/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/ZoomWindowIntegrationTest.kt b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/ZoomWindowIntegrationTest.kt new file mode 100644 index 0000000000..93dfcde9a4 --- /dev/null +++ b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/ZoomWindowIntegrationTest.kt @@ -0,0 +1,104 @@ +package org.catrobat.paintroid.test.espresso + +import android.os.Build +import android.widget.RelativeLayout +import androidx.test.espresso.Espresso.onView +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.rule.ActivityTestRule +import org.catrobat.paintroid.MainActivity +import org.catrobat.paintroid.test.espresso.util.wrappers.DrawingSurfaceInteraction.onDrawingSurfaceView +import org.catrobat.paintroid.test.espresso.util.wrappers.ToolBarViewInteraction +import org.catrobat.paintroid.tools.ToolType +import org.junit.Before +import org.junit.Rule +import org.junit.runner.RunWith +import org.catrobat.paintroid.R +import org.junit.Test +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers +import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility +import androidx.test.espresso.matcher.ViewMatchers.withId +import org.catrobat.paintroid.test.espresso.util.DrawingSurfaceLocationProvider +import org.catrobat.paintroid.test.espresso.util.UiInteractions.PressAndReleaseActions.tearDownPressAndRelease +import org.catrobat.paintroid.test.espresso.util.UiInteractions.PressAndReleaseActions.pressAction +import org.catrobat.paintroid.test.espresso.util.UiInteractions.PressAndReleaseActions.releaseAction +import org.catrobat.paintroid.test.espresso.util.wrappers.ZoomWindowInteraction.onZoomWindow +import org.junit.After + +@RunWith(AndroidJUnit4::class) +class ZoomWindowIntegrationTest { + + @get:Rule + val launchActivityRule = ActivityTestRule(MainActivity::class.java) + @Before + fun setUp() { + ToolBarViewInteraction.onToolBarView() + .performSelectTool(ToolType.BRUSH) + } + + @After + fun tearDown() { + tearDownPressAndRelease() + } + + @Test + fun windowAppearsWhenDrawingSurfaceIsTouched() { + onDrawingSurfaceView() + .perform(pressAction(DrawingSurfaceLocationProvider.MIDDLE)) + + onView(withId(R.id.pocketpaint_zoom_window)) + .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))) + + onDrawingSurfaceView() + .perform(releaseAction()) + } + + @Test + fun windowDisappearsWhenDrawingSurfaceIsPressedAndReleased() { + onDrawingSurfaceView() + .perform(pressAction(DrawingSurfaceLocationProvider.MIDDLE)) + + onDrawingSurfaceView() + .perform(releaseAction()) + + onView(withId(R.id.pocketpaint_zoom_window)) + .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE))) + } + + @Test + fun windowAppearsOnRightWhenClickedAtTheTopLeft() { + onDrawingSurfaceView() + .perform(pressAction(DrawingSurfaceLocationProvider.HALFWAY_TOP_LEFT)) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + onZoomWindow() + .checkAlignment(RelativeLayout.ALIGN_PARENT_RIGHT) + } else { + // Layout params rules for ALIGN_PARENT_LEFT and ALIGN_PARENT_RIGHT from + // https://developer.android.com/reference/android/widget/RelativeLayout.LayoutParams#getRules() + // for API level less than M + onZoomWindow() + .checkAlignmentBelowM(11) + } + + onDrawingSurfaceView() + .perform(releaseAction()) + } + + @Test + fun windowAppearsOnLeftWhenClickedAnywhere() { + onDrawingSurfaceView() + .perform(pressAction(DrawingSurfaceLocationProvider.MIDDLE)) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + onZoomWindow() + .checkAlignment(RelativeLayout.ALIGN_PARENT_LEFT) + } else { + onZoomWindow() + .checkAlignmentBelowM(9) + } + + onDrawingSurfaceView() + .perform(releaseAction()) + } +} diff --git a/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/util/UiInteractions.java b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/util/UiInteractions.java index cf69dfc43c..6532a0c982 100644 --- a/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/util/UiInteractions.java +++ b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/util/UiInteractions.java @@ -49,6 +49,7 @@ import static androidx.test.espresso.action.ViewActions.actionWithAssertions; import static androidx.test.espresso.matcher.ViewMatchers.assertThat; import static androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom; +import static androidx.test.espresso.matcher.ViewMatchers.isDisplayingAtLeast; import static androidx.test.espresso.matcher.ViewMatchers.isRoot; import static androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility; @@ -298,4 +299,72 @@ public Status sendTap(UiController uiController, float[] coordinates, float[] pr MotionEvent.BUTTON_PRIMARY); } } + + public static class PressAndReleaseActions { + + static MotionEvent motionEvent = null; + + public static PressAction pressAction(DrawingSurfaceLocationProvider coordinates) { + return new PressAction(coordinates); + } + + public static ReleaseAction releaseAction() { + return new ReleaseAction(); + } + + public static void tearDownPressAndRelease() { + motionEvent = null; + } + + public static class PressAction implements ViewAction { + + DrawingSurfaceLocationProvider coordinates; + + PressAction(DrawingSurfaceLocationProvider coords) { + this.coordinates = coords; + } + + @Override + public Matcher getConstraints() { + return isDisplayingAtLeast(90); + } + + @Override + public String getDescription() { + return "Press Action"; + } + + @Override + public void perform(UiController uiController, View view) { + if (motionEvent != null) { + throw new AssertionError("Only one view can be held at a time"); + } + float[] coords = coordinates.calculateCoordinates(view); + float[] precision = Press.FINGER.describePrecision(); + + motionEvent = MotionEvents.sendDown(uiController, coords, precision).down; + } + } + + public static class ReleaseAction implements ViewAction { + + @Override + public Matcher getConstraints() { + return isDisplayingAtLeast(90); + } + + @Override + public String getDescription() { + return "Release"; + } + + @Override + public void perform(UiController uiController, View view) { + if (motionEvent == null) { + throw new AssertionError("Only one view can be held at a time"); + } + MotionEvents.sendUp(uiController, motionEvent); + } + } + } } diff --git a/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/util/wrappers/ZoomWindowInteraction.java b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/util/wrappers/ZoomWindowInteraction.java new file mode 100644 index 0000000000..7552e8c524 --- /dev/null +++ b/Paintroid/src/androidTest/java/org/catrobat/paintroid/test/espresso/util/wrappers/ZoomWindowInteraction.java @@ -0,0 +1,71 @@ +package org.catrobat.paintroid.test.espresso.util.wrappers; + +import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.assertion.ViewAssertions.matches; +import static androidx.test.espresso.matcher.ViewMatchers.withId; + +import static org.catrobat.paintroid.test.espresso.util.MainActivityHelper.getMainActivityFromView; + +import android.view.View; +import android.widget.RelativeLayout; + +import org.catrobat.paintroid.MainActivity; +import org.catrobat.paintroid.R; +import org.hamcrest.Description; +import org.hamcrest.TypeSafeMatcher; + +public final class ZoomWindowInteraction extends CustomViewInteraction { + + private ZoomWindowInteraction() { + super(onView(withId(R.id.pocketpaint_zoom_window))); + } + + public static ZoomWindowInteraction onZoomWindow() { + return new ZoomWindowInteraction(); + } + + public ZoomWindowInteraction checkAlignment(final int verb) { + check(matches(new TypeSafeMatcher() { + @Override + protected boolean matchesSafely(View view) { + MainActivity activity = getMainActivityFromView(view); + RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) + activity.findViewById(R.id.pocketpaint_zoom_window_inner).getLayoutParams(); + + int rulesFromLayout = layoutParams.getRule(verb); + + return rulesFromLayout == -1; + } + + @Override + public void describeTo(Description description) { + String alignedAccordingTo = + (verb == RelativeLayout.ALIGN_PARENT_LEFT) ? "left" : "right"; + description.appendText("The zoom window is aligned to the " + alignedAccordingTo); + } + })); + return this; + } + + public ZoomWindowInteraction checkAlignmentBelowM(final int index) { + check(matches(new TypeSafeMatcher() { + @Override + protected boolean matchesSafely(View view) { + MainActivity activity = getMainActivityFromView(view); + RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) + activity.findViewById(R.id.pocketpaint_zoom_window_inner).getLayoutParams(); + + int[] rulesFromLayout = layoutParams.getRules(); + + return rulesFromLayout[index] == -1; + } + + @Override + public void describeTo(Description description) { + String alignedAccordingTo = index == 11 ? "right" : "left"; + description.appendText("The zoom window is aligned to " + alignedAccordingTo); + } + })); + return this; + } +} diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt b/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt index 6c835c68c2..0f70d93bae 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/MainActivity.kt @@ -102,6 +102,7 @@ import org.catrobat.paintroid.ui.viewholder.BottomNavigationViewHolder import org.catrobat.paintroid.ui.viewholder.DrawerLayoutViewHolder import org.catrobat.paintroid.ui.viewholder.LayerMenuViewHolder import org.catrobat.paintroid.ui.viewholder.TopBarViewHolder +import org.catrobat.paintroid.ui.zoomwindow.DefaultZoomWindowController import java.io.File import java.util.Locale @@ -128,6 +129,9 @@ class MainActivity : AppCompatActivity(), MainView, CommandListener { var idlingResource: CountingIdlingResource = CountingIdlingResource("MainIdleResource") + @VisibleForTesting + lateinit var zoomWindowController: DefaultZoomWindowController + lateinit var commandManager: CommandManager lateinit var toolPaint: ToolPaint lateinit var bottomNavigationViewHolder: BottomNavigationViewHolder @@ -371,6 +375,10 @@ class MainActivity : AppCompatActivity(), MainView, CommandListener { R.id.pocketpaint_options_about -> presenterMain.showAboutClicked() R.id.pocketpaint_share_image_button -> presenterMain.shareImageClicked() R.id.pocketpaint_options_feedback -> presenterMain.sendFeedback() + R.id.pocketpaint_zoom_window_settings -> + presenterMain.showZoomWindowSettingsClicked( + UserPreferences(getPreferences(MODE_PRIVATE)) + ) R.id.pocketpaint_advanced_settings -> presenterMain.showAdvancedSettingsClicked() android.R.id.home -> presenterMain.backToPocketCodeClicked() else -> return false @@ -443,6 +451,14 @@ class MainActivity : AppCompatActivity(), MainView, CommandListener { listener, CommandSerializationUtilities(this, commandManager, model) ) + zoomWindowController = DefaultZoomWindowController( + this, + layerModel, + workspace, + toolReference, + UserPreferences(getPreferences(MODE_PRIVATE)) + ) + model = MainActivityModel() defaultToolController = DefaultToolController( toolReference, toolOptionsViewController, @@ -515,7 +531,9 @@ class MainActivity : AppCompatActivity(), MainView, CommandListener { toolReference, idlingResource, supportFragmentManager, - toolOptionsViewController + toolOptionsViewController, + zoomWindowController, + UserPreferences(getPreferences(MODE_PRIVATE)) ) layerPresenter.setDrawingSurface(drawingSurface) appFragment.perspective = perspective diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/UserPreferences.kt b/Paintroid/src/main/java/org/catrobat/paintroid/UserPreferences.kt index 82e316b109..3465535971 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/UserPreferences.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/UserPreferences.kt @@ -21,8 +21,11 @@ package org.catrobat.paintroid import android.content.SharedPreferences import org.catrobat.paintroid.common.IMAGE_NUMBER_SHARED_PREFERENCES_TAG import org.catrobat.paintroid.common.SHOW_LIKE_US_DIALOG_SHARED_PREFERENCES_TAG +import org.catrobat.paintroid.common.ZOOM_WINDOW_ENABLED_SHARED_PREFERENCES_TAG +import org.catrobat.paintroid.common.ZOOM_WINDOW_ZOOM_PERCENTAGE_SHARED_PREFERENCES_TAG open class UserPreferences(var preferences: SharedPreferences) { + open val preferenceLikeUsDialogValue: Boolean get() = preferences.getBoolean(SHOW_LIKE_US_DIALOG_SHARED_PREFERENCES_TAG, false) open var preferenceImageNumber: Int @@ -33,6 +36,26 @@ open class UserPreferences(var preferences: SharedPreferences) { .putInt(IMAGE_NUMBER_SHARED_PREFERENCES_TAG, value) .apply() } + open var preferenceZoomWindowEnabled: Boolean + get() = preferences.getBoolean(ZOOM_WINDOW_ENABLED_SHARED_PREFERENCES_TAG, true) + set(value) { + preferences + .edit() + .putBoolean(ZOOM_WINDOW_ENABLED_SHARED_PREFERENCES_TAG, value) + .apply() + } + + open var preferenceZoomWindowZoomPercentage: Int + get() = preferences.getInt( + ZOOM_WINDOW_ZOOM_PERCENTAGE_SHARED_PREFERENCES_TAG, + initialZoomPercent + ) + set(value) { + preferences + .edit() + .putInt(ZOOM_WINDOW_ZOOM_PERCENTAGE_SHARED_PREFERENCES_TAG, value) + .apply() + } open fun setPreferenceLikeUsDialogValue() { preferences @@ -40,4 +63,8 @@ open class UserPreferences(var preferences: SharedPreferences) { .putBoolean(SHOW_LIKE_US_DIALOG_SHARED_PREFERENCES_TAG, true) .apply() } + + companion object { + const val initialZoomPercent: Int = 100 + } } diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/common/Constants.kt b/Paintroid/src/main/java/org/catrobat/paintroid/common/Constants.kt index 208c0bfb21..cb84a56746 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/common/Constants.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/common/Constants.kt @@ -29,6 +29,7 @@ const val ABOUT_DIALOG_FRAGMENT_TAG = "aboutdialogfragment" const val LIKE_US_DIALOG_FRAGMENT_TAG = "likeusdialogfragment" const val RATE_US_DIALOG_FRAGMENT_TAG = "rateusdialogfragment" const val FEEDBACK_DIALOG_FRAGMENT_TAG = "feedbackdialogfragment" +const val ZOOM_WINDOW_SETTINGS_DIALOG_FRAGMENT_TAG = "zoomwindowsettingsdialogfragment" const val ADVANCED_SETTINGS_DIALOG_FRAGMENT_TAG = "advancedsettingsdialogfragment" const val SAVE_DIALOG_FRAGMENT_TAG = "savedialogerror" const val LOAD_DIALOG_FRAGMENT_TAG = "loadbitmapdialogerror" @@ -43,6 +44,8 @@ const val CATROBAT_INFORMATION_DIALOG_TAG = "catrobatinformationdialogfragment" const val CATROID_MEDIA_GALLERY_FRAGMENT_TAG = "catroidmediagalleryfragment" const val PERMISSION_DIALOG_FRAGMENT_TAG = "permissiondialogfragment" const val SHOW_LIKE_US_DIALOG_SHARED_PREFERENCES_TAG = "showlikeusdialog" +const val ZOOM_WINDOW_ENABLED_SHARED_PREFERENCES_TAG = "zoomwindowenabled" +const val ZOOM_WINDOW_ZOOM_PERCENTAGE_SHARED_PREFERENCES_TAG = "zoomwindowzoompercentage" const val IMAGE_NUMBER_SHARED_PREFERENCES_TAG = "imagenumbertag" const val SCALE_IMAGE_FRAGMENT_TAG = "showscaleimagedialog" const val INDETERMINATE_PROGRESS_DIALOG_TAG = "indeterminateprogressdialogfragment" diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/contract/MainActivityContracts.kt b/Paintroid/src/main/java/org/catrobat/paintroid/contract/MainActivityContracts.kt index 0cc5eb66d7..43bb945d2e 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/contract/MainActivityContracts.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/contract/MainActivityContracts.kt @@ -27,6 +27,7 @@ import android.util.DisplayMetrics import android.view.Menu import androidx.annotation.ColorInt import androidx.annotation.StringRes +import org.catrobat.paintroid.UserPreferences import org.catrobat.paintroid.colorpicker.ColorHistory import org.catrobat.paintroid.common.MainActivityConstants.ActivityRequestCode import org.catrobat.paintroid.dialog.PermissionInfoDialog.PermissionType @@ -58,6 +59,8 @@ interface MainActivityContracts { fun showFeedbackDialog() + fun showZoomWindowSettingsDialog(sharedPreferences: UserPreferences) + fun showAdvancedSettingsDialog() fun showOverwriteDialog(permissionCode: Int, isExport: Boolean) @@ -215,6 +218,8 @@ interface MainActivityContracts { fun showAboutClicked() + fun showZoomWindowSettingsClicked(sharedPreferences: UserPreferences) + fun showAdvancedSettingsClicked() fun showRateUsDialog() diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/dialog/ZoomWindowSettingsDialog.kt b/Paintroid/src/main/java/org/catrobat/paintroid/dialog/ZoomWindowSettingsDialog.kt new file mode 100644 index 0000000000..32631bbf69 --- /dev/null +++ b/Paintroid/src/main/java/org/catrobat/paintroid/dialog/ZoomWindowSettingsDialog.kt @@ -0,0 +1,78 @@ +package org.catrobat.paintroid.dialog + +import android.annotation.SuppressLint +import android.app.Dialog +import android.content.DialogInterface +import android.os.Bundle +import android.view.View +import android.widget.TextView +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.widget.SwitchCompat +import com.google.android.material.slider.Slider +import org.catrobat.paintroid.R +import org.catrobat.paintroid.UserPreferences + +class ZoomWindowSettingsDialog( + private val sharedPreferences: UserPreferences +) : MainActivityDialogFragment() { + + private val initialEnabledValue = sharedPreferences.preferenceZoomWindowEnabled + private val initialPercentageValue = sharedPreferences.preferenceZoomWindowZoomPercentage + + private var enabled = sharedPreferences.preferenceZoomWindowEnabled + private var percentage = sharedPreferences.preferenceZoomWindowZoomPercentage + + @SuppressLint("SetTextI18n") + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + val enabledSwitch = view.findViewById(R.id.pocketpaint_zoom_window_enabled) + val slider = view.findViewById(R.id.pocketpaint_zoom_window_slider) + val sliderTextView = view.findViewById(R.id.pocketpaint_zoom_window_slider_progress) + + enabledSwitch.isChecked = initialEnabledValue + sliderTextView.text = "$initialPercentageValue%" + slider.value = initialPercentageValue.toFloat() + + enabledSwitch.setOnCheckedChangeListener { _, isChecked -> + enabled = isChecked + } + + slider.addOnChangeListener { _, value, _ -> + var percentageValue = value.toInt().toString() + sliderTextView.text = "$percentageValue%" + + percentage = value.toInt() + } + + slider.setLabelFormatter { value: Float -> + value.toInt().toString() + '%' + } + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val inflater = requireActivity().layoutInflater + val layout = inflater.inflate(R.layout.dialog_pocketpaint_zoomwindow_settings, null) + onViewCreated(layout, savedInstanceState) + + return AlertDialog.Builder(requireContext(), R.style.PocketPaintAlertDialog) + .setTitle(R.string.menu_zoom_settings) + .setView(layout) + .setPositiveButton(R.string.pocketpaint_ok) { _, _ -> + sharedPreferences.preferenceZoomWindowEnabled = enabled + sharedPreferences.preferenceZoomWindowZoomPercentage = percentage + dismiss() + } + .setNegativeButton(R.string.cancel_button_text) { _, _ -> + sharedPreferences.preferenceZoomWindowEnabled = initialEnabledValue + sharedPreferences.preferenceZoomWindowZoomPercentage = initialPercentageValue + dismiss() + } + .create() + } + + override fun onCancel(dialog: DialogInterface) { + sharedPreferences.preferenceZoomWindowEnabled = initialEnabledValue + sharedPreferences.preferenceZoomWindowZoomPercentage = initialPercentageValue + super.onCancel(dialog) + } +} diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/listener/DrawingSurfaceListener.kt b/Paintroid/src/main/java/org/catrobat/paintroid/listener/DrawingSurfaceListener.kt index 6beef74067..89ee8d1107 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/listener/DrawingSurfaceListener.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/listener/DrawingSurfaceListener.kt @@ -24,11 +24,13 @@ import android.os.Handler import android.view.MotionEvent import android.view.View import android.view.View.OnTouchListener +import org.catrobat.paintroid.UserPreferences import org.catrobat.paintroid.tools.Tool import org.catrobat.paintroid.tools.Tool.StateChange import org.catrobat.paintroid.tools.ToolType import org.catrobat.paintroid.tools.options.ToolOptionsViewController import org.catrobat.paintroid.ui.DrawingSurface +import org.catrobat.paintroid.ui.zoomwindow.ZoomWindowController import java.util.EnumSet import kotlin.collections.ArrayList import kotlin.collections.MutableList @@ -43,7 +45,7 @@ private const val JITTER_DISTANCE_THRESHOLD = 50f open class DrawingSurfaceListener( private val autoScrollTask: AutoScrollTask, private val callback: DrawingSurfaceListenerCallback, - private val displayDensity: Float + private val displayDensity: Float, ) : OnTouchListener { private var touchMode: TouchMode private var pointerDistance = 0f @@ -56,6 +58,9 @@ open class DrawingSurfaceListener( private val drawerEdgeSize: Int = (DRAWER_EDGE_SIZE * displayDensity + CONSTANT_1).toInt() private var autoScroll = true private var timerStartDraw = 0.toLong() + private lateinit var zoomController: ZoomWindowController + private var callZoomWindow: Boolean = true + private lateinit var sharedPreferences: UserPreferences private var recentTouchEventsData: MutableList = mutableListOf() @@ -103,6 +108,14 @@ open class DrawingSurfaceListener( autoScrollTask.setViewDimensions(view.width, view.height) } + fun setZoomController( + zoomWindowController: ZoomWindowController, + sharedPreferences: UserPreferences + ) { + zoomController = zoomWindowController + this.sharedPreferences = sharedPreferences + } + private fun handleActionMove(currentTool: Tool?, view: View, event: MotionEvent) { val xOld: Float val yOld: Float @@ -131,6 +144,9 @@ open class DrawingSurfaceListener( } currentTool.handleMove(canvasTouchPoint) } + if (callZoomWindow) { + handleZoomOnMove(currentTool) + } } else { disableAutoScroll() if (touchMode == TouchMode.DRAW) { @@ -149,6 +165,15 @@ open class DrawingSurfaceListener( if (xOld > 0 && xMidPoint != xOld || yOld > 0 && yMidPoint != yOld) { callback.translatePerspective(xMidPoint - xOld, yMidPoint - yOld) } + zoomController.dismissOnPinch() + } + } + + private fun handleZoomOnMove(currentTool: Tool) { + if (zoomController.checkCurrentTool(callback.getCurrentTool()) == 1) { + zoomController.onMove(currentTool.toolPositionCoordinates(canvasTouchPoint)) + } else { + zoomController.onMove(canvasTouchPoint) } } @@ -172,6 +197,18 @@ open class DrawingSurfaceListener( setEvenPointAndViewDimensionsForAutoScrollTask(view) autoScrollTask.start() } + if (sharedPreferences.preferenceZoomWindowEnabled) { + if (zoomController.checkCurrentTool(callback.getCurrentTool()) == 1) { + currentTool?.let { + zoomController.onMove(it.toolPositionCoordinates(canvasTouchPoint)) + } + } else { + zoomController.onMove(canvasTouchPoint) + } + callZoomWindow = true + } else { + callZoomWindow = false + } } MotionEvent.ACTION_MOVE -> handleActionMove(currentTool, view, event) MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { @@ -207,6 +244,8 @@ open class DrawingSurfaceListener( eventX = 0f eventY = 0f touchMode = TouchMode.DRAW + if (callZoomWindow) zoomController.dismiss() + callback.getCurrentTool()?.handToolMode() } } drawingSurface.refreshDrawingSurface() diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/presenter/MainActivityPresenter.kt b/Paintroid/src/main/java/org/catrobat/paintroid/presenter/MainActivityPresenter.kt index 474e3927d4..1041952418 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/presenter/MainActivityPresenter.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/presenter/MainActivityPresenter.kt @@ -279,6 +279,10 @@ open class MainActivityPresenter( navigator.showAboutDialog() } + override fun showZoomWindowSettingsClicked(sharedPreferences: UserPreferences) { + navigator.showZoomWindowSettingsDialog(sharedPreferences) + } + override fun showAdvancedSettingsClicked() { navigator.showAdvancedSettingsDialog() } diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/tools/Tool.kt b/Paintroid/src/main/java/org/catrobat/paintroid/tools/Tool.kt index 0ed20d5616..85f1f22aab 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/tools/Tool.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/tools/Tool.kt @@ -64,4 +64,6 @@ interface Tool { enum class StateChange { ALL, RESET_INTERNAL_STATE, NEW_IMAGE_LOADED, MOVE_CANCELED } + + fun toolPositionCoordinates(coordinate: PointF): PointF } diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/BrushTool.kt b/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/BrushTool.kt index 9ed5c52843..092e97881d 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/BrushTool.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/BrushTool.kt @@ -147,6 +147,10 @@ open class BrushTool( } } + override fun toolPositionCoordinates(coordinate: PointF): PointF = + // The tool coordinate is same as the touch coordinate + coordinate + override fun resetInternalState() { pathToDraw.rewind() pointArray.clear() diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/CursorTool.kt b/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/CursorTool.kt index a2e2dcc683..4a888fe5fc 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/CursorTool.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/CursorTool.kt @@ -381,4 +381,14 @@ open class CursorTool( pointArray.clear() } + + override fun toolPositionCoordinates(coordinate: PointF): PointF { + var finalCoordinates: PointF = PointF(0f, 0f) + previousEventCoordinate?.let { + val deltaX = coordinate.x - it.x + val deltaY = coordinate.y - it.y + finalCoordinates = calculateNewClampedToolPosition(deltaX, deltaY) + } + return finalCoordinates + } } diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/FillTool.kt b/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/FillTool.kt index 8cbc72b1e3..e26267dc71 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/FillTool.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/FillTool.kt @@ -87,6 +87,10 @@ class FillTool( return true } + override fun toolPositionCoordinates(coordinate: PointF): PointF = + // The tool coordinate is same as the touch coordinate + coordinate + public override fun resetInternalState() = Unit override val toolType: ToolType = ToolType.FILL diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/HandTool.kt b/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/HandTool.kt index 52a7dea5b7..b65d0fa73f 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/HandTool.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/HandTool.kt @@ -57,5 +57,7 @@ class HandTool( override fun handleUp(coordinate: PointF?): Boolean = true + override fun toolPositionCoordinates(coordinate: PointF): PointF = coordinate + override fun handToolMode(): Boolean = true } diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/ImportTool.kt b/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/ImportTool.kt index 6c933fba27..711d2a6c24 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/ImportTool.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/ImportTool.kt @@ -20,6 +20,7 @@ package org.catrobat.paintroid.tools.implementation import android.graphics.Bitmap import android.graphics.Canvas +import android.graphics.PointF import android.os.Bundle import androidx.test.espresso.idling.CountingIdlingResource import org.catrobat.paintroid.command.CommandManager @@ -48,6 +49,10 @@ class ImportTool( override val toolType: ToolType get() = ToolType.IMPORTPNG + override fun toolPositionCoordinates(coordinate: PointF): PointF = + // The tool coordinate is same as the touch coordinate + PointF(toolPosition.x - boxWidth / 2, toolPosition.y - boxHeight / 2) + init { rotationEnabled = true } diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/LineTool.kt b/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/LineTool.kt index c1d9ac34f1..ced89757d1 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/LineTool.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/LineTool.kt @@ -321,6 +321,10 @@ class LineTool( return true } + override fun toolPositionCoordinates(coordinate: PointF): PointF = + // The tool coordinate is same as the touch coordinate + coordinate + override fun resetInternalState() { initialEventCoordinate = null currentCoordinate = null diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/PipetteTool.kt b/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/PipetteTool.kt index 203ef386ca..26db01c8b0 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/PipetteTool.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/PipetteTool.kt @@ -58,6 +58,10 @@ class PipetteTool( override fun handleUp(coordinate: PointF?): Boolean = setColor(coordinate) + override fun toolPositionCoordinates(coordinate: PointF): PointF = + // The tool coordinate is same as the touch coordinate + coordinate + override fun resetInternalState() { updateSurfaceBitmap() } diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/ShapeTool.kt b/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/ShapeTool.kt index 30a9717866..a3e1626814 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/ShapeTool.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/ShapeTool.kt @@ -20,6 +20,7 @@ package org.catrobat.paintroid.tools.implementation import android.graphics.Canvas import android.graphics.Paint +import android.graphics.PointF import android.graphics.RectF import android.os.Bundle import androidx.test.espresso.idling.CountingIdlingResource @@ -67,6 +68,10 @@ class ShapeTool( override val toolType: ToolType get() = ToolType.SHAPE + override fun toolPositionCoordinates(coordinate: PointF): PointF = + // The tool coordinate is same as the touch coordinate + PointF(toolPosition.x - boxWidth / 2, toolPosition.y - boxHeight / 2) + init { rotationEnabled = true this.shapeToolOptionsView = shapeToolOptionsView diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/SmudgeTool.kt b/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/SmudgeTool.kt index 2644646a43..03fafba2a0 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/SmudgeTool.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/SmudgeTool.kt @@ -232,6 +232,10 @@ class SmudgeTool( } } + override fun toolPositionCoordinates(coordinate: PointF): PointF = + // The tool coordinate is same as the touch coordinate + coordinate + override fun draw(canvas: Canvas) { if (pointArray.isNotEmpty()) { val pointPath = pointArray.toMutableList() diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/SprayTool.kt b/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/SprayTool.kt index e0c6c7a535..ff5aed6acf 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/SprayTool.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/SprayTool.kt @@ -103,6 +103,10 @@ class SprayTool( return true } + override fun toolPositionCoordinates(coordinate: PointF): PointF = + // The tool coordinate is same as the touch coordinate + coordinate + override fun handleMove(coordinate: PointF?): Boolean { currentCoordinate = coordinate return true diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/StampTool.kt b/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/StampTool.kt index 258fda431c..354f608277 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/StampTool.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/StampTool.kt @@ -21,6 +21,7 @@ package org.catrobat.paintroid.tools.implementation import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.Color +import android.graphics.PointF import android.os.Bundle import androidx.test.espresso.idling.CountingIdlingResource import org.catrobat.paintroid.R @@ -60,6 +61,10 @@ class StampTool( override val toolType: ToolType get() = ToolType.STAMP + override fun toolPositionCoordinates(coordinate: PointF): PointF = + // The tool coordinate is same as the touch coordinate + PointF(toolPosition.x - boxWidth / 2, toolPosition.y - boxHeight / 2) + init { rotationEnabled = true this.stampToolOptionsView = stampToolOptionsView diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/TextTool.kt b/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/TextTool.kt index 5416205fe6..4fe6536858 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/TextTool.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/TextTool.kt @@ -116,6 +116,10 @@ class TextTool( override val toolType: ToolType get() = ToolType.TEXT + override fun toolPositionCoordinates(coordinate: PointF): PointF = + // The tool coordinate is same as the touch coordinate + PointF(toolPosition.x - boxWidth / 2, toolPosition.y - boxHeight / 2) + init { rotationEnabled = ROTATION_ENABLED resizePointsVisible = RESIZE_POINTS_VISIBLE diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/TransformTool.kt b/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/TransformTool.kt index 6f618f1229..cc3f3c5b65 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/TransformTool.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/tools/implementation/TransformTool.kt @@ -112,6 +112,10 @@ class TransformTool( override val toolType: ToolType get() = ToolType.TRANSFORM + override fun toolPositionCoordinates(coordinate: PointF): PointF = + // The tool coordinate is same as the touch coordinate + coordinate + init { rotationEnabled = ROTATION_ENABLED resizePointsVisible = RESIZE_POINTS_VISIBLE diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/ui/DrawingSurface.kt b/Paintroid/src/main/java/org/catrobat/paintroid/ui/DrawingSurface.kt index 307e79c826..23b06b7868 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/ui/DrawingSurface.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/ui/DrawingSurface.kt @@ -43,6 +43,7 @@ import androidx.core.content.ContextCompat import androidx.test.espresso.idling.CountingIdlingResource import androidx.fragment.app.FragmentManager import org.catrobat.paintroid.R +import org.catrobat.paintroid.UserPreferences import org.catrobat.paintroid.colorpicker.ColorPickerDialog import org.catrobat.paintroid.common.COLOR_PICKER_DIALOG_TAG import org.catrobat.paintroid.contract.LayerContracts @@ -54,6 +55,7 @@ import org.catrobat.paintroid.tools.Tool import org.catrobat.paintroid.tools.ToolReference import org.catrobat.paintroid.tools.ToolType import org.catrobat.paintroid.tools.options.ToolOptionsViewController +import org.catrobat.paintroid.ui.zoomwindow.ZoomWindowController open class DrawingSurface : SurfaceView, SurfaceHolder.Callback { private val canvasRect = Rect() @@ -71,6 +73,8 @@ open class DrawingSurface : SurfaceView, SurfaceHolder.Callback { private lateinit var toolOptionsViewController: ToolOptionsViewController private lateinit var fragmentManager: FragmentManager private lateinit var idlingResource: CountingIdlingResource + private lateinit var zoomController: ZoomWindowController + private lateinit var sharedPreferences: UserPreferences constructor(context: Context?, attrSet: AttributeSet?) : super(context, attrSet) @@ -125,7 +129,9 @@ open class DrawingSurface : SurfaceView, SurfaceHolder.Callback { toolReference: ToolReference, idlingResource: CountingIdlingResource, fragmentManager: FragmentManager, - toolOptionsViewController: ToolOptionsViewController + toolOptionsViewController: ToolOptionsViewController, + zoomController: ZoomWindowController, + sharedPreferences: UserPreferences ) { this.layerModel = layerModel this.perspective = perspective @@ -133,7 +139,9 @@ open class DrawingSurface : SurfaceView, SurfaceHolder.Callback { this.toolOptionsViewController = toolOptionsViewController this.idlingResource = idlingResource this.fragmentManager = fragmentManager - this.toolOptionsViewController = toolOptionsViewController + this.zoomController = zoomController + drawingSurfaceListener.setZoomController(zoomController, sharedPreferences) + this.sharedPreferences = sharedPreferences } @Synchronized @@ -164,7 +172,46 @@ open class DrawingSurface : SurfaceView, SurfaceHolder.Callback { } val tool = toolReference.tool - tool?.draw(surfaceViewCanvas) + + // Will create the zoom window only if the tool is a compatible tool + when (zoomController.checkCurrentTool(tool)) { + 0 -> { + // NON-COMPATIBLE TOOLS + tool?.draw(surfaceViewCanvas) + } + 1 -> { + // LINE TOOL OR CURSOR TOOL + // Does not return the contents of the current layer + // But only the new lines drawn + + val bitmapOfDrawingBoard = Bitmap.createBitmap( + layerModel.width, layerModel.height, Bitmap.Config.ARGB_8888) + + tool?.draw(surfaceViewCanvas) + + val canvas = Canvas(bitmapOfDrawingBoard) + tool?.draw(canvas) + + handler.post( + Runnable { + zoomController.getBitmap(bitmapOfDrawingBoard) + } + ) + } + 2 -> { + // OTHER COMPATIBLE TOOLS + val bitmapOfDrawingBoard = layerModel.currentLayer?.bitmap + surfaceViewCanvas.setBitmap(bitmapOfDrawingBoard) + + tool?.draw(surfaceViewCanvas) + + handler.post( + Runnable { + zoomController.getBitmap(bitmapOfDrawingBoard) + } + ) + } + } } } } diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/ui/MainActivityNavigator.kt b/Paintroid/src/main/java/org/catrobat/paintroid/ui/MainActivityNavigator.kt index 47d291ae09..3136f1cc98 100644 --- a/Paintroid/src/main/java/org/catrobat/paintroid/ui/MainActivityNavigator.kt +++ b/Paintroid/src/main/java/org/catrobat/paintroid/ui/MainActivityNavigator.kt @@ -37,6 +37,7 @@ import androidx.fragment.app.Fragment import org.catrobat.paintroid.FileIO import org.catrobat.paintroid.MainActivity import org.catrobat.paintroid.R +import org.catrobat.paintroid.UserPreferences import org.catrobat.paintroid.WelcomeActivity import org.catrobat.paintroid.colorpicker.ColorPickerDialog import org.catrobat.paintroid.colorpicker.OnColorPickedListener @@ -64,6 +65,7 @@ import org.catrobat.paintroid.common.SAVE_DIALOG_FRAGMENT_TAG import org.catrobat.paintroid.common.SAVE_INFORMATION_DIALOG_TAG import org.catrobat.paintroid.common.SAVE_QUESTION_FRAGMENT_TAG import org.catrobat.paintroid.common.SCALE_IMAGE_FRAGMENT_TAG +import org.catrobat.paintroid.common.ZOOM_WINDOW_SETTINGS_DIALOG_FRAGMENT_TAG import org.catrobat.paintroid.contract.MainActivityContracts import org.catrobat.paintroid.dialog.AboutDialog import org.catrobat.paintroid.dialog.AdvancedSettingsDialog @@ -86,6 +88,7 @@ import org.catrobat.paintroid.dialog.SaveBeforeLoadImageDialog import org.catrobat.paintroid.dialog.SaveBeforeNewImageDialog import org.catrobat.paintroid.dialog.SaveInformationDialog import org.catrobat.paintroid.dialog.ScaleImageOnLoadDialog +import org.catrobat.paintroid.dialog.ZoomWindowSettingsDialog import org.catrobat.paintroid.tools.ToolReference import org.catrobat.paintroid.tools.ToolType import org.catrobat.paintroid.ui.fragments.CatroidMediaGalleryFragment @@ -299,6 +302,14 @@ class MainActivityNavigator( ) } + override fun showZoomWindowSettingsDialog(sharedPreferences: UserPreferences) { + val zoomWindowSettingsDialog = ZoomWindowSettingsDialog(sharedPreferences) + zoomWindowSettingsDialog.show( + mainActivity.supportFragmentManager, + ZOOM_WINDOW_SETTINGS_DIALOG_FRAGMENT_TAG + ) + } + override fun showAdvancedSettingsDialog() { val advancedSettingsDialog = AdvancedSettingsDialog() advancedSettingsDialog.show( diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/ui/zoomwindow/DefaultZoomWindowController.kt b/Paintroid/src/main/java/org/catrobat/paintroid/ui/zoomwindow/DefaultZoomWindowController.kt new file mode 100644 index 0000000000..35a2a118ca --- /dev/null +++ b/Paintroid/src/main/java/org/catrobat/paintroid/ui/zoomwindow/DefaultZoomWindowController.kt @@ -0,0 +1,259 @@ +package org.catrobat.paintroid.ui.zoomwindow + +import android.graphics.Bitmap +import android.graphics.Color +import android.graphics.Paint +import android.graphics.Rect +import android.graphics.PorterDuffXfermode +import android.graphics.PorterDuff +import android.graphics.BitmapFactory +import android.graphics.BitmapShader +import android.graphics.Canvas +import android.graphics.Shader +import android.graphics.Matrix +import android.graphics.PointF +import android.graphics.RectF +import android.view.View +import android.widget.ImageView +import android.widget.RelativeLayout +import org.catrobat.paintroid.MainActivity +import org.catrobat.paintroid.R +import org.catrobat.paintroid.UserPreferences +import org.catrobat.paintroid.contract.LayerContracts +import org.catrobat.paintroid.tools.Tool +import org.catrobat.paintroid.tools.ToolReference +import org.catrobat.paintroid.tools.ToolType +import org.catrobat.paintroid.tools.Workspace +import kotlin.math.roundToInt + +class DefaultZoomWindowController + (val activity: MainActivity, + val layerModel: LayerContracts.Model, + val workspace: Workspace, + val toolReference: ToolReference, + val sharedPreferences: UserPreferences) : + ZoomWindowController { + + private val canvasRect = Rect() + private val checkeredPattern = Paint() + private val framePaint = Paint() + + // Getting the dimensions of the zoom window + private val windowSideDimen = + activity.resources.getDimensionPixelSize(R.dimen.pocketpaint_zoom_window_height) + + // CHEQUERED + private val chequeredBackgroundBitmap = + Bitmap.createBitmap(layerModel.width, layerModel.height, Bitmap.Config.ARGB_8888) + + // GREY BACKGROUND + private val greyBackgroundBitmap = + Bitmap.createBitmap( + layerModel.width + windowSideDimen, + layerModel.height + windowSideDimen, + Bitmap.Config.ARGB_8888 + ) + + private val backgroundBitmap = + Bitmap.createBitmap( + layerModel.width + windowSideDimen, + layerModel.height + windowSideDimen, + Bitmap.Config.ARGB_8888 + ) + + init { + framePaint.color = Color.BLACK + framePaint.style = Paint.Style.STROKE + framePaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC) + val checkerboard = + BitmapFactory.decodeResource(activity.resources, R.drawable.pocketpaint_checkeredbg) + val shader = BitmapShader(checkerboard, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT) + checkeredPattern.shader = shader + checkeredPattern.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC) + + val backgroundCanvas: Canvas? = chequeredBackgroundBitmap?.let { Canvas(it) } + + canvasRect.set(0, 0, layerModel.width, layerModel.height) + + backgroundCanvas?.drawRect(canvasRect, checkeredPattern) + backgroundCanvas?.drawRect(canvasRect, framePaint) + + val greyBackgroundCanvas = Canvas(greyBackgroundBitmap) + greyBackgroundCanvas.drawColor( + activity.resources.getColor(R.color.pocketpaint_main_drawing_surface_background) + ) + + val canvasBackground = Canvas(backgroundBitmap) + + canvasBackground.drawBitmap(greyBackgroundBitmap, Matrix(), null) + canvasBackground.drawBitmap( + chequeredBackgroundBitmap, windowSideDimen / 2f, windowSideDimen / 2f, null) + } + + private val zoomWindow: RelativeLayout = + activity.findViewById(R.id.pocketpaint_zoom_window) + private val zoomWindowImage: ImageView = + activity.findViewById(R.id.pocketpaint_zoom_window_image) + private var coordinates: PointF? = null + + override fun show(coordinates: PointF) { +// Check if the tool is a compatible tool + if (checkCurrentTool(toolReference.tool) != 0 && + sharedPreferences.preferenceZoomWindowEnabled && + isPointOnCanvas(coordinates.x, coordinates.y)) { + if (shouldBeInTheRight(coordinates = coordinates)) { + setLayoutAlignment(right = true) + } else { + setLayoutAlignment(right = false) + } + zoomWindow.visibility = View.VISIBLE + zoomWindowImage.setImageBitmap(cropBitmap(workspace.bitmapOfAllLayers, coordinates)) + } + } + + override fun dismiss() { + zoomWindow.visibility = View.GONE + } + + override fun dismissOnPinch() { + zoomWindow.visibility = View.GONE + } + + override fun onMove(coordinates: PointF) { + if (shouldBeInTheRight(coordinates = coordinates)) { + setLayoutAlignment(right = true) + } else { + setLayoutAlignment(right = false) + } + if (isPointOnCanvas(coordinates.x, coordinates.y)) { + if (checkCurrentTool(toolReference.tool) != 0 && sharedPreferences.preferenceZoomWindowEnabled) { + if (zoomWindow.visibility == View.GONE) { + zoomWindow.visibility = View.VISIBLE + } + this.coordinates = coordinates + } + } else { + dismiss() + } + } + + override fun getBitmap(bitmap: Bitmap?) { + zoomWindowImage.setImageBitmap(coordinates?.let { cropBitmap(bitmap, it) }) + } + + private fun isPointOnCanvas(pointX: Float, pointY: Float): Boolean = + pointX > 0 && pointX < layerModel.width && pointY > 0 && pointY < layerModel.height + + private fun shouldBeInTheRight(coordinates: PointF): Boolean { + if (coordinates.x < layerModel.width / 2 && coordinates.y < layerModel.height / 2) { + return true + } + return false + } + + private fun setLayoutAlignment(right: Boolean) { + val params: RelativeLayout.LayoutParams = + zoomWindowImage.layoutParams as RelativeLayout.LayoutParams + if (right) { + params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT) + params.removeRule(RelativeLayout.ALIGN_PARENT_LEFT) + } else { + params.addRule(RelativeLayout.ALIGN_PARENT_LEFT) + params.removeRule(RelativeLayout.ALIGN_PARENT_RIGHT) + } + zoomWindowImage.layoutParams = params + } + + private fun cropBitmap(bitmap: Bitmap?, coordinates: PointF): Bitmap? { + + val bitmapWithBackground: Bitmap? = mergeBackground(bitmap) + + val startX: Int = coordinates.x.roundToInt() + windowSideDimen / 2 - getSizeOfZoomWindow() / 2 + val startY: Int = coordinates.y.roundToInt() + windowSideDimen / 2 - getSizeOfZoomWindow() / 2 + + val croppedBitmap: Bitmap? = + Bitmap.createBitmap(getSizeOfZoomWindow(), getSizeOfZoomWindow(), Bitmap.Config.ARGB_8888) + + val canvas: Canvas? = croppedBitmap?.let { Canvas(it) } + + val paint = Paint() + paint.isAntiAlias = true + + val rect = Rect(0, 0, getSizeOfZoomWindow(), getSizeOfZoomWindow()) + val rectF = RectF(rect) + + canvas?.drawOval(rectF, paint) + + paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN) + + bitmapWithBackground?.let { + canvas?.drawBitmap(it, + Rect(startX, startY, startX + getSizeOfZoomWindow(), startY + getSizeOfZoomWindow()), + rect, + paint + ) } + + return croppedBitmap + } + + private fun mergeBackground(bitmap: Bitmap?): Bitmap? { + + // Adding the extra width and height for the grey background + val bmOverlay = + Bitmap.createBitmap( + layerModel.width + windowSideDimen, + layerModel.height + windowSideDimen, + Bitmap.Config.ARGB_8888 + ) + val canvas = Canvas(bmOverlay) + + canvas.drawBitmap(backgroundBitmap, Matrix(), null) + + // Add the current layer if the tool is line or cursor tool + if (checkCurrentTool(toolReference.tool) == 1) { + layerModel.currentLayer?.bitmap?.let { + canvas.drawBitmap(it, windowSideDimen / 2f, windowSideDimen / 2f, null) + } + } + + bitmap?.let { canvas.drawBitmap(it, windowSideDimen / 2f, windowSideDimen / 2f, null) } + + return bmOverlay + } + + private fun getSizeOfZoomWindow(): Int { + val zoomIndex = (sharedPreferences.preferenceZoomWindowZoomPercentage - initialZoomValue) / zoomPercentStepValue + return windowSideDimen - zoomIndex * zoomFactor + } + + override fun checkCurrentTool(tool: Tool?): Int = + if ( + tool?.toolType?.name.equals(ToolType.HAND.name) || + tool?.toolType?.name.equals(ToolType.FILL.name) || + tool?.toolType?.name.equals(ToolType.TRANSFORM.name) + ) { + // NON-COMPATIBLE + 0 + } else if ( + tool?.toolType?.name.equals(ToolType.LINE.name) || + tool?.toolType?.name.equals(ToolType.CURSOR.name) || + tool?.toolType?.name.equals(ToolType.TEXT.name) + ) { + 1 + } else if ( + tool?.toolType?.name.equals(ToolType.SHAPE.name) || + tool?.toolType?.name.equals(ToolType.IMPORTPNG.name) || + tool?.toolType?.name.equals(ToolType.STAMP.name) + ) { + 1 + } else { + // COMPATIBLE + 2 + } + + companion object { + const val zoomFactor: Int = 25 + const val initialZoomValue: Int = 100 + const val zoomPercentStepValue = 50 + } +} diff --git a/Paintroid/src/main/java/org/catrobat/paintroid/ui/zoomwindow/ZoomWindowController.kt b/Paintroid/src/main/java/org/catrobat/paintroid/ui/zoomwindow/ZoomWindowController.kt new file mode 100644 index 0000000000..4fa075f685 --- /dev/null +++ b/Paintroid/src/main/java/org/catrobat/paintroid/ui/zoomwindow/ZoomWindowController.kt @@ -0,0 +1,19 @@ +package org.catrobat.paintroid.ui.zoomwindow + +import android.graphics.Bitmap +import android.graphics.PointF +import org.catrobat.paintroid.tools.Tool + +interface ZoomWindowController { + fun show(coordinates: PointF) + + fun dismiss() + + fun dismissOnPinch() + + fun onMove(coordinates: PointF) + + fun getBitmap(bitmap: Bitmap?) + + fun checkCurrentTool(tool: Tool?): Int +} diff --git a/Paintroid/src/main/res/drawable/pocketpaint_zoom_window_shape.xml b/Paintroid/src/main/res/drawable/pocketpaint_zoom_window_shape.xml new file mode 100644 index 0000000000..e2b2e6be10 --- /dev/null +++ b/Paintroid/src/main/res/drawable/pocketpaint_zoom_window_shape.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Paintroid/src/main/res/layout/activity_pocketpaint_main.xml b/Paintroid/src/main/res/layout/activity_pocketpaint_main.xml index 405154b8f8..34e1c6312b 100644 --- a/Paintroid/src/main/res/layout/activity_pocketpaint_main.xml +++ b/Paintroid/src/main/res/layout/activity_pocketpaint_main.xml @@ -52,6 +52,13 @@ android:layout_above="@id/pocketpaint_main_bottom_bar" android:layout_below="@+id/pocketpaint_layout_top_bar"/> + + + + + + + + + + + + + + + + + diff --git a/Paintroid/src/main/res/layout/pocketpaint_layout_zoom_window.xml b/Paintroid/src/main/res/layout/pocketpaint_layout_zoom_window.xml new file mode 100644 index 0000000000..7c349c43bf --- /dev/null +++ b/Paintroid/src/main/res/layout/pocketpaint_layout_zoom_window.xml @@ -0,0 +1,19 @@ + + + + + + \ No newline at end of file diff --git a/Paintroid/src/main/res/menu/menu_pocketpaint_more_options.xml b/Paintroid/src/main/res/menu/menu_pocketpaint_more_options.xml index f7aeafe64b..67934c43cc 100644 --- a/Paintroid/src/main/res/menu/menu_pocketpaint_more_options.xml +++ b/Paintroid/src/main/res/menu/menu_pocketpaint_more_options.xml @@ -46,6 +46,9 @@ + diff --git a/Paintroid/src/main/res/values/dimens.xml b/Paintroid/src/main/res/values/dimens.xml index 5429d16521..b46bf456ab 100644 --- a/Paintroid/src/main/res/values/dimens.xml +++ b/Paintroid/src/main/res/values/dimens.xml @@ -29,4 +29,8 @@ 100dp 50dp 25dp + + + 80dp + 80dp diff --git a/Paintroid/src/main/res/values/string.xml b/Paintroid/src/main/res/values/string.xml index d7be03e3db..f3ad908896 100644 --- a/Paintroid/src/main/res/values/string.xml +++ b/Paintroid/src/main/res/values/string.xml @@ -97,6 +97,7 @@ Export Feedback Advanced settings + Zoom Window Settings Image saved to\n Image saved Copy saved to\n @@ -164,6 +165,11 @@ Antialiasing Smoothing + Enabled + 100 + 300 + 100 + Quality: jpg @@ -239,4 +245,6 @@ Image is too big to load The image is too big to load. Tap OK to scale down the image automatically. + Used to display a zoomed in part of the drawing surface +