mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-30 21:45:20 +00:00
Image Editor - Undo button visibility.
This commit is contained in:
parent
b5d37702f9
commit
6777b3e0e6
@ -60,6 +60,9 @@ public final class ImageEditorView extends FrameLayout {
|
|||||||
@Nullable
|
@Nullable
|
||||||
private DrawingChangedListener drawingChangedListener;
|
private DrawingChangedListener drawingChangedListener;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private UndoRedoStackListener undoRedoStackListener;
|
||||||
|
|
||||||
private final Matrix viewMatrix = new Matrix();
|
private final Matrix viewMatrix = new Matrix();
|
||||||
private final RectF viewPort = Bounds.newFullBounds();
|
private final RectF viewPort = Bounds.newFullBounds();
|
||||||
private final RectF visibleViewPort = Bounds.newFullBounds();
|
private final RectF visibleViewPort = Bounds.newFullBounds();
|
||||||
@ -200,9 +203,11 @@ public final class ImageEditorView extends FrameLayout {
|
|||||||
if (this.model != model) {
|
if (this.model != model) {
|
||||||
if (this.model != null) {
|
if (this.model != null) {
|
||||||
this.model.setInvalidate(null);
|
this.model.setInvalidate(null);
|
||||||
|
this.model.setUndoRedoStackListener(null);
|
||||||
}
|
}
|
||||||
this.model = model;
|
this.model = model;
|
||||||
this.model.setInvalidate(this::invalidate);
|
this.model.setInvalidate(this::invalidate);
|
||||||
|
this.model.setUndoRedoStackListener(this::onUndoRedoAvailabilityChanged);
|
||||||
this.model.setVisibleViewPort(visibleViewPort);
|
this.model.setVisibleViewPort(visibleViewPort);
|
||||||
invalidate();
|
invalidate();
|
||||||
}
|
}
|
||||||
@ -386,6 +391,10 @@ public final class ImageEditorView extends FrameLayout {
|
|||||||
this.drawingChangedListener = drawingChangedListener;
|
this.drawingChangedListener = drawingChangedListener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setUndoRedoStackListener(@Nullable UndoRedoStackListener undoRedoStackListener) {
|
||||||
|
this.undoRedoStackListener = undoRedoStackListener;
|
||||||
|
}
|
||||||
|
|
||||||
public void setTapListener(TapListener tapListener) {
|
public void setTapListener(TapListener tapListener) {
|
||||||
this.tapListener = tapListener;
|
this.tapListener = tapListener;
|
||||||
}
|
}
|
||||||
@ -398,6 +407,12 @@ public final class ImageEditorView extends FrameLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onUndoRedoAvailabilityChanged(boolean undoAvailable, boolean redoAvailable) {
|
||||||
|
if (undoRedoStackListener != null) {
|
||||||
|
undoRedoStackListener.onAvailabilityChanged(undoAvailable, redoAvailable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private final class DoubleTapGestureListener extends GestureDetector.SimpleOnGestureListener {
|
private final class DoubleTapGestureListener extends GestureDetector.SimpleOnGestureListener {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
package org.thoughtcrime.securesms.imageeditor;
|
||||||
|
|
||||||
|
public interface UndoRedoStackListener {
|
||||||
|
|
||||||
|
void onAvailabilityChanged(boolean undoAvailable, boolean redoAvailable);
|
||||||
|
}
|
@ -44,7 +44,7 @@ public final class EditorElement implements Parcelable {
|
|||||||
|
|
||||||
private final Matrix tempMatrix = new Matrix();
|
private final Matrix tempMatrix = new Matrix();
|
||||||
|
|
||||||
private final List<EditorElement> children = new LinkedList<>();
|
private final List<EditorElement> children = new LinkedList<>();
|
||||||
private final List<EditorElement> deletedChildren = new LinkedList<>();
|
private final List<EditorElement> deletedChildren = new LinkedList<>();
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
|
@ -17,6 +17,7 @@ import org.thoughtcrime.securesms.imageeditor.Bounds;
|
|||||||
import org.thoughtcrime.securesms.imageeditor.ColorableRenderer;
|
import org.thoughtcrime.securesms.imageeditor.ColorableRenderer;
|
||||||
import org.thoughtcrime.securesms.imageeditor.Renderer;
|
import org.thoughtcrime.securesms.imageeditor.Renderer;
|
||||||
import org.thoughtcrime.securesms.imageeditor.RendererContext;
|
import org.thoughtcrime.securesms.imageeditor.RendererContext;
|
||||||
|
import org.thoughtcrime.securesms.imageeditor.UndoRedoStackListener;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
@ -42,6 +43,8 @@ public final class EditorModel implements Parcelable, RendererContext.Ready {
|
|||||||
@NonNull
|
@NonNull
|
||||||
private Runnable invalidate = NULL_RUNNABLE;
|
private Runnable invalidate = NULL_RUNNABLE;
|
||||||
|
|
||||||
|
private UndoRedoStackListener undoRedoStackListener;
|
||||||
|
|
||||||
private final UndoRedoStacks undoRedoStacks;
|
private final UndoRedoStacks undoRedoStacks;
|
||||||
private final UndoRedoStacks cropUndoRedoStacks;
|
private final UndoRedoStacks cropUndoRedoStacks;
|
||||||
private final InBoundsMemory inBoundsMemory = new InBoundsMemory();
|
private final InBoundsMemory inBoundsMemory = new InBoundsMemory();
|
||||||
@ -70,6 +73,10 @@ public final class EditorModel implements Parcelable, RendererContext.Ready {
|
|||||||
this.invalidate = invalidate != null ? invalidate : NULL_RUNNABLE;
|
this.invalidate = invalidate != null ? invalidate : NULL_RUNNABLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setUndoRedoStackListener(UndoRedoStackListener undoRedoStackListener) {
|
||||||
|
this.undoRedoStackListener = undoRedoStackListener;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders tree with the following matrix:
|
* Renders tree with the following matrix:
|
||||||
* <p>
|
* <p>
|
||||||
@ -117,9 +124,7 @@ public final class EditorModel implements Parcelable, RendererContext.Ready {
|
|||||||
|
|
||||||
UndoRedoStacks stacks = cropping ? cropUndoRedoStacks : undoRedoStacks;
|
UndoRedoStacks stacks = cropping ? cropUndoRedoStacks : undoRedoStacks;
|
||||||
|
|
||||||
if (stacks.getUndoStack().tryPush(editorElementHierarchy.getRoot())) {
|
stacks.pushState(editorElementHierarchy.getRoot());
|
||||||
stacks.getRedoStack().clear();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void undo() {
|
public void undo() {
|
||||||
@ -127,6 +132,8 @@ public final class EditorModel implements Parcelable, RendererContext.Ready {
|
|||||||
UndoRedoStacks stacks = cropping ? cropUndoRedoStacks : undoRedoStacks;
|
UndoRedoStacks stacks = cropping ? cropUndoRedoStacks : undoRedoStacks;
|
||||||
|
|
||||||
undoRedo(stacks.getUndoStack(), stacks.getRedoStack(), cropping);
|
undoRedo(stacks.getUndoStack(), stacks.getRedoStack(), cropping);
|
||||||
|
|
||||||
|
updateUndoRedoAvailableState(stacks);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void redo() {
|
public void redo() {
|
||||||
@ -134,12 +141,15 @@ public final class EditorModel implements Parcelable, RendererContext.Ready {
|
|||||||
UndoRedoStacks stacks = cropping ? cropUndoRedoStacks : undoRedoStacks;
|
UndoRedoStacks stacks = cropping ? cropUndoRedoStacks : undoRedoStacks;
|
||||||
|
|
||||||
undoRedo(stacks.getRedoStack(), stacks.getUndoStack(), cropping);
|
undoRedo(stacks.getRedoStack(), stacks.getUndoStack(), cropping);
|
||||||
|
|
||||||
|
updateUndoRedoAvailableState(stacks);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void undoRedo(@NonNull ElementStack fromStack, @NonNull ElementStack toStack, boolean keepEditorState) {
|
private void undoRedo(@NonNull ElementStack fromStack, @NonNull ElementStack toStack, boolean keepEditorState) {
|
||||||
final EditorElement popped = fromStack.pop();
|
final EditorElement oldRootElement = editorElementHierarchy.getRoot();
|
||||||
|
final EditorElement popped = fromStack.pop(oldRootElement);
|
||||||
|
|
||||||
if (popped != null) {
|
if (popped != null) {
|
||||||
EditorElement oldRootElement = editorElementHierarchy.getRoot();
|
|
||||||
editorElementHierarchy = EditorElementHierarchy.create(popped);
|
editorElementHierarchy = EditorElementHierarchy.create(popped);
|
||||||
toStack.tryPush(oldRootElement);
|
toStack.tryPush(oldRootElement);
|
||||||
|
|
||||||
@ -187,6 +197,14 @@ public final class EditorModel implements Parcelable, RendererContext.Ready {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateUndoRedoAvailableState(UndoRedoStacks currentStack) {
|
||||||
|
if (undoRedoStackListener == null) return;
|
||||||
|
|
||||||
|
EditorElement root = editorElementHierarchy.getRoot();
|
||||||
|
|
||||||
|
undoRedoStackListener.onAvailabilityChanged(currentStack.canUndo(root), currentStack.canRedo(root));
|
||||||
|
}
|
||||||
|
|
||||||
private static Map<UUID, EditorElement> getElementMap(@NonNull EditorElement element) {
|
private static Map<UUID, EditorElement> getElementMap(@NonNull EditorElement element) {
|
||||||
final Map<UUID, EditorElement> result = new HashMap<>();
|
final Map<UUID, EditorElement> result = new HashMap<>();
|
||||||
element.buildMap(result);
|
element.buildMap(result);
|
||||||
@ -195,14 +213,15 @@ public final class EditorModel implements Parcelable, RendererContext.Ready {
|
|||||||
|
|
||||||
public void startCrop() {
|
public void startCrop() {
|
||||||
pushUndoPoint();
|
pushUndoPoint();
|
||||||
cropUndoRedoStacks.getUndoStack().clear();
|
cropUndoRedoStacks.clear(editorElementHierarchy.getRoot());
|
||||||
cropUndoRedoStacks.getUndoStack().clear();
|
|
||||||
editorElementHierarchy.startCrop(invalidate);
|
editorElementHierarchy.startCrop(invalidate);
|
||||||
inBoundsMemory.push(editorElementHierarchy.getMainImage(), editorElementHierarchy.getCropEditorElement());
|
inBoundsMemory.push(editorElementHierarchy.getMainImage(), editorElementHierarchy.getCropEditorElement());
|
||||||
|
updateUndoRedoAvailableState(cropUndoRedoStacks);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void doneCrop() {
|
public void doneCrop() {
|
||||||
editorElementHierarchy.doneCrop(visibleViewPort, invalidate);
|
editorElementHierarchy.doneCrop(visibleViewPort, invalidate);
|
||||||
|
updateUndoRedoAvailableState(undoRedoStacks);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCropAspectLock(boolean locked) {
|
public void setCropAspectLock(boolean locked) {
|
||||||
@ -223,6 +242,9 @@ public final class EditorModel implements Parcelable, RendererContext.Ready {
|
|||||||
if (isCropping()) {
|
if (isCropping()) {
|
||||||
ensureFitsBounds(allowScaleToRepairCrop);
|
ensureFitsBounds(allowScaleToRepairCrop);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UndoRedoStacks stacks = isCropping() ? cropUndoRedoStacks : undoRedoStacks;
|
||||||
|
updateUndoRedoAvailableState(stacks);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ensureFitsBounds(boolean allowScaleToRepairCrop) {
|
private void ensureFitsBounds(boolean allowScaleToRepairCrop) {
|
||||||
@ -467,13 +489,14 @@ public final class EditorModel implements Parcelable, RendererContext.Ready {
|
|||||||
parent.addElement(element);
|
parent.addElement(element);
|
||||||
|
|
||||||
if (parent != mainImage) {
|
if (parent != mainImage) {
|
||||||
undoRedoStacks.getUndoStack().clear();
|
undoRedoStacks.clear(editorElementHierarchy.getRoot());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateUndoRedoAvailableState(undoRedoStacks);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isChanged() {
|
public boolean isChanged() {
|
||||||
ElementStack undoStack = undoRedoStacks.getUndoStack();
|
return undoRedoStacks.isChanged(editorElementHierarchy.getRoot());
|
||||||
return !undoStack.isEmpty() || undoStack.isOverflowed();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public RectF findCropRelativeToRoot() {
|
public RectF findCropRelativeToRoot() {
|
||||||
@ -578,4 +601,5 @@ public final class EditorModel implements Parcelable, RendererContext.Ready {
|
|||||||
public boolean isCropping() {
|
public boolean isCropping() {
|
||||||
return editorElementHierarchy.getCropEditorElement().getFlags().isVisible();
|
return editorElementHierarchy.getCropEditorElement().getFlags().isVisible();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -13,14 +13,12 @@ import java.util.Stack;
|
|||||||
* <p>
|
* <p>
|
||||||
* Elements are mutable, so this stack serializes the element and keeps a stack of serialized data.
|
* Elements are mutable, so this stack serializes the element and keeps a stack of serialized data.
|
||||||
* <p>
|
* <p>
|
||||||
* The stack has a {@link #limit} and if it exceeds that limit the {@link #overflowed} flag is set.
|
* The stack has a {@link #limit} and if it exceeds that limit during a push the earliest item is removed.
|
||||||
* So that when used as an undo stack, {@link #isEmpty()} and {@link #isOverflowed()} tell you if the image has ever changed.
|
|
||||||
*/
|
*/
|
||||||
final class ElementStack implements Parcelable {
|
final class ElementStack implements Parcelable {
|
||||||
|
|
||||||
private final int limit;
|
private final int limit;
|
||||||
private final Stack<byte[]> stack = new Stack<>();
|
private final Stack<byte[]> stack = new Stack<>();
|
||||||
private boolean overflowed;
|
|
||||||
|
|
||||||
ElementStack(int limit) {
|
ElementStack(int limit) {
|
||||||
this.limit = limit;
|
this.limit = limit;
|
||||||
@ -28,7 +26,6 @@ final class ElementStack implements Parcelable {
|
|||||||
|
|
||||||
private ElementStack(@NonNull Parcel in) {
|
private ElementStack(@NonNull Parcel in) {
|
||||||
this(in.readInt());
|
this(in.readInt());
|
||||||
overflowed = in.readInt() != 0;
|
|
||||||
final int count = in.readInt();
|
final int count = in.readInt();
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
stack.add(i, in.createByteArray());
|
stack.add(i, in.createByteArray());
|
||||||
@ -43,32 +40,52 @@ final class ElementStack implements Parcelable {
|
|||||||
* @return true iff the pushed item was different to the top item.
|
* @return true iff the pushed item was different to the top item.
|
||||||
*/
|
*/
|
||||||
boolean tryPush(@NonNull EditorElement element) {
|
boolean tryPush(@NonNull EditorElement element) {
|
||||||
Parcel parcel = Parcel.obtain();
|
byte[] bytes = getBytes(element);
|
||||||
byte[] bytes;
|
boolean push = stack.isEmpty() || !Arrays.equals(bytes, stack.peek());
|
||||||
try {
|
|
||||||
parcel.writeParcelable(element, 0);
|
|
||||||
bytes = parcel.marshall();
|
|
||||||
} finally {
|
|
||||||
parcel.recycle();
|
|
||||||
}
|
|
||||||
boolean push = stack.isEmpty() || !Arrays.equals(bytes, stack.peek());
|
|
||||||
if (push) {
|
if (push) {
|
||||||
stack.push(bytes);
|
stack.push(bytes);
|
||||||
if (stack.size() > limit) {
|
if (stack.size() > limit) {
|
||||||
stack.remove(0);
|
stack.remove(0);
|
||||||
overflowed = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return push;
|
return push;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable EditorElement pop() {
|
static byte[] getBytes(@NonNull Parcelable parcelable) {
|
||||||
|
Parcel parcel = Parcel.obtain();
|
||||||
|
byte[] bytes;
|
||||||
|
try {
|
||||||
|
parcel.writeParcelable(parcelable, 0);
|
||||||
|
bytes = parcel.marshall();
|
||||||
|
} finally {
|
||||||
|
parcel.recycle();
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pops the first different state from the supplied element.
|
||||||
|
*/
|
||||||
|
@Nullable EditorElement pop(@NonNull EditorElement element) {
|
||||||
if (stack.empty()) return null;
|
if (stack.empty()) return null;
|
||||||
|
|
||||||
byte[] data = stack.pop();
|
byte[] elementBytes = getBytes(element);
|
||||||
|
byte[] stackData = null;
|
||||||
|
|
||||||
|
while (!stack.empty() && stackData == null) {
|
||||||
|
byte[] topData = stack.pop();
|
||||||
|
|
||||||
|
if (!Arrays.equals(topData, elementBytes)) {
|
||||||
|
stackData = topData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stackData == null) return null;
|
||||||
|
|
||||||
Parcel parcel = Parcel.obtain();
|
Parcel parcel = Parcel.obtain();
|
||||||
try {
|
try {
|
||||||
parcel.unmarshall(data, 0, data.length);
|
parcel.unmarshall(stackData, 0, stackData.length);
|
||||||
parcel.setDataPosition(0);
|
parcel.setDataPosition(0);
|
||||||
return parcel.readParcelable(EditorElement.class.getClassLoader());
|
return parcel.readParcelable(EditorElement.class.getClassLoader());
|
||||||
} finally {
|
} finally {
|
||||||
@ -100,7 +117,6 @@ final class ElementStack implements Parcelable {
|
|||||||
@Override
|
@Override
|
||||||
public void writeToParcel(Parcel dest, int flags) {
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
dest.writeInt(limit);
|
dest.writeInt(limit);
|
||||||
dest.writeInt(overflowed ? 1 : 0);
|
|
||||||
final int count = stack.size();
|
final int count = stack.size();
|
||||||
dest.writeInt(count);
|
dest.writeInt(count);
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
@ -108,11 +124,17 @@ final class ElementStack implements Parcelable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isEmpty() {
|
boolean stackContainsStateDifferentFrom(@NonNull EditorElement element) {
|
||||||
return stack.isEmpty();
|
if (stack.isEmpty()) return false;
|
||||||
}
|
|
||||||
|
|
||||||
boolean isOverflowed() {
|
byte[] currentStateBytes = getBytes(element);
|
||||||
return overflowed;
|
|
||||||
|
for (byte[] item : stack) {
|
||||||
|
if (!Arrays.equals(item, currentStateBytes)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,19 +2,27 @@ package org.thoughtcrime.securesms.imageeditor.model;
|
|||||||
|
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
final class UndoRedoStacks implements Parcelable {
|
final class UndoRedoStacks implements Parcelable {
|
||||||
|
|
||||||
private final ElementStack undoStack;
|
private final ElementStack undoStack;
|
||||||
private final ElementStack redoStack;
|
private final ElementStack redoStack;
|
||||||
|
|
||||||
public UndoRedoStacks(int limit) {
|
@NonNull
|
||||||
this(new ElementStack(limit), new ElementStack(limit));
|
private byte[] unchangedState;
|
||||||
|
|
||||||
|
UndoRedoStacks(int limit) {
|
||||||
|
this(new ElementStack(limit), new ElementStack(limit), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private UndoRedoStacks(ElementStack undoStack, ElementStack redoStack) {
|
private UndoRedoStacks(ElementStack undoStack, ElementStack redoStack, @Nullable byte[] unchangedState) {
|
||||||
this.undoStack = undoStack;
|
this.undoStack = undoStack;
|
||||||
this.redoStack = redoStack;
|
this.redoStack = redoStack;
|
||||||
|
this.unchangedState = unchangedState != null ? unchangedState : new byte[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final Creator<UndoRedoStacks> CREATOR = new Creator<UndoRedoStacks>() {
|
public static final Creator<UndoRedoStacks> CREATOR = new Creator<UndoRedoStacks>() {
|
||||||
@ -22,7 +30,8 @@ final class UndoRedoStacks implements Parcelable {
|
|||||||
public UndoRedoStacks createFromParcel(Parcel in) {
|
public UndoRedoStacks createFromParcel(Parcel in) {
|
||||||
return new UndoRedoStacks(
|
return new UndoRedoStacks(
|
||||||
in.readParcelable(ElementStack.class.getClassLoader()),
|
in.readParcelable(ElementStack.class.getClassLoader()),
|
||||||
in.readParcelable(ElementStack.class.getClassLoader())
|
in.readParcelable(ElementStack.class.getClassLoader()),
|
||||||
|
in.createByteArray()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,6 +45,7 @@ final class UndoRedoStacks implements Parcelable {
|
|||||||
public void writeToParcel(Parcel dest, int flags) {
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
dest.writeParcelable(undoStack, flags);
|
dest.writeParcelable(undoStack, flags);
|
||||||
dest.writeParcelable(redoStack, flags);
|
dest.writeParcelable(redoStack, flags);
|
||||||
|
dest.writeByteArray(unchangedState);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -50,4 +60,34 @@ final class UndoRedoStacks implements Parcelable {
|
|||||||
ElementStack getRedoStack() {
|
ElementStack getRedoStack() {
|
||||||
return redoStack;
|
return redoStack;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void pushState(@NonNull EditorElement element) {
|
||||||
|
if (undoStack.tryPush(element)) {
|
||||||
|
redoStack.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear(@NonNull EditorElement element) {
|
||||||
|
undoStack.clear();
|
||||||
|
redoStack.clear();
|
||||||
|
unchangedState = ElementStack.getBytes(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isChanged(@NonNull EditorElement element) {
|
||||||
|
return !Arrays.equals(ElementStack.getBytes(element), unchangedState);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* As long as there is something different in the stack somewhere, then we can undo.
|
||||||
|
*/
|
||||||
|
boolean canUndo(@NonNull EditorElement currentState) {
|
||||||
|
return undoStack.stackContainsStateDifferentFrom(currentState);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* As long as there is something different in the stack somewhere, then we can redo.
|
||||||
|
*/
|
||||||
|
boolean canRedo(@NonNull EditorElement currentState) {
|
||||||
|
return redoStack.stackContainsStateDifferentFrom(currentState);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -118,13 +118,14 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
|
|||||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
|
||||||
imageEditorHud = view.findViewById(R.id.scribble_hud);
|
imageEditorHud = view.findViewById(R.id.scribble_hud);
|
||||||
imageEditorView = view.findViewById(R.id.image_editor_view);
|
imageEditorView = view.findViewById(R.id.image_editor_view);
|
||||||
|
|
||||||
imageEditorHud.setEventListener(this);
|
imageEditorHud.setEventListener(this);
|
||||||
|
|
||||||
imageEditorView.setTapListener(selectionListener);
|
imageEditorView.setTapListener(selectionListener);
|
||||||
imageEditorView.setDrawingChangedListener(this::refreshUniqueColors);
|
imageEditorView.setDrawingChangedListener(this::refreshUniqueColors);
|
||||||
|
imageEditorView.setUndoRedoStackListener(this::onUndoRedoAvailabilityChanged);
|
||||||
|
|
||||||
EditorModel editorModel = null;
|
EditorModel editorModel = null;
|
||||||
|
|
||||||
@ -321,6 +322,10 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
|
|||||||
imageEditorHud.setColorPalette(imageEditorView.getModel().getUniqueColorsIgnoringAlpha());
|
imageEditorHud.setColorPalette(imageEditorView.getModel().getUniqueColorsIgnoringAlpha());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onUndoRedoAvailabilityChanged(boolean undoAvailable, boolean redoAvailable) {
|
||||||
|
imageEditorHud.setUndoAvailability(undoAvailable);
|
||||||
|
}
|
||||||
|
|
||||||
private final ImageEditorView.TapListener selectionListener = new ImageEditorView.TapListener() {
|
private final ImageEditorView.TapListener selectionListener = new ImageEditorView.TapListener() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -47,7 +47,10 @@ public final class ImageEditorHud extends LinearLayout {
|
|||||||
private ColorPaletteAdapter colorPaletteAdapter;
|
private ColorPaletteAdapter colorPaletteAdapter;
|
||||||
|
|
||||||
private final Map<Mode, Set<View>> visibilityModeMap = new HashMap<>();
|
private final Map<Mode, Set<View>> visibilityModeMap = new HashMap<>();
|
||||||
private final Set<View> allViews = new HashSet<>();
|
private final Set<View> allViews = new HashSet<>();
|
||||||
|
|
||||||
|
private Mode currentMode;
|
||||||
|
private boolean undoAvailable;
|
||||||
|
|
||||||
public ImageEditorHud(@NonNull Context context) {
|
public ImageEditorHud(@NonNull Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
@ -171,9 +174,10 @@ public final class ImageEditorHud extends LinearLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setMode(@NonNull Mode mode, boolean notify) {
|
private void setMode(@NonNull Mode mode, boolean notify) {
|
||||||
|
this.currentMode = mode;
|
||||||
Set<View> visibleButtons = visibilityModeMap.get(mode);
|
Set<View> visibleButtons = visibilityModeMap.get(mode);
|
||||||
for (View button : allViews) {
|
for (View button : allViews) {
|
||||||
button.setVisibility(visibleButtons != null && visibleButtons.contains(button) ? VISIBLE : GONE);
|
button.setVisibility(buttonIsVisible(visibleButtons, button) ? VISIBLE : GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
@ -189,6 +193,12 @@ public final class ImageEditorHud extends LinearLayout {
|
|||||||
eventListener.onRequestFullScreen(mode != Mode.NONE);
|
eventListener.onRequestFullScreen(mode != Mode.NONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean buttonIsVisible(@Nullable Set<View> visibleButtons, @NonNull View button) {
|
||||||
|
return visibleButtons != null &&
|
||||||
|
visibleButtons.contains(button) &&
|
||||||
|
(button != undoButton || undoAvailable);
|
||||||
|
}
|
||||||
|
|
||||||
private void presentModeCrop() {
|
private void presentModeCrop() {
|
||||||
updateCropAspectLockImage(eventListener.isCropAspectLocked());
|
updateCropAspectLockImage(eventListener.isCropAspectLocked());
|
||||||
}
|
}
|
||||||
@ -216,6 +226,12 @@ public final class ImageEditorHud extends LinearLayout {
|
|||||||
return color & ~0xff000000 | 0x80000000;
|
return color & ~0xff000000 | 0x80000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setUndoAvailability(boolean undoAvailable) {
|
||||||
|
this.undoAvailable = undoAvailable;
|
||||||
|
|
||||||
|
undoButton.setVisibility(buttonIsVisible(visibilityModeMap.get(currentMode), undoButton) ? VISIBLE : GONE);
|
||||||
|
}
|
||||||
|
|
||||||
public enum Mode {
|
public enum Mode {
|
||||||
NONE, DRAW, HIGHLIGHT, TEXT, MOVE_DELETE, CROP
|
NONE, DRAW, HIGHLIGHT, TEXT, MOVE_DELETE, CROP
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user