Implement radial dial.

Co-authored-by: Alan Evans <alan@signal.org>
This commit is contained in:
Alex Hart 2021-09-17 13:09:13 -03:00 committed by GitHub
parent ce2c2002c6
commit 7bcc338a49
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 491 additions and 14 deletions

View file

@ -95,6 +95,9 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
private int imageMaxWidth;
private final ThrottledDebouncer deleteFadeDebouncer = new ThrottledDebouncer(500);
private float initialDialImageDegrees;
private float initialDialScale;
private float minDialScaleDown;
public static class Data {
private final Bundle bundle;
@ -133,7 +136,6 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
private boolean hasMadeAnEditThisSession;
private boolean wasInTrashHitZone;
public static ImageEditorFragment newInstanceForAvatarCapture(@NonNull Uri imageUri) {
ImageEditorFragment fragment = newInstance(imageUri);
fragment.requireArguments().putString(KEY_MODE, Mode.AVATAR_CAPTURE.code);
@ -422,6 +424,8 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
.setVisible(mode == ImageEditorHudV2.Mode.DELETE)
.persist();
updateHudDialRotation();
switch (mode) {
case CROP: {
imageEditorView.getModel().startCrop();
@ -561,6 +565,7 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
@Override
public void onClearAll() {
imageEditorView.getModel().clearUndoStack();
updateHudDialRotation();
}
@Override
@ -586,6 +591,7 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
public void onUndo() {
imageEditorView.getModel().undo();
imageEditorHud.setBlurFacesToggleEnabled(imageEditorView.getModel().hasFaceRenderer());
updateHudDialRotation();
}
@Override
@ -641,6 +647,32 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
controller.onDoneEditing();
}
@Override
public void onDialRotationGestureStarted() {
float localScaleX = imageEditorView.getModel().getMainImage().getLocalScaleX();
minDialScaleDown = initialDialScale / localScaleX;
imageEditorView.getModel().pushUndoPoint();
imageEditorView.getModel().updateUndoRedoAvailabilityState();
initialDialImageDegrees = (float) Math.toDegrees(imageEditorView.getModel().getMainImage().getLocalRotationAngle());
}
@Override
public void onDialRotationGestureFinished() {
imageEditorView.getModel().getMainImage().commitEditorMatrix();
imageEditorView.getModel().postEdit(true);
imageEditorView.invalidate();
}
@Override
public void onDialRotationChanged(float degrees) {
imageEditorView.setMainImageEditorMatrixRotation(degrees - initialDialImageDegrees, minDialScaleDown);
}
private void updateHudDialRotation() {
imageEditorHud.setDialRotation(getRotationDegreesRounded(imageEditorView.getModel().getMainImage()));
initialDialScale = imageEditorView.getModel().getMainImage().getLocalScaleX();
}
private ResizeAnimation resizeAnimation;
private void scaleViewPortForDrawing(int orientation) {
@ -738,6 +770,7 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
private void onDrawingChanged(boolean stillTouching, boolean isUserEdit) {
if (isUserEdit) {
hasMadeAnEditThisSession = true;
}
}
@ -832,10 +865,18 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
}
};
public float getRotationDegreesRounded(@Nullable EditorElement editorElement) {
if (editorElement == null) {
return 0f;
}
return Math.round(Math.toDegrees(editorElement.getLocalRotationAngle()));
}
private final ImageEditorView.DragListener dragListener = new ImageEditorView.DragListener() {
@Override
public void onDragStarted(@Nullable EditorElement editorElement) {
if (imageEditorHud.getMode() == ImageEditorHudV2.Mode.CROP) {
updateHudDialRotation();
return;
}
@ -855,6 +896,7 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
@Override
public void onDragMoved(@Nullable EditorElement editorElement, boolean isInTrashHitZone) {
if (imageEditorHud.getMode() == ImageEditorHudV2.Mode.CROP || editorElement == null) {
updateHudDialRotation();
return;
}
@ -882,6 +924,7 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
wasInTrashHitZone = false;
imageEditorHud.animate().alpha(1f);
if (imageEditorHud.getMode() == ImageEditorHudV2.Mode.CROP) {
updateHudDialRotation();
return;
}
@ -961,6 +1004,7 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
if (editorElement != null && editorElement.getRenderer() instanceof SelectableRenderer) {
((SelectableRenderer) editorElement.getRenderer()).onSelected(selected);
}
imageEditorView.getModel().setSelected(selected ? editorElement : null);
}
private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) {

View file

@ -65,16 +65,18 @@ class ImageEditorHudV2 @JvmOverloads constructor(
private val bottomGuideline: Guideline = findViewById(R.id.image_editor_bottom_guide)
private val brushPreview: BrushWidthPreviewView = findViewById(R.id.image_editor_hud_brush_preview)
private val textStyleToggle: ImageView = findViewById(R.id.image_editor_hud_text_style_button)
private val rotationDial: RotationDialView = findViewById(R.id.image_editor_hud_crop_rotation_dial)
private val selectableSet: Set<View> = setOf(drawButton, textButton, stickerButton, blurButton)
private val undoTools: Set<View> = setOf(undoButton, clearAllButton)
private val drawTools: Set<View> = setOf(brushToggle, drawSeekBar, widthSeekBar)
private val blurTools: Set<View> = setOf(blurToggleContainer, blurHelpText, widthSeekBar)
private val cropTools: Set<View> = setOf(rotationDial)
private val drawButtonRow: Set<View> = setOf(cancelButton, doneButton, drawButton, textButton, stickerButton, blurButton)
private val cropButtonRow: Set<View> = setOf(cancelButton, doneButton, cropRotateButton, cropFlipButton, cropAspectLockButton)
private val allModeTools: Set<View> = drawTools + blurTools + drawButtonRow + cropButtonRow + textStyleToggle
private val allModeTools: Set<View> = drawTools + blurTools + drawButtonRow + cropButtonRow + textStyleToggle + cropTools
private val viewsToSlide: Set<View> = drawButtonRow + cropButtonRow
@ -150,6 +152,24 @@ class ImageEditorHudV2 @JvmOverloads constructor(
blurToggle.setOnCheckedChangeListener { _, enabled -> listener?.onBlurFacesToggled(enabled) }
setupWidthSeekBar()
rotationDial.listener = object : RotationDialView.Listener {
override fun onDegreeChanged(degrees: Float) {
listener?.onDialRotationChanged(degrees)
}
override fun onGestureStart() {
listener?.onDialRotationGestureStarted()
}
override fun onGestureEnd() {
listener?.onDialRotationGestureFinished()
}
}
}
fun setDialRotation(degrees: Float) {
rotationDial.setDegrees(degrees)
}
fun setBottomOfImageEditorView(bottom: Int) {
@ -326,7 +346,7 @@ class ImageEditorHudV2 @JvmOverloads constructor(
private fun presentModeCrop() {
animateModeChange(
inSet = cropButtonRow - if (isAvatarEdit) setOf(cropAspectLockButton) else setOf(),
inSet = cropTools + cropButtonRow - if (isAvatarEdit) setOf(cropAspectLockButton) else setOf(),
outSet = allModeTools
)
animateInUndoTools()
@ -523,6 +543,9 @@ class ImageEditorHudV2 @JvmOverloads constructor(
fun onRotate90AntiClockwise()
fun onCropAspectLock()
fun onTextStyleToggle()
fun onDialRotationGestureStarted()
fun onDialRotationChanged(degrees: Float)
fun onDialRotationGestureFinished()
val isCropAspectLocked: Boolean
fun onRequestFullScreen(fullScreen: Boolean, hideKeyboard: Boolean)

View file

@ -0,0 +1,297 @@
package org.thoughtcrime.securesms.scribbles
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Rect
import android.graphics.RectF
import android.graphics.Typeface
import android.util.AttributeSet
import android.view.GestureDetector
import android.view.HapticFeedbackConstants
import android.view.MotionEvent
import android.view.View
import androidx.annotation.ColorInt
import androidx.annotation.Dimension
import androidx.annotation.Px
import org.thoughtcrime.securesms.util.ViewUtil
import kotlin.math.abs
import kotlin.math.ceil
import kotlin.math.floor
import kotlin.math.roundToInt
class RotationDialView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private val canvasBounds = Rect()
private val centerMostIndicatorRect = RectF()
private val indicatorRect = RectF()
private val dimensions = Dimensions()
private var snapDegrees: Float = 0f
private var degrees: Float = 0f
private var isInGesture: Boolean = false
private val gestureDetector: GestureDetector = GestureDetector(context, GestureListener())
var listener: Listener? = null
private val textPaint = Paint().apply {
isAntiAlias = true
textSize = ViewUtil.spToPx(15f).toFloat()
typeface = Typeface.DEFAULT
color = Colors.textColor
style = Paint.Style.FILL
textAlign = Paint.Align.CENTER
}
private val angleIndicatorPaint = Paint().apply {
isAntiAlias = true
color = Color.WHITE
style = Paint.Style.FILL
}
fun setDegrees(degrees: Float) {
if (degrees != this.degrees) {
this.degrees = degrees
this.snapDegrees = calculateSnapDegrees()
if (isInGesture) {
listener?.onDegreeChanged(snapDegrees)
}
invalidate()
}
}
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent): Boolean {
if (event.actionIndex != 0) return false
isInGesture = gestureDetector.onTouchEvent(event)
when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> listener?.onGestureStart()
MotionEvent.ACTION_UP -> listener?.onGestureEnd()
}
return isInGesture
}
override fun onDraw(canvas: Canvas) {
if (isInEditMode) {
canvas.drawColor(Color.BLACK)
}
canvas.getClipBounds(canvasBounds)
val dialDegrees = getDialDegrees(snapDegrees)
val bottom = canvasBounds.bottom
val approximateCenterDegree = dialDegrees.roundToInt()
var currentDegree = approximateCenterDegree
val fractionalOffset = dialDegrees - approximateCenterDegree
val dialOffset = dimensions.spaceBetweenAngleIndicators * fractionalOffset
val centerX = width / 2f
centerMostIndicatorRect.set(
centerX - dimensions.angleIndicatorWidth / 2f,
bottom.toFloat() - dimensions.majorAngleIndicatorHeight,
centerX + dimensions.angleIndicatorWidth / 2f,
bottom.toFloat()
)
centerMostIndicatorRect.offset(-dialOffset, 0f)
indicatorRect.set(centerMostIndicatorRect)
angleIndicatorPaint.color = Colors.colorForOtherDegree(currentDegree)
indicatorRect.top = bottom.toFloat() - dimensions.getHeightForDegree(currentDegree)
canvas.drawRect(indicatorRect, angleIndicatorPaint)
indicatorRect.offset(dimensions.spaceBetweenAngleIndicators.toFloat(), 0f)
currentDegree += 1
while (indicatorRect.left < width && currentDegree <= ceil(MAX_DEGREES)) {
angleIndicatorPaint.color = Colors.colorForOtherDegree(currentDegree)
indicatorRect.top = bottom.toFloat() - dimensions.getHeightForDegree(currentDegree)
canvas.drawRect(indicatorRect, angleIndicatorPaint)
indicatorRect.offset(dimensions.spaceBetweenAngleIndicators.toFloat(), 0f)
currentDegree += 1
}
currentDegree = approximateCenterDegree
indicatorRect.set(centerMostIndicatorRect)
indicatorRect.offset(-dimensions.spaceBetweenAngleIndicators.toFloat(), 0f)
currentDegree -= 1
while (indicatorRect.left >= 0 && currentDegree >= floor(MIN_DEGRESS)) {
angleIndicatorPaint.color = Colors.colorForOtherDegree(currentDegree)
indicatorRect.top = bottom.toFloat() - dimensions.getHeightForDegree(currentDegree)
canvas.drawRect(indicatorRect, angleIndicatorPaint)
indicatorRect.offset(-dimensions.spaceBetweenAngleIndicators.toFloat(), 0f)
currentDegree -= 1
}
centerMostIndicatorRect.offset(dialOffset, 0f)
angleIndicatorPaint.color = Colors.colorForCenterDegree(approximateCenterDegree)
canvas.drawRect(centerMostIndicatorRect, angleIndicatorPaint)
drawText(canvas)
}
private fun drawText(canvas: Canvas) {
val approximateDegrees = getDialDegrees(snapDegrees).roundToInt()
canvas.drawText(
"$approximateDegrees",
width / 2f,
canvasBounds.bottom - textPaint.descent() - dimensions.majorAngleIndicatorHeight - dimensions.textPaddingBottom,
textPaint
)
}
private fun getDialDegrees(degrees: Float): Float {
val alpha: Float = degrees % 360f
if (alpha % 90 == 0f) {
return 0f
}
val beta: Float = floor(alpha / 90f)
val offset: Float = alpha - beta * 90f
return if (offset > 45f) {
offset - 90f
} else {
offset
}
}
private fun calculateSnapDegrees(): Float {
return if (isInGesture) {
val dialDegrees = getDialDegrees(degrees)
if (dialDegrees.roundToInt() == 0) {
degrees - dialDegrees
} else {
degrees
}
} else {
degrees
}
}
private inner class GestureListener : GestureDetector.SimpleOnGestureListener() {
override fun onDown(e: MotionEvent?): Boolean {
return true
}
override fun onScroll(e1: MotionEvent?, e2: MotionEvent?, distanceX: Float, distanceY: Float): Boolean {
val degreeIncrement: Float = distanceX / dimensions.spaceBetweenAngleIndicators
val prevDialDegrees = getDialDegrees(degrees)
val newDialDegrees = getDialDegrees(degrees + degreeIncrement)
val offEndOfMax = prevDialDegrees >= MAX_DEGREES / 2f && newDialDegrees <= MIN_DEGRESS / 2f
val offEndOfMin = newDialDegrees >= MAX_DEGREES / 2f && prevDialDegrees <= MIN_DEGRESS / 2f
if (prevDialDegrees.roundToInt() != newDialDegrees.roundToInt() && isHapticFeedbackEnabled) {
performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
}
when {
offEndOfMax -> {
val newIncrement = MAX_DEGREES - prevDialDegrees
setDegrees(degrees + newIncrement)
}
offEndOfMin -> {
val newIncrement = MAX_DEGREES - abs(prevDialDegrees)
setDegrees(degrees - newIncrement)
}
else -> {
setDegrees(degrees + degreeIncrement)
}
}
return true
}
}
private class Dimensions {
@Px
val spaceBetweenAngleIndicators: Int = ViewUtil.dpToPx(Dimensions.spaceBetweenAngleIndicators)
@Px
val angleIndicatorWidth: Int = ViewUtil.dpToPx(Dimensions.angleIndicatorWidth)
@Px
val minorAngleIndicatorHeight: Int = ViewUtil.dpToPx(Dimensions.minorAngleIndicatorHeight)
@Px
val majorAngleIndicatorHeight: Int = ViewUtil.dpToPx(Dimensions.majorAngleIndicatorHeight)
@Px
val textPaddingBottom: Int = ViewUtil.dpToPx(Dimensions.textPaddingBottom)
fun getHeightForDegree(degree: Int): Int {
return if (degree == 0) {
majorAngleIndicatorHeight
} else {
minorAngleIndicatorHeight
}
}
companion object {
@Dimension(unit = Dimension.DP)
private val spaceBetweenAngleIndicators: Int = 12
@Dimension(unit = Dimension.DP)
private val angleIndicatorWidth: Int = 1
@Dimension(unit = Dimension.DP)
private val minorAngleIndicatorHeight: Int = 12
@Dimension(unit = Dimension.DP)
private val majorAngleIndicatorHeight: Int = 32
@Dimension(unit = Dimension.DP)
private val textPaddingBottom: Int = 8
}
}
private object Colors {
@ColorInt
val textColor: Int = Color.WHITE
@ColorInt
val majorAngleIndicatorColor: Int = 0xFF62E87A.toInt()
@ColorInt
val modFiveIndicatorColor: Int = Color.WHITE
@ColorInt
val minorAngleIndicatorColor: Int = 0x80FFFFFF.toInt()
fun colorForCenterDegree(degree: Int) = if (degree == 0) modFiveIndicatorColor else majorAngleIndicatorColor
fun colorForOtherDegree(degree: Int): Int {
return when {
degree % 5 == 0 -> modFiveIndicatorColor
else -> minorAngleIndicatorColor
}
}
}
companion object {
private const val MAX_DEGREES: Float = 44.99999f
private const val MIN_DEGRESS: Float = -44.99999f
}
interface Listener {
fun onDegreeChanged(degrees: Float)
fun onGestureStart()
fun onGestureEnd()
}
}

View file

@ -331,6 +331,16 @@
tools:translationY="0dp"
tools:visibility="visible" />
<org.thoughtcrime.securesms.scribbles.RotationDialView
android:id="@+id/image_editor_hud_crop_rotation_dial"
android:layout_width="match_parent"
android:layout_height="72dp"
android:alpha="0"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/image_editor_hud_top_of_button_bar_spacing"
tools:alpha="1"
tools:visibility="visible" />
<!-- endregion -->
<!-- region blur stuff -->

View file

@ -427,6 +427,11 @@ public final class ImageEditorView extends FrameLayout {
this.mode = mode;
}
public void setMainImageEditorMatrixRotation(float angle, float minScaleDown) {
model.setMainImageEditorMatrixRotation(angle, minScaleDown);
invalidate();
}
public void startDrawing(float thickness, @NonNull Paint.Cap cap, boolean blur) {
this.thickness = thickness;
this.cap = cap;

View file

@ -0,0 +1,37 @@
package org.signal.imageeditor.core;
import android.graphics.Matrix;
import androidx.annotation.NonNull;
public final class MatrixUtils {
private static final ThreadLocal<float[]> tempMatrixValues = new ThreadLocal<>();
protected static @NonNull float[] getTempMatrixValues() {
float[] floats = tempMatrixValues.get();
if(floats == null) {
floats = new float[9];
tempMatrixValues.set(floats);
}
return floats;
}
/**
* Extracts the angle from a matrix in radians.
*/
public static float getRotationAngle(@NonNull Matrix matrix) {
float[] matrixValues = getTempMatrixValues();
matrix.getValues(matrixValues);
return (float) -Math.atan2(matrixValues[Matrix.MSKEW_X], matrixValues[Matrix.MSCALE_X]);
}
/** Gets the scale on the X axis */
public static float getScaleX(@NonNull Matrix matrix) {
float[] matrixValues = getTempMatrixValues();
matrix.getValues(matrixValues);
float scaleX = matrixValues[Matrix.MSCALE_X];
float skewX = matrixValues[Matrix.MSKEW_X];
return (float) Math.sqrt(scaleX * scaleX + skewX * skewX);
}
}

View file

@ -7,6 +7,7 @@ import android.os.Parcelable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.signal.imageeditor.core.MatrixUtils;
import org.signal.imageeditor.core.Renderer;
import org.signal.imageeditor.core.RendererContext;
@ -299,6 +300,14 @@ public final class EditorElement implements Parcelable {
children.clear();
}
public float getLocalRotationAngle() {
return MatrixUtils.getRotationAngle(localMatrix);
}
public float getLocalScaleX() {
return MatrixUtils.getScaleX(localMatrix);
}
public interface PerElementFunction {
void apply(EditorElement element);
}

View file

@ -221,6 +221,12 @@ final class EditorElementHierarchy {
selectedElement = null;
}
void updateSelectionThumbsForElement(@NonNull EditorElement element, @Nullable Matrix overlayMappingMatrix) {
if (element == selectedElement) {
setOrUpdateSelectionThumbsForElement(element, overlayMappingMatrix);
}
}
void setOrUpdateSelectionThumbsForElement(@NonNull EditorElement element, @Nullable Matrix overlayMappingMatrix) {
if (selectedElement != element) {
removeAllSelectionArtifacts();
@ -433,7 +439,7 @@ final class EditorElementHierarchy {
return dst;
}
void flipRotate(int degrees, int scaleX, int scaleY, @NonNull RectF visibleViewPort, @Nullable Runnable invalidate) {
void flipRotate(float degrees, int scaleX, int scaleY, @NonNull RectF visibleViewPort, @Nullable Runnable invalidate) {
Matrix newLocal = new Matrix(flipRotate.getLocalMatrix());
if (degrees != 0) {
newLocal.postRotate(degrees);

View file

@ -76,6 +76,11 @@ public final class EditorModel implements Parcelable, RendererContext.Ready {
}
}
public void updateSelectionThumbsIfSelected(@NonNull EditorElement editorElement) {
Matrix overlayMappingMatrix = findRelativeMatrix(editorElement, editorElementHierarchy.getOverlay());
editorElementHierarchy.updateSelectionThumbsForElement(editorElement, overlayMappingMatrix);
}
public void setSelectionVisible(boolean visible) {
editorElementHierarchy.getSelection()
.getFlags()
@ -145,6 +150,51 @@ public final class EditorModel implements Parcelable, RendererContext.Ready {
updateUndoRedoAvailableState(getActiveUndoRedoStacks(isCropping()));
}
/** Keeps the image within the crop bounds as it rotates */
public void setMainImageEditorMatrixRotation(float angle, float minScaleDown) {
setEditorMatrixToRotationMatrixAboutParentsOrigin(editorElementHierarchy.getMainImage(), angle);
scaleMainImageEditorMatrixToFitInsideCropBounds(minScaleDown, 2f);
}
private void scaleMainImageEditorMatrixToFitInsideCropBounds(float minScaleDown, float maxScaleUp) {
EditorElement mainImage = editorElementHierarchy.getMainImage();
Matrix mainImageLocalBackup = new Matrix(mainImage.getLocalMatrix());
Matrix mainImageEditorBackup = new Matrix(mainImage.getEditorMatrix());
mainImage.commitEditorMatrix();
Matrix combinedLocal = new Matrix(mainImage.getLocalMatrix());
Matrix newLocal = Bisect.bisectToTest(mainImage,
minScaleDown,
maxScaleUp,
this::cropIsWithinMainImageBounds,
(matrix, scale) -> matrix.preScale(scale, scale));
Matrix invertLocal = new Matrix();
if (newLocal != null && combinedLocal.invert(invertLocal)) {
invertLocal.preConcat(newLocal); // L^-1 (L * Scale) -> Scale
mainImageEditorBackup.preConcat(invertLocal); // add the scale to editor matrix to keep this image within crop
}
mainImage.getLocalMatrix().set(mainImageLocalBackup);
mainImage.getEditorMatrix().set(mainImageEditorBackup);
}
/**
* Sets the editor matrix for the element to a rotation of the degrees but does so that we are rotating around the
* parents elements origin.
*/
private void setEditorMatrixToRotationMatrixAboutParentsOrigin(@NonNull EditorElement element, float degrees) {
Matrix localMatrix = element.getLocalMatrix();
Matrix editorMatrix = element.getEditorMatrix();
localMatrix.invert(editorMatrix);
editorMatrix.preRotate(degrees);
editorMatrix.preConcat(localMatrix);
// Editor Matrix is then: Local^-1 * Rotate(degrees) * Local
// So you end up with this overall for the element: Local * Local^-1 * Rotate(degrees) * Local
// Meaning the rotate applies after existing effects of the local matrix
// Where as simply setting the editor matrix rotate gives this: Local * Rotate(degrees)
// which rotates around local origin first
}
/**
* Renders tree with the following matrix:
* <p>
@ -233,6 +283,10 @@ public final class EditorModel implements Parcelable, RendererContext.Ready {
getActiveUndoRedoStacks(cropping).pushState(editorElementHierarchy.getRoot());
}
public void updateUndoRedoAvailabilityState() {
updateUndoRedoAvailableState(getActiveUndoRedoStacks(isCropping()));
}
public void clearUndoStack() {
EditorElement root = editorElementHierarchy.getRoot();
EditorElement original = root;
@ -598,7 +652,7 @@ public final class EditorModel implements Parcelable, RendererContext.Ready {
*/
public void moving(@NonNull EditorElement editorElement) {
if (!isCropping()) {
setSelected(editorElement);
updateSelectionThumbsIfSelected(editorElement);
return;
}
@ -902,10 +956,6 @@ public final class EditorModel implements Parcelable, RendererContext.Ready {
return null;
}
public void rotate90clockwise() {
flipRotate(90, 1, 1);
}
public void rotate90anticlockwise() {
flipRotate(-90, 1, 1);
}
@ -914,11 +964,7 @@ public final class EditorModel implements Parcelable, RendererContext.Ready {
flipRotate(0, -1, 1);
}
public void flipVertical() {
flipRotate(0, 1, -1);
}
private void flipRotate(int degrees, int scaleX, int scaleY) {
private void flipRotate(float degrees, int scaleX, int scaleY) {
pushUndoPoint();
editorElementHierarchy.flipRotate(degrees, scaleX, scaleY, visibleViewPort, invalidate);
updateUndoRedoAvailableState(getActiveUndoRedoStacks(isCropping()));