Add text styles support to image editor.

Co-authored-by: Greyson Parrelli <greyson@signal.org>
fork-5.53.8
Alex Hart 2021-09-08 17:01:20 -03:00 zatwierdzone przez Greyson Parrelli
rodzic 05f7dce503
commit 715ad0d459
8 zmienionych plików z 212 dodań i 22 usunięć

Wyświetl plik

@ -7,6 +7,7 @@ import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.os.Build;
import android.os.Parcel;
import android.view.animation.Interpolator;
@ -32,7 +33,11 @@ import static java.util.Collections.emptyList;
*/
public final class MultiLineTextRenderer extends InvalidateableRenderer implements ColorableRenderer, SelectableRenderer {
private static final float HIT_PADDING = ViewUtil.dpToPx(30);
private static final float HIT_PADDING = ViewUtil.dpToPx(30);
private static final float HIGHLIGHT_HORIZONTAL_PADDING = ViewUtil.dpToPx(8);
private static final float HIGHLIGHT_TOP_PADDING = ViewUtil.dpToPx(10);
private static final float HIGHLIGHT_BOTTOM_PADDING = ViewUtil.dpToPx(6);
private static final float HIGHLIGHT_CORNER_RADIUS = ViewUtil.dpToPx(4);
@NonNull
private String text = "";
@ -42,6 +47,7 @@ public final class MultiLineTextRenderer extends InvalidateableRenderer implemen
private final Paint paint = new Paint();
private final Paint selectionPaint = new Paint();
private final Paint modePaint = new Paint();
private final float textScale;
@ -49,6 +55,7 @@ public final class MultiLineTextRenderer extends InvalidateableRenderer implemen
private int selEnd;
private boolean hasFocus;
private boolean selected;
private Mode mode;
private List<Line> lines = emptyList();
@ -60,14 +67,27 @@ public final class MultiLineTextRenderer extends InvalidateableRenderer implemen
private final SelectedElementGuideRenderer selectedElementGuideRenderer = new SelectedElementGuideRenderer();
private final RectF textBounds = new RectF();
public MultiLineTextRenderer(@Nullable String text, @ColorInt int color) {
setColor(color);
public MultiLineTextRenderer(@Nullable String text, @ColorInt int color, @NonNull Mode mode) {
this.mode = mode;
Typeface typeface = getTypeface();
modePaint.setAntiAlias(true);
modePaint.setTextSize(100);
modePaint.setTypeface(typeface);
setColorInternal(color);
float regularTextSize = paint.getTextSize();
paint.setAntiAlias(true);
paint.setTextSize(100);
paint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD));
paint.setTypeface(typeface);
textScale = paint.getTextSize() / regularTextSize;
selectionPaint.setAntiAlias(true);
setText(text != null ? text : "");
createLinesForText();
}
@ -102,6 +122,14 @@ public final class MultiLineTextRenderer extends InvalidateableRenderer implemen
}
}
public void nextMode() {
setMode(Mode.fromCode(mode.code + 1));
}
public @NonNull Mode getMode() {
return mode;
}
/**
* Post concats an additional matrix to the supplied matrix that scales and positions the editor
* so that all the text is visible.
@ -158,6 +186,7 @@ public final class MultiLineTextRenderer extends InvalidateableRenderer implemen
private final RectF selectionBounds = new RectF();
private final RectF textBounds = new RectF();
private final RectF hitBounds = new RectF();
private final RectF modeBounds = new RectF();
private String text;
private int selStart;
@ -293,6 +322,31 @@ public final class MultiLineTextRenderer extends InvalidateableRenderer implemen
rendererContext.canvasMatrix.concat(projectionMatrix);
if (mode == Mode.HIGHLIGHT) {
modeBounds.set(textBounds.left - HIGHLIGHT_HORIZONTAL_PADDING,
selectionBounds.top - HIGHLIGHT_TOP_PADDING,
textBounds.right + HIGHLIGHT_HORIZONTAL_PADDING,
selectionBounds.bottom + HIGHLIGHT_BOTTOM_PADDING);
int alpha = modePaint.getAlpha();
modePaint.setAlpha(rendererContext.getAlpha(alpha));
rendererContext.canvas.drawRoundRect(modeBounds, HIGHLIGHT_CORNER_RADIUS, HIGHLIGHT_CORNER_RADIUS, modePaint);
modePaint.setAlpha(alpha);
} else if (mode == Mode.UNDERLINE) {
modeBounds.set(textBounds.left, selectionBounds.top, textBounds.right, selectionBounds.bottom);
modeBounds.inset(-ViewUtil.dpToPx(2), -ViewUtil.dpToPx(2));
modeBounds.set(modeBounds.left,
Math.max(modeBounds.top, modeBounds.bottom - ViewUtil.dpToPx(6)),
modeBounds.right,
modeBounds.bottom - ViewUtil.dpToPx(2));
int alpha = modePaint.getAlpha();
modePaint.setAlpha(rendererContext.getAlpha(alpha));
rendererContext.canvas.drawRect(modeBounds, modePaint);
modePaint.setAlpha(alpha);
}
if (hasFocus && showSelectionOrCursor()) {
if (selStart == selEnd) {
selectionPaint.setAlpha((int) (cursorAnimatedValue * 128));
@ -309,6 +363,13 @@ public final class MultiLineTextRenderer extends InvalidateableRenderer implemen
paint.setAlpha(alpha);
if (mode == Mode.OUTLINE) {
int modeAlpha = modePaint.getAlpha();
modePaint.setAlpha(rendererContext.getAlpha(alpha));
rendererContext.canvas.drawText(text, 0, 0, modePaint);
modePaint.setAlpha(modeAlpha);
}
rendererContext.restore();
// add our descent for the next lines
@ -332,10 +393,7 @@ public final class MultiLineTextRenderer extends InvalidateableRenderer implemen
@Override
public void setColor(@ColorInt int color) {
if (this.color != color) {
this.color = color;
paint.setColor(color);
selectionPaint.setColor(color);
invalidate();
setColorInternal(color);
}
}
@ -392,10 +450,39 @@ public final class MultiLineTextRenderer extends InvalidateableRenderer implemen
}
}
private void setMode(@NonNull Mode mode) {
if (this.mode != mode) {
this.mode = mode;
setColorInternal(color);
}
}
private void setColorInternal(@ColorInt int color) {
this.color = color;
if (mode == Mode.REGULAR) {
paint.setColor(color);
selectionPaint.setColor(color);
} else {
paint.setColor(Color.WHITE);
selectionPaint.setColor(Color.WHITE);
}
if (mode == Mode.OUTLINE) {
modePaint.setStrokeWidth(ViewUtil.dpToPx(15) / 10f);
modePaint.setStyle(Paint.Style.STROKE);
} else {
modePaint.setStyle(Paint.Style.FILL);
}
modePaint.setColor(color);
invalidate();
}
public static final Creator<MultiLineTextRenderer> CREATOR = new Creator<MultiLineTextRenderer>() {
@Override
public MultiLineTextRenderer createFromParcel(Parcel in) {
return new MultiLineTextRenderer(in.readString(), in.readInt());
return new MultiLineTextRenderer(in.readString(), in.readInt(), Mode.fromCode(in.readInt()));
}
@Override
@ -413,6 +500,7 @@ public final class MultiLineTextRenderer extends InvalidateableRenderer implemen
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(text);
dest.writeInt(color);
dest.writeInt(mode.code);
}
private static Interpolator pulseInterpolator() {
@ -424,4 +512,38 @@ public final class MultiLineTextRenderer extends InvalidateableRenderer implemen
return Math.max(0, Math.min(1, input));
};
}
private static @NonNull Typeface getTypeface() {
if (Build.VERSION.SDK_INT < 26) {
return Typeface.create(Typeface.DEFAULT, Typeface.BOLD);
} else {
return new Typeface.Builder("")
.setFallback("sans-serif")
.setWeight(900)
.build();
}
}
public enum Mode {
REGULAR(0),
HIGHLIGHT(1),
UNDERLINE(2),
OUTLINE(3);
private final int code;
Mode(int code) {
this.code = code;
}
private static Mode fromCode(int code) {
for (final Mode value : Mode.values()) {
if (value.code == code) {
return value;
}
}
return REGULAR;
}
}
}

Wyświetl plik

@ -1,7 +1,5 @@
package org.thoughtcrime.securesms.scribbles;
import static android.app.Activity.RESULT_OK;
import android.Manifest;
import android.content.Intent;
import android.content.res.Configuration;
@ -14,7 +12,6 @@ import android.net.Uri;
import android.os.Bundle;
import android.view.HapticFeedbackConstants;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
@ -71,6 +68,8 @@ import java.util.Collections;
import java.util.List;
import java.util.Objects;
import static android.app.Activity.RESULT_OK;
public final class ImageEditorFragment extends Fragment implements ImageEditorHudV2.EventListener,
MediaSendPageFragment,
TextEntryDialogFragment.Controller
@ -351,6 +350,13 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
imageEditorView.zoomToFitText(editorElement, textRenderer);
}
@Override
public void onTextStyleToggle() {
if (currentSelection != null && currentSelection.getRenderer() instanceof MultiLineTextRenderer) {
((MultiLineTextRenderer) currentSelection.getRenderer()).nextMode();
}
}
@Override
public void onTextEntryDialogDismissed(boolean hasText) {
imageEditorView.doneTextEditing();
@ -366,7 +372,7 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
protected void addText() {
String initialText = "";
int color = imageEditorHud.getActiveColor();
MultiLineTextRenderer renderer = new MultiLineTextRenderer(initialText, color);
MultiLineTextRenderer renderer = new MultiLineTextRenderer(initialText, color, MultiLineTextRenderer.Mode.REGULAR);
EditorElement element = new EditorElement(renderer, EditorModel.Z_TEXT);
imageEditorView.getModel().addElementCentered(element, 1);

Wyświetl plik

@ -64,6 +64,7 @@ class ImageEditorHudV2 @JvmOverloads constructor(
private val colorIndicator: ImageView = findViewById(R.id.image_editor_hud_color_indicator)
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 selectableSet: Set<View> = setOf(drawButton, textButton, stickerButton, blurButton)
@ -73,7 +74,7 @@ class ImageEditorHudV2 @JvmOverloads constructor(
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
private val allModeTools: Set<View> = drawTools + blurTools + drawButtonRow + cropButtonRow + textStyleToggle
private val viewsToSlide: Set<View> = drawButtonRow + cropButtonRow
@ -93,6 +94,7 @@ class ImageEditorHudV2 @JvmOverloads constructor(
clearAllButton.setOnClickListener { listener?.onClearAll() }
cancelButton.setOnClickListener { listener?.onCancel() }
textStyleToggle.setOnClickListener { listener?.onTextStyleToggle() }
drawButton.setOnClickListener { setMode(Mode.DRAW) }
blurButton.setOnClickListener { setMode(Mode.BLUR) }
textButton.setOnClickListener { setMode(Mode.TEXT) }
@ -370,7 +372,7 @@ class ImageEditorHudV2 @JvmOverloads constructor(
private fun presentModeText() {
animateModeChange(
inSet = drawButtonRow + setOf(drawSeekBar),
inSet = drawButtonRow + setOf(drawSeekBar, textStyleToggle),
outSet = allModeTools
)
animateInUndoTools()
@ -520,6 +522,7 @@ class ImageEditorHudV2 @JvmOverloads constructor(
fun onFlipHorizontal()
fun onRotate90AntiClockwise()
fun onCropAspectLock()
fun onTextStyleToggle()
val isCropAspectLocked: Boolean
fun onRequestFullScreen(fullScreen: Boolean, hideKeyboard: Boolean)

Wyświetl plik

@ -61,6 +61,7 @@ class TextEntryDialogFragment : KeyboardEntryDialogFragment(R.layout.v2_media_im
val slider: AppCompatSeekBar = view.findViewById(R.id.image_editor_hud_draw_color_bar)
val colorIndicator: ImageView = view.findViewById(R.id.image_editor_hud_color_indicator)
val styleToggle: ImageView = view.findViewById(R.id.image_editor_hud_text_style_button)
slider.setUpForColor(
Color.WHITE,
{
@ -83,6 +84,10 @@ class TextEntryDialogFragment : KeyboardEntryDialogFragment(R.layout.v2_media_im
)
slider.progress = requireArguments().getInt("color_index")
styleToggle.setOnClickListener {
(element.renderer as MultiLineTextRenderer).nextMode()
}
}
override fun onDismiss(dialog: DialogInterface) {

Wyświetl plik

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M17.202,9.988l-1.6,0.2L15.202,7.988L13.002,7.988v8h1.9v1.7L9.202,17.688L9.202,15.988L11.002,15.988L11.002,7.988L8.802,7.988l-0.4,2.2L6.802,9.988l0.3,-3.7h9.8Z"
android:fillColor="#000600"/>
<path
android:fillColor="#FF000000"
android:pathData="M16.015,1.5A6.5,6.5 0,0 1,22.502 7.995v8.011a6.5,6.5 0,0 1,-6.487 6.494L7.986,22.5A6.5,6.5 0,0 1,1.502 16.006L1.502,7.995A6.5,6.5 0,0 1,7.986 1.5h8.029m0,-1.5L7.986,-0A7.991,7.991 0,0 0,0.002 7.995v8.011a7.989,7.989 0,0 0,7.986 7.994h8.029A7.989,7.989 0,0 0,24.002 16.006L24.002,7.995A7.991,7.991 0,0 0,16.015 0Z"/>
</vector>

Wyświetl plik

@ -203,16 +203,24 @@
android:splitTrack="false"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/image_editor_hud_top_of_button_bar_spacing"
app:layout_constraintEnd_toStartOf="@+id/image_editor_hud_draw_brush"
app:layout_constraintEnd_toStartOf="@+id/toggle_button_barrier"
app:layout_constraintStart_toStartOf="parent"
app:layout_goneMarginEnd="32dp"
tools:visibility="visible" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/toggle_button_barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:barrierDirection="start"
app:constraint_referenced_ids="image_editor_hud_draw_brush,image_editor_hud_text_style_button" />
<ImageView
android:id="@+id/image_editor_hud_draw_brush"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginEnd="16dp"
android:alpha="0"
android:background="@drawable/circle_tintable_padded"
android:contentDescription="@string/ImageEditorHud__toggle_between_marker_and_highlighter"
android:padding="12dp"
@ -222,6 +230,26 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/image_editor_hud_draw_color_bar"
app:srcCompat="@drawable/ic_draw_white_24"
tools:alpha="1"
tools:visibility="visible" />
<ImageView
android:id="@+id/image_editor_hud_text_style_button"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginEnd="16dp"
android:alpha="0"
android:background="@drawable/circle_tintable_padded"
android:contentDescription="@string/ImageEditorHud__toggle_between_text_styles"
android:padding="12dp"
android:visibility="gone"
app:backgroundTint="@color/transparent_black_40"
app:layout_constraintBottom_toBottomOf="@id/image_editor_hud_draw_color_bar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/image_editor_hud_draw_color_bar"
app:srcCompat="@drawable/ic_text_tool_24"
app:tint="@color/core_white"
tools:alpha="1"
tools:visibility="visible" />
<androidx.appcompat.widget.AppCompatSeekBar
@ -380,8 +408,8 @@
android:id="@+id/image_editor_hud_brush_preview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
tools:visibility="visible"
android:alpha="0"
tools:alpha="1" />
android:visibility="gone"
tools:alpha="1"
tools:visibility="visible" />
</merge>

Wyświetl plik

@ -31,11 +31,24 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
android:splitTrack="false"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintEnd_toStartOf="@id/image_editor_hud_text_style_button"
app:layout_constraintStart_toStartOf="parent" />
<ImageView
android:id="@+id/image_editor_hud_text_style_button"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginEnd="16dp"
android:background="@drawable/circle_tintable_padded"
android:contentDescription="@string/ImageEditorHud__toggle_between_text_styles"
android:padding="12dp"
app:backgroundTint="@color/transparent_black_40"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:srcCompat="@drawable/ic_text_tool_24"
app:tint="@color/core_white" />
</androidx.constraintlayout.widget.ConstraintLayout>
</org.thoughtcrime.securesms.components.InputAwareLayout>

Wyświetl plik

@ -3826,6 +3826,7 @@
<string name="MediaReviewImagePageFragment__youll_lose_any_changes">You\'ll lose any changes you\'ve made to this photo.</string>
<string name="ImageEditorHud__delete">Delete</string>
<string name="CameraFragment__failed_to_open_camera">Failed to open camera</string>
<string name="ImageEditorHud__toggle_between_text_styles">Toggle between text styles</string>
<!-- EOF -->