diff --git a/app/src/main/java/org/thoughtcrime/securesms/imageeditor/ImageEditorView.java b/app/src/main/java/org/thoughtcrime/securesms/imageeditor/ImageEditorView.java index 95108d9092..31db75d8a6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/imageeditor/ImageEditorView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/imageeditor/ImageEditorView.java @@ -118,6 +118,7 @@ public final class ImageEditorView extends FrameLayout { } public void startTextEditing(@NonNull EditorElement editorElement) { + getModel().addFade(); if (editorElement.getRenderer() instanceof MultiLineTextRenderer) { editText.setCurrentTextEditorElement(editorElement); } @@ -133,6 +134,7 @@ public final class ImageEditorView extends FrameLayout { public void doneTextEditing() { getModel().zoomOut(); + getModel().removeFade(); if (editText.getCurrentTextEntity() != null) { editText.setCurrentTextEditorElement(null); editText.hideKeyboard(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/imageeditor/model/EditorElement.java b/app/src/main/java/org/thoughtcrime/securesms/imageeditor/model/EditorElement.java index c620243ba2..49f0069dd7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/imageeditor/model/EditorElement.java +++ b/app/src/main/java/org/thoughtcrime/securesms/imageeditor/model/EditorElement.java @@ -227,7 +227,7 @@ public final class EditorElement implements Parcelable { fromElement.animateFadeOut(invalidate); } - private void animateFadeOut(@Nullable Runnable invalidate) { + void animateFadeOut(@Nullable Runnable invalidate) { alphaAnimation = AlphaAnimation.animate(1, 0, invalidate); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/imageeditor/model/EditorElementHierarchy.java b/app/src/main/java/org/thoughtcrime/securesms/imageeditor/model/EditorElementHierarchy.java index 2c7cb59359..b363087e09 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/imageeditor/model/EditorElementHierarchy.java +++ b/app/src/main/java/org/thoughtcrime/securesms/imageeditor/model/EditorElementHierarchy.java @@ -11,6 +11,7 @@ import androidx.annotation.Nullable; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.imageeditor.Bounds; import org.thoughtcrime.securesms.imageeditor.renderers.CropAreaRenderer; +import org.thoughtcrime.securesms.imageeditor.renderers.FillRenderer; import org.thoughtcrime.securesms.imageeditor.renderers.InverseFillRenderer; import org.thoughtcrime.securesms.imageeditor.renderers.OvalGuideRenderer; @@ -32,6 +33,7 @@ import org.thoughtcrime.securesms.imageeditor.renderers.OvalGuideRenderer; * | | | |- cropEditorElement - user crop, not always square, but upright, the area of the view * | | | | | All children do not move/scale or rotate. * | | | | |- blackout + * | | | | |- fade * | | | | |- thumbs * | | | | | |- Center left thumb * | | | | | |- Center right thumb @@ -68,6 +70,7 @@ final class EditorElementHierarchy { private final EditorElement imageCrop; private final EditorElement cropEditorElement; private final EditorElement blackout; + private final EditorElement fade; private final EditorElement thumbs; private EditorElementHierarchy(@NonNull EditorElement root) { @@ -80,6 +83,7 @@ final class EditorElementHierarchy { this.cropEditorElement = this.imageCrop.getChild(0); this.blackout = this.cropEditorElement.getChild(0); this.thumbs = this.cropEditorElement.getChild(1); + this.fade = this.cropEditorElement.getChild(2); } private enum CropStyle { @@ -129,6 +133,14 @@ final class EditorElementHierarchy { imageCrop.addElement(cropEditorElement); + EditorElement fade = new EditorElement(new FillRenderer(0x66000000), EditorModel.Z_FADE); + fade.getFlags() + .setSelectable(false) + .setEditable(false) + .setVisible(false) + .persist(); + cropEditorElement.addElement(fade); + EditorElement blackout = new EditorElement(new InverseFillRenderer(0xff000000)); blackout.getFlags() @@ -223,6 +235,22 @@ final class EditorElementHierarchy { return flipRotate; } + void addFade(@NonNull Runnable invalidate) { + fade.getFlags() + .setVisible(true) + .persist(); + + invalidate.run(); + } + + void removeFade(@NonNull Runnable invalidate) { + fade.getFlags() + .setVisible(false) + .persist(); + + invalidate.run(); + } + /** * @param scaleIn Use 1 for no scale in, use less than 1 and it will zoom the image out * so user can see more of the surrounding image while cropping. diff --git a/app/src/main/java/org/thoughtcrime/securesms/imageeditor/model/EditorModel.java b/app/src/main/java/org/thoughtcrime/securesms/imageeditor/model/EditorModel.java index ba7b0b6563..eb2486deae 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/imageeditor/model/EditorModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/imageeditor/model/EditorModel.java @@ -39,7 +39,8 @@ public final class EditorModel implements Parcelable, RendererContext.Ready { public static final int Z_MASK = -1; public static final int Z_DRAWING = 0; public static final int Z_STICKERS = 0; - public static final int Z_TEXT = 1; + public static final int Z_FADE = 1; + public static final int Z_TEXT = 2; private static final Runnable NULL_RUNNABLE = () -> { }; @@ -311,6 +312,14 @@ public final class EditorModel implements Parcelable, RendererContext.Ready { return result; } + public void addFade() { + editorElementHierarchy.addFade(invalidate); + } + + public void removeFade() { + editorElementHierarchy.removeFade(invalidate); + } + public void startCrop() { float scaleIn = editingPurpose == EditingPurpose.WALLPAPER ? 1 : 0.8f; diff --git a/app/src/main/java/org/thoughtcrime/securesms/imageeditor/renderers/FillRenderer.java b/app/src/main/java/org/thoughtcrime/securesms/imageeditor/renderers/FillRenderer.java new file mode 100644 index 0000000000..47f058025b --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/imageeditor/renderers/FillRenderer.java @@ -0,0 +1,76 @@ +package org.thoughtcrime.securesms.imageeditor.renderers; + +import android.graphics.Path; +import android.graphics.RectF; +import android.os.Parcel; + +import androidx.annotation.ColorInt; +import androidx.annotation.NonNull; + +import org.thoughtcrime.securesms.imageeditor.Bounds; +import org.thoughtcrime.securesms.imageeditor.Renderer; +import org.thoughtcrime.securesms.imageeditor.RendererContext; +import org.thoughtcrime.securesms.util.ViewUtil; + +/** + * Renders the {@link color} outside of the {@link Bounds}. + *

+ * Hit tests outside of the bounds. + */ +public final class FillRenderer implements Renderer { + + private final int color; + + private final RectF dst = new RectF(); + private final Path path = new Path(); + + @Override + public void render(@NonNull RendererContext rendererContext) { + rendererContext.canvas.save(); + + rendererContext.mapRect(dst, Bounds.FULL_BOUNDS); + rendererContext.canvasMatrix.setToIdentity(); + + path.reset(); + path.addRoundRect(dst, ViewUtil.dpToPx(18), ViewUtil.dpToPx(18), Path.Direction.CW); + + rendererContext.canvas.clipPath(path); + rendererContext.canvas.drawColor(color); + rendererContext.canvas.restore(); + } + + public FillRenderer(@ColorInt int color) { + this.color = color; + } + + private FillRenderer(Parcel in) { + this(in.readInt()); + } + + @Override + public boolean hitTest(float x, float y) { + return !Bounds.contains(x, y); + } + + public static final Creator CREATOR = new Creator() { + @Override + public FillRenderer createFromParcel(Parcel in) { + return new FillRenderer(in); + } + + @Override + public FillRenderer[] newArray(int size) { + return new FillRenderer[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(color); + } +}