Improve the image editor.
A variety of improvements to the image editor, such as: - New, fullscreen styling - Smoother lines - Better text and sticker handling - Improved color picker with a history pallette - New highlighter tool
@ -375,7 +375,7 @@
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".scribbles.ScribbleActivity"
|
||||
android:theme="@style/TextSecure.LightNoActionBar"
|
||||
android:theme="@style/TextSecure.ScribbleTheme"
|
||||
android:windowSoftInputMode="stateHidden"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
|
BIN
res/drawable-hdpi/baseline_highlight_white_24.png
Normal file
After Width: | Height: | Size: 961 B |
Before Width: | Height: | Size: 268 B After Width: | Height: | Size: 788 B |
Before Width: | Height: | Size: 331 B After Width: | Height: | Size: 561 B |
Before Width: | Height: | Size: 345 B After Width: | Height: | Size: 582 B |
Before Width: | Height: | Size: 391 B After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 593 B After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 139 B After Width: | Height: | Size: 708 B |
BIN
res/drawable-mdpi/baseline_highlight_white_24.png
Normal file
After Width: | Height: | Size: 654 B |
Before Width: | Height: | Size: 193 B After Width: | Height: | Size: 506 B |
Before Width: | Height: | Size: 311 B After Width: | Height: | Size: 351 B |
Before Width: | Height: | Size: 301 B After Width: | Height: | Size: 446 B |
Before Width: | Height: | Size: 244 B After Width: | Height: | Size: 838 B |
Before Width: | Height: | Size: 376 B After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 100 B After Width: | Height: | Size: 482 B |
BIN
res/drawable-xhdpi/baseline_highlight_white_24.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 315 B After Width: | Height: | Size: 984 B |
Before Width: | Height: | Size: 474 B After Width: | Height: | Size: 576 B |
Before Width: | Height: | Size: 398 B After Width: | Height: | Size: 685 B |
Before Width: | Height: | Size: 457 B After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 774 B After Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 105 B After Width: | Height: | Size: 539 B |
BIN
res/drawable-xxhdpi/baseline_highlight_white_24.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 429 B After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 460 B After Width: | Height: | Size: 990 B |
Before Width: | Height: | Size: 338 B After Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 675 B After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 4.8 KiB |
Before Width: | Height: | Size: 134 B After Width: | Height: | Size: 763 B |
BIN
res/drawable-xxxhdpi/baseline_highlight_white_24.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 532 B After Width: | Height: | Size: 2.4 KiB |
BIN
res/drawable-xxxhdpi/ic_check_white_24dp.png
Normal file
After Width: | Height: | Size: 946 B |
BIN
res/drawable-xxxhdpi/ic_delete_white_24dp.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 908 B After Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 6.8 KiB |
Before Width: | Height: | Size: 111 B After Width: | Height: | Size: 840 B |
4
res/drawable/circle_white.xml
Normal file
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
|
||||
<solid android:color="@color/white"/>
|
||||
</shape>
|
22
res/layout/item_color.xml
Normal file
@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="6dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:src="@drawable/circle_white"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/palette_item_foreground"
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:src="@drawable/circle_white"
|
||||
android:layout_gravity="center"
|
||||
tools:tint="@color/red"/>
|
||||
|
||||
</FrameLayout>
|
@ -1,44 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/activity_main"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
tools:context="org.thoughtcrime.securesms.scribbles.ScribbleActivity">
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/activity_main"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/black"
|
||||
tools:context="org.thoughtcrime.securesms.scribbles.ScribbleActivity">
|
||||
|
||||
<org.thoughtcrime.securesms.scribbles.ScribbleToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/white"/>
|
||||
<org.thoughtcrime.securesms.scribbles.widget.ScribbleView
|
||||
android:id="@+id/scribble_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center" />
|
||||
|
||||
<FrameLayout android:layout_weight="1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_gravity="center"
|
||||
android:background="@color/grey_300"
|
||||
android:gravity="center">
|
||||
<org.thoughtcrime.securesms.scribbles.ScribbleHud
|
||||
android:id="@+id/scribble_hud"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<org.thoughtcrime.securesms.scribbles.widget.ScribbleView
|
||||
android:id="@+id/scribble_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"/>
|
||||
|
||||
<org.thoughtcrime.securesms.scribbles.widget.VerticalSlideColorPicker
|
||||
android:id="@+id/scribble_color_picker"
|
||||
android:layout_width="13dp"
|
||||
android:layout_height="300dp"
|
||||
android:layout_gravity="top|right"
|
||||
android:layout_marginRight="25dp"
|
||||
android:layout_marginTop="20dp"
|
||||
app:pickerBorderWidth="1dp"
|
||||
app:pickerBorderColor="@color/grey_600"
|
||||
app:pickerColors="@array/scribble_colors"/>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
|
89
res/layout/scribble_hud.xml
Normal file
@ -0,0 +1,89 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/scribble_save_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="top|left"
|
||||
android:src="@drawable/ic_check_white_24dp"
|
||||
android:padding="12dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="right|top"
|
||||
android:orientation="vertical"
|
||||
android:animateLayoutChanges="true">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/scribble_text_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/ic_text_fields_white_24dp"
|
||||
android:padding="12dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/scribble_draw_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/ic_brush_white_24dp"
|
||||
android:padding="12dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/scribble_highlight_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/baseline_highlight_white_24"
|
||||
android:padding="12dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/scribble_sticker_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/ic_tag_faces_white_24dp"
|
||||
android:padding="12dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/scribble_undo_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/ic_replay_white_24dp"
|
||||
android:padding="12dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/scribble_delete_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/ic_delete_white_24dp"
|
||||
android:padding="12dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"/>
|
||||
|
||||
<org.thoughtcrime.securesms.scribbles.widget.VerticalSlideColorPicker
|
||||
android:id="@+id/scribble_color_picker"
|
||||
android:layout_width="26dp"
|
||||
android:layout_height="170dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="12dp"
|
||||
app:pickerBorderWidth="4dp"
|
||||
app:pickerBorderColor="@color/white"
|
||||
app:pickerColors="@array/scribble_colors"/>
|
||||
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/scribble_color_palette"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_gravity="center_horizontal"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</merge>
|
@ -1,9 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge tools:parentTag="FrameLayout"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<ImageView android:id="@+id/image_view"
|
||||
android:layout_width="wrap_content"
|
||||
|
8
res/values-v19/themes.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="TextSecure.ScribbleTheme" parent="TextSecure.DarkNoActionBar">
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
<item name="android:windowFullscreen">true</item>
|
||||
<item name="android:windowTranslucentNavigation">true</item>
|
||||
</style>
|
||||
</resources>
|
@ -273,11 +273,15 @@
|
||||
</integer-array>
|
||||
|
||||
<array name="scribble_colors">
|
||||
<item>#000000</item>
|
||||
<item>#ffffff</item>
|
||||
<item>#ff0000</item>
|
||||
<item>#ffff00</item>
|
||||
<item>#00ffff</item>
|
||||
<item>#ff00ff</item>
|
||||
<item>#0000ff</item>
|
||||
<item>#00ffff</item>
|
||||
<item>#00ff00</item>
|
||||
<item>#ffff00</item>
|
||||
<item>#ff5500</item>
|
||||
<item>#000000</item>
|
||||
</array>
|
||||
|
||||
<string-array name="pref_message_font_size_entries">
|
||||
|
@ -54,7 +54,7 @@
|
||||
<color name="import_export_touch_highlight_light">#400099cc</color>
|
||||
<color name="import_export_touch_highlight_dark">#40ffffff</color>
|
||||
|
||||
<color name="sticker_selected_color">#8cf437</color>
|
||||
<color name="sticker_selected_color">#99ffffff</color>
|
||||
<color name="transparent">#00FFFFFF</color>
|
||||
|
||||
<color name="MediaOverview_Media_selected_overlay">#88000000</color>
|
||||
|
@ -58,7 +58,7 @@
|
||||
<dimen name="onboarding_title_size">34sp</dimen>
|
||||
<dimen name="onboarding_subtitle_size">20sp</dimen>
|
||||
|
||||
<dimen name="scribble_stroke_size">3dp</dimen>
|
||||
<dimen name="scribble_stroke_size">2dp</dimen>
|
||||
|
||||
<dimen name="floating_action_button_margin">16dp</dimen>
|
||||
|
||||
|
@ -373,4 +373,7 @@
|
||||
<item name="android:windowBackground">@drawable/permission_rationale_dialog_corners</item>
|
||||
</style>
|
||||
|
||||
<style name="TextSecure.ScribbleTheme" parent="TextSecure.DarkNoActionBar">
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
|
@ -5,7 +5,6 @@ import android.annotation.TargetApi;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.PointF;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
@ -18,7 +17,6 @@ import android.view.View;
|
||||
|
||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.providers.PersistentBlobProvider;
|
||||
@ -40,41 +38,37 @@ import java.io.IOException;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
|
||||
public class ScribbleActivity extends PassphraseRequiredActionBarActivity implements ScribbleToolbar.ScribbleToolbarListener, VerticalSlideColorPicker.OnColorChangeListener {
|
||||
public class ScribbleActivity extends PassphraseRequiredActionBarActivity implements ScribbleHud.EventListener, VerticalSlideColorPicker.OnColorChangeListener {
|
||||
|
||||
private static final String TAG = ScribbleActivity.class.getName();
|
||||
|
||||
public static final int SELECT_STICKER_REQUEST_CODE = 123;
|
||||
public static final int SCRIBBLE_REQUEST_CODE = 31424;
|
||||
|
||||
private VerticalSlideColorPicker colorPicker;
|
||||
private ScribbleToolbar toolbar;
|
||||
private ScribbleView scribbleView;
|
||||
private GlideRequests glideRequests;
|
||||
private ScribbleHud scribbleHud;
|
||||
private ScribbleView scribbleView;
|
||||
private GlideRequests glideRequests;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState, boolean ready) {
|
||||
setContentView(R.layout.scribble_activity);
|
||||
|
||||
this.glideRequests = GlideApp.with(this);
|
||||
this.scribbleHud = findViewById(R.id.scribble_hud);
|
||||
this.scribbleView = findViewById(R.id.scribble_view);
|
||||
this.toolbar = findViewById(R.id.toolbar);
|
||||
this.colorPicker = findViewById(R.id.scribble_color_picker);
|
||||
|
||||
this.toolbar.setListener(this);
|
||||
this.toolbar.setToolColor(Color.RED);
|
||||
scribbleHud.setEventListener(this);
|
||||
|
||||
scribbleView.setMotionViewCallback(motionViewCallback);
|
||||
scribbleView.setDrawingChangedListener(() -> scribbleHud.setColorPalette(scribbleView.getUniqueColors()));
|
||||
scribbleView.setDrawingMode(false);
|
||||
scribbleView.setImage(glideRequests, getIntent().getData());
|
||||
|
||||
colorPicker.setOnColorChangeListener(this);
|
||||
colorPicker.setVisibility(View.GONE);
|
||||
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
|
||||
getSupportActionBar().setTitle(null);
|
||||
if (Build.VERSION.SDK_INT >= 19) {
|
||||
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN |
|
||||
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY |
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
|
||||
}
|
||||
}
|
||||
|
||||
private void addSticker(final Bitmap pica) {
|
||||
@ -96,6 +90,7 @@ public class ScribbleActivity extends PassphraseRequiredActionBarActivity implem
|
||||
textEntity.getLayer().getFont().setColor(selectedColor);
|
||||
textEntity.updateEntity();
|
||||
scribbleView.invalidate();
|
||||
scribbleHud.setColorPalette(scribbleView.getUniqueColors());
|
||||
}
|
||||
|
||||
private void startTextEntityEditing() {
|
||||
@ -119,23 +114,21 @@ public class ScribbleActivity extends PassphraseRequiredActionBarActivity implem
|
||||
TextEntity textEntity = new TextEntity(textLayer, scribbleView.getWidth(), scribbleView.getHeight());
|
||||
scribbleView.addEntityAndPosition(textEntity);
|
||||
|
||||
// move text sticker up so that its not hidden under keyboard
|
||||
PointF center = textEntity.absoluteCenter();
|
||||
center.y = center.y * 0.5F;
|
||||
textEntity.moveCenterTo(center);
|
||||
|
||||
// redraw
|
||||
scribbleView.invalidate();
|
||||
|
||||
startTextEntityEditing();
|
||||
changeTextEntityColor(toolbar.getToolColor());
|
||||
changeTextEntityColor(scribbleHud.getActiveColor());
|
||||
}
|
||||
|
||||
private TextLayer createTextLayer() {
|
||||
TextLayer textLayer = new TextLayer();
|
||||
Font font = new Font();
|
||||
|
||||
font.setColor(TextLayer.Limits.INITIAL_FONT_COLOR);
|
||||
font.setColor(scribbleHud.getActiveColor());
|
||||
font.setSize(TextLayer.Limits.INITIAL_FONT_SIZE);
|
||||
|
||||
textLayer.setFont(font);
|
||||
@ -150,7 +143,6 @@ public class ScribbleActivity extends PassphraseRequiredActionBarActivity implem
|
||||
if (resultCode == RESULT_OK) {
|
||||
if (requestCode == SELECT_STICKER_REQUEST_CODE) {
|
||||
if (data != null) {
|
||||
toolbar.setStickerSelected(true);
|
||||
final String stickerFile = data.getStringExtra(StickerSelectActivity.EXTRA_STICKER_FILE);
|
||||
|
||||
new AsyncTask<Void, Void, Bitmap>() {
|
||||
@ -176,44 +168,52 @@ public class ScribbleActivity extends PassphraseRequiredActionBarActivity implem
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBrushSelected(boolean enabled) {
|
||||
scribbleView.setDrawingMode(enabled);
|
||||
colorPicker.setVisibility(enabled ? View.VISIBLE : View.GONE);
|
||||
public void onModeStarted(@NonNull ScribbleHud.Mode mode) {
|
||||
switch (mode) {
|
||||
case DRAW:
|
||||
scribbleView.setDrawingMode(true);
|
||||
scribbleView.setDrawingBrushWidth(ScribbleView.DEFAULT_BRUSH_WIDTH);
|
||||
break;
|
||||
|
||||
case HIGHLIGHT:
|
||||
scribbleView.setDrawingMode(true);
|
||||
scribbleView.setDrawingBrushWidth(ScribbleView.DEFAULT_BRUSH_WIDTH * 3);
|
||||
break;
|
||||
|
||||
case TEXT:
|
||||
scribbleView.setDrawingMode(false);
|
||||
addTextSticker();
|
||||
break;
|
||||
|
||||
case STICKER:
|
||||
scribbleView.setDrawingMode(false);
|
||||
Intent intent = new Intent(this, StickerSelectActivity.class);
|
||||
startActivityForResult(intent, SELECT_STICKER_REQUEST_CODE);
|
||||
break;
|
||||
|
||||
case NONE:
|
||||
scribbleView.clearSelection();
|
||||
scribbleView.setDrawingMode(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPaintUndo() {
|
||||
public void onColorChange(int color) {
|
||||
scribbleView.setDrawingBrushColor(color);
|
||||
changeTextEntityColor(color);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUndo() {
|
||||
scribbleView.undoDrawing();
|
||||
scribbleHud.setColorPalette(scribbleView.getUniqueColors());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextSelected(boolean enabled) {
|
||||
if (enabled) {
|
||||
addTextSticker();
|
||||
scribbleView.setDrawingMode(false);
|
||||
colorPicker.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
scribbleView.clearSelection();
|
||||
colorPicker.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStickerSelected(boolean enabled) {
|
||||
colorPicker.setVisibility(View.GONE);
|
||||
|
||||
if (!enabled) {
|
||||
scribbleView.clearSelection();
|
||||
} else {
|
||||
scribbleView.setDrawingMode(false);
|
||||
Intent intent = new Intent(this, StickerSelectActivity.class);
|
||||
startActivityForResult(intent, SELECT_STICKER_REQUEST_CODE);
|
||||
}
|
||||
}
|
||||
|
||||
public void onDeleteSelected() {
|
||||
public void onDelete() {
|
||||
scribbleView.deleteSelected();
|
||||
colorPicker.setVisibility(View.GONE);
|
||||
scribbleHud.setColorPalette(scribbleView.getUniqueColors());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -250,14 +250,14 @@ public class ScribbleActivity extends PassphraseRequiredActionBarActivity implem
|
||||
@Override
|
||||
public void onEntitySelected(@Nullable MotionEntity entity) {
|
||||
if (entity == null) {
|
||||
toolbar.setNoneSelected();
|
||||
colorPicker.setVisibility(View.GONE);
|
||||
scribbleHud.enterMode(ScribbleHud.Mode.NONE);
|
||||
} else if (entity instanceof TextEntity) {
|
||||
toolbar.setTextSelected(true);
|
||||
colorPicker.setVisibility(View.VISIBLE);
|
||||
int textColor = ((TextEntity) entity).getLayer().getFont().getColor();
|
||||
|
||||
scribbleHud.enterMode(ScribbleHud.Mode.TEXT);
|
||||
scribbleHud.setActiveColor(textColor);
|
||||
} else {
|
||||
toolbar.setStickerSelected(true);
|
||||
colorPicker.setVisibility(View.GONE);
|
||||
scribbleHud.enterMode(ScribbleHud.Mode.STICKER);
|
||||
}
|
||||
}
|
||||
|
||||
@ -266,14 +266,4 @@ public class ScribbleActivity extends PassphraseRequiredActionBarActivity implem
|
||||
startTextEntityEditing();
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void onColorChange(int color) {
|
||||
if (color == 0) color = Color.RED;
|
||||
|
||||
toolbar.setToolColor(color);
|
||||
scribbleView.setDrawingBrushColor(color);
|
||||
|
||||
changeTextEntityColor(color);
|
||||
}
|
||||
}
|
||||
|
250
src/org/thoughtcrime/securesms/scribbles/ScribbleHud.java
Normal file
@ -0,0 +1,250 @@
|
||||
package org.thoughtcrime.securesms.scribbles;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.scribbles.widget.ColorPaletteAdapter;
|
||||
import org.thoughtcrime.securesms.scribbles.widget.VerticalSlideColorPicker;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* The HUD (heads-up display) that contains all of the tools for interacting with
|
||||
* {@link org.thoughtcrime.securesms.scribbles.widget.ScribbleView}
|
||||
*/
|
||||
public class ScribbleHud extends FrameLayout {
|
||||
|
||||
private View drawButton;
|
||||
private View highlightButton;
|
||||
private View textButton;
|
||||
private View stickerButton;
|
||||
private View undoButton;
|
||||
private View deleteButton;
|
||||
private View saveButton;
|
||||
private VerticalSlideColorPicker colorPicker;
|
||||
private RecyclerView colorPalette;
|
||||
|
||||
private EventListener eventListener;
|
||||
private ColorPaletteAdapter colorPaletteAdapter;
|
||||
|
||||
public ScribbleHud(@NonNull Context context) {
|
||||
super(context);
|
||||
initialize();
|
||||
}
|
||||
|
||||
public ScribbleHud(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
initialize();
|
||||
}
|
||||
|
||||
public ScribbleHud(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
initialize();
|
||||
}
|
||||
|
||||
private void initialize() {
|
||||
inflate(getContext(), R.layout.scribble_hud, this);
|
||||
|
||||
drawButton = findViewById(R.id.scribble_draw_button);
|
||||
highlightButton = findViewById(R.id.scribble_highlight_button);
|
||||
textButton = findViewById(R.id.scribble_text_button);
|
||||
stickerButton = findViewById(R.id.scribble_sticker_button);
|
||||
undoButton = findViewById(R.id.scribble_undo_button);
|
||||
deleteButton = findViewById(R.id.scribble_delete_button);
|
||||
saveButton = findViewById(R.id.scribble_save_button);
|
||||
colorPicker = findViewById(R.id.scribble_color_picker);
|
||||
colorPalette = findViewById(R.id.scribble_color_palette);
|
||||
|
||||
undoButton.setOnClickListener(v -> {
|
||||
if (eventListener != null) {
|
||||
eventListener.onUndo();
|
||||
}
|
||||
});
|
||||
|
||||
deleteButton.setOnClickListener(v -> {
|
||||
if (eventListener != null) {
|
||||
eventListener.onDelete();
|
||||
}
|
||||
setMode(Mode.NONE);
|
||||
});
|
||||
|
||||
saveButton.setOnClickListener(v -> {
|
||||
if (eventListener != null) {
|
||||
eventListener.onSave();
|
||||
}
|
||||
setMode(Mode.NONE);
|
||||
});
|
||||
|
||||
colorPaletteAdapter = new ColorPaletteAdapter();
|
||||
colorPaletteAdapter.setEventListener(colorPicker::setActiveColor);
|
||||
|
||||
colorPalette.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
colorPalette.setAdapter(colorPaletteAdapter);
|
||||
|
||||
setMode(Mode.NONE);
|
||||
}
|
||||
|
||||
public void setColorPalette(@NonNull Set<Integer> colors) {
|
||||
colorPaletteAdapter.setColors(colors);
|
||||
}
|
||||
|
||||
public void enterMode(@NonNull Mode mode) {
|
||||
setMode(mode, false);
|
||||
}
|
||||
|
||||
private void setMode(@NonNull Mode mode) {
|
||||
setMode(mode, true);
|
||||
}
|
||||
|
||||
private void setMode(@NonNull Mode mode, boolean notify) {
|
||||
switch (mode) {
|
||||
case NONE: presentModeNone(); break;
|
||||
case DRAW: presentModeDraw(); break;
|
||||
case HIGHLIGHT: presentModeHighlight(); break;
|
||||
case TEXT: presentModeText(); break;
|
||||
case STICKER: presentModeSticker(); break;
|
||||
}
|
||||
|
||||
if (notify && eventListener != null) {
|
||||
eventListener.onModeStarted(mode);
|
||||
}
|
||||
}
|
||||
|
||||
private void presentModeNone() {
|
||||
drawButton.setVisibility(VISIBLE);
|
||||
highlightButton.setVisibility(VISIBLE);
|
||||
textButton.setVisibility(VISIBLE);
|
||||
stickerButton.setVisibility(VISIBLE);
|
||||
|
||||
undoButton.setVisibility(GONE);
|
||||
deleteButton.setVisibility(GONE);
|
||||
colorPicker.setVisibility(GONE);
|
||||
colorPalette.setVisibility(GONE);
|
||||
|
||||
drawButton.setOnClickListener(v -> setMode(Mode.DRAW));
|
||||
highlightButton.setOnClickListener(v -> setMode(Mode.HIGHLIGHT));
|
||||
textButton.setOnClickListener(v -> setMode(Mode.TEXT));
|
||||
stickerButton.setOnClickListener(v -> setMode(Mode.STICKER));
|
||||
}
|
||||
|
||||
private void presentModeDraw() {
|
||||
drawButton.setVisibility(VISIBLE);
|
||||
undoButton.setVisibility(VISIBLE);
|
||||
colorPicker.setVisibility(VISIBLE);
|
||||
colorPalette.setVisibility(VISIBLE);
|
||||
|
||||
highlightButton.setVisibility(GONE);
|
||||
textButton.setVisibility(GONE);
|
||||
stickerButton.setVisibility(GONE);
|
||||
deleteButton.setVisibility(GONE);
|
||||
|
||||
drawButton.setOnClickListener(v -> setMode(Mode.NONE));
|
||||
|
||||
colorPicker.setOnColorChangeListener(standardOnColorChangeListener);
|
||||
colorPicker.setActiveColor(Color.RED);
|
||||
}
|
||||
|
||||
private void presentModeHighlight() {
|
||||
highlightButton.setVisibility(VISIBLE);
|
||||
undoButton.setVisibility(VISIBLE);
|
||||
colorPicker.setVisibility(VISIBLE);
|
||||
colorPalette.setVisibility(VISIBLE);
|
||||
|
||||
drawButton.setVisibility(GONE);
|
||||
textButton.setVisibility(GONE);
|
||||
stickerButton.setVisibility(GONE);
|
||||
deleteButton.setVisibility(GONE);
|
||||
|
||||
highlightButton.setOnClickListener(v -> setMode(Mode.NONE));
|
||||
|
||||
colorPicker.setOnColorChangeListener(highlightOnColorChangeListener);
|
||||
colorPicker.setActiveColor(Color.YELLOW);
|
||||
}
|
||||
|
||||
private void presentModeText() {
|
||||
textButton.setVisibility(VISIBLE);
|
||||
deleteButton.setVisibility(VISIBLE);
|
||||
colorPicker.setVisibility(VISIBLE);
|
||||
colorPalette.setVisibility(VISIBLE);
|
||||
|
||||
drawButton.setVisibility(GONE);
|
||||
highlightButton.setVisibility(GONE);
|
||||
stickerButton.setVisibility(GONE);
|
||||
undoButton.setVisibility(GONE);
|
||||
|
||||
textButton.setOnClickListener(v -> setMode(Mode.NONE));
|
||||
|
||||
colorPicker.setOnColorChangeListener(standardOnColorChangeListener);
|
||||
colorPicker.setActiveColor(Color.WHITE);
|
||||
}
|
||||
|
||||
private void presentModeSticker() {
|
||||
stickerButton.setVisibility(VISIBLE);
|
||||
deleteButton.setVisibility(VISIBLE);
|
||||
|
||||
drawButton.setVisibility(GONE);
|
||||
highlightButton.setVisibility(GONE);
|
||||
textButton.setVisibility(GONE);
|
||||
undoButton.setVisibility(GONE);
|
||||
colorPicker.setVisibility(GONE);
|
||||
colorPalette.setVisibility(GONE);
|
||||
|
||||
stickerButton.setOnClickListener(v -> setMode(Mode.NONE));
|
||||
}
|
||||
|
||||
public int getActiveColor() {
|
||||
return colorPicker.getActiveColor();
|
||||
}
|
||||
|
||||
public void setActiveColor(int color) {
|
||||
colorPicker.setActiveColor(color);
|
||||
}
|
||||
|
||||
public void setEventListener(@Nullable EventListener eventListener) {
|
||||
this.eventListener = eventListener;
|
||||
}
|
||||
|
||||
private final VerticalSlideColorPicker.OnColorChangeListener standardOnColorChangeListener = new VerticalSlideColorPicker.OnColorChangeListener() {
|
||||
@Override
|
||||
public void onColorChange(int selectedColor) {
|
||||
if (eventListener != null) {
|
||||
eventListener.onColorChange(selectedColor);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private final VerticalSlideColorPicker.OnColorChangeListener highlightOnColorChangeListener = new VerticalSlideColorPicker.OnColorChangeListener() {
|
||||
@Override
|
||||
public void onColorChange(int selectedColor) {
|
||||
if (eventListener != null) {
|
||||
int r = Color.red(selectedColor);
|
||||
int g = Color.green(selectedColor);
|
||||
int b = Color.blue(selectedColor);
|
||||
int a = 128;
|
||||
|
||||
eventListener.onColorChange(Color.argb(a, r, g, b));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public enum Mode {
|
||||
NONE, DRAW, HIGHLIGHT, TEXT, STICKER
|
||||
}
|
||||
|
||||
public interface EventListener {
|
||||
void onModeStarted(@NonNull Mode mode);
|
||||
void onColorChange(int color);
|
||||
void onUndo();
|
||||
void onDelete();
|
||||
void onSave();
|
||||
}
|
||||
}
|
@ -1,240 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2016 Open Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.thoughtcrime.securesms.scribbles;
|
||||
|
||||
import android.animation.LayoutTransition;
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffColorFilter;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
|
||||
public class ScribbleToolbar extends Toolbar implements View.OnClickListener {
|
||||
|
||||
private enum Selected {
|
||||
NONE,
|
||||
STICKER,
|
||||
TEXT,
|
||||
BRUSH
|
||||
}
|
||||
|
||||
private int foregroundSelectedTint;
|
||||
private int foregroundUnselectedTint;
|
||||
|
||||
private LinearLayout toolsView;
|
||||
|
||||
private ImageView saveView;
|
||||
private ImageView brushView;
|
||||
private ImageView textView;
|
||||
private ImageView stickerView;
|
||||
|
||||
private ImageView separatorView;
|
||||
|
||||
private ImageView undoView;
|
||||
private ImageView deleteView;
|
||||
|
||||
private Drawable background;
|
||||
|
||||
@Nullable
|
||||
private ScribbleToolbarListener listener;
|
||||
|
||||
private int toolColor = Color.RED;
|
||||
private Selected selected = Selected.NONE;
|
||||
|
||||
public ScribbleToolbar(Context context) {
|
||||
super(context);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public ScribbleToolbar(Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public ScribbleToolbar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init(context);
|
||||
}
|
||||
|
||||
private void init(Context context) {
|
||||
inflate(context, R.layout.scribble_toolbar, this);
|
||||
|
||||
this.toolsView = (LinearLayout) findViewById(R.id.tools_view);
|
||||
this.brushView = (ImageView) findViewById(R.id.brush_button);
|
||||
this.textView = (ImageView) findViewById(R.id.text_button);
|
||||
this.stickerView = (ImageView) findViewById(R.id.sticker_button);
|
||||
this.separatorView = (ImageView) findViewById(R.id.separator);
|
||||
this.saveView = (ImageView) findViewById(R.id.save);
|
||||
|
||||
this.undoView = (ImageView) findViewById(R.id.undo);
|
||||
this.deleteView = (ImageView) findViewById(R.id.delete);
|
||||
|
||||
this.background = getResources().getDrawable(R.drawable.circle_tintable);
|
||||
this.foregroundSelectedTint = getResources().getColor(R.color.white);
|
||||
this.foregroundUnselectedTint = getResources().getColor(R.color.grey_800);
|
||||
|
||||
this.undoView.setOnClickListener(this);
|
||||
this.brushView.setOnClickListener(this);
|
||||
this.textView.setOnClickListener(this);
|
||||
this.stickerView.setOnClickListener(this);
|
||||
this.separatorView.setOnClickListener(this);
|
||||
this.deleteView.setOnClickListener(this);
|
||||
this.saveView.setOnClickListener(this);
|
||||
}
|
||||
|
||||
public void setListener(@Nullable ScribbleToolbarListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public void setToolColor(int toolColor) {
|
||||
this.toolColor = toolColor;
|
||||
this.background.setColorFilter(new PorterDuffColorFilter(toolColor, PorterDuff.Mode.MULTIPLY));
|
||||
}
|
||||
|
||||
public int getToolColor() {
|
||||
return this.toolColor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
this.toolsView.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING);
|
||||
|
||||
if (v == this.brushView) {
|
||||
boolean enabled = selected != Selected.BRUSH;
|
||||
setBrushSelected(enabled);
|
||||
if (listener != null) listener.onBrushSelected(enabled);
|
||||
} else if (v == this.stickerView) {
|
||||
setNoneSelected();
|
||||
if (listener != null) listener.onStickerSelected(true);
|
||||
} else if (v == this.textView) {
|
||||
boolean enabled = selected != Selected.TEXT;
|
||||
setTextSelected(enabled);
|
||||
if (listener != null) listener.onTextSelected(enabled);
|
||||
} else if (v == this.deleteView) {
|
||||
setNoneSelected();
|
||||
if (listener != null) listener.onDeleteSelected();
|
||||
} else if (v == this.undoView) {
|
||||
if (listener != null) listener.onPaintUndo();
|
||||
} else if (v == this.saveView) {
|
||||
if (listener != null) listener.onSave();
|
||||
}
|
||||
}
|
||||
|
||||
private void setBrushSelected(boolean enabled) {
|
||||
if (enabled) {
|
||||
|
||||
this.textView.setBackground(null);
|
||||
this.textView.setColorFilter(new PorterDuffColorFilter(foregroundUnselectedTint, PorterDuff.Mode.MULTIPLY));
|
||||
|
||||
this.brushView.setBackground(background);
|
||||
this.brushView.setColorFilter(new PorterDuffColorFilter(foregroundSelectedTint, PorterDuff.Mode.MULTIPLY));
|
||||
|
||||
this.stickerView.setBackground(null);
|
||||
this.stickerView.setColorFilter(new PorterDuffColorFilter(foregroundUnselectedTint, PorterDuff.Mode.MULTIPLY));
|
||||
|
||||
this.separatorView.setVisibility(View.VISIBLE);
|
||||
this.undoView.setVisibility(View.VISIBLE);
|
||||
this.deleteView.setVisibility(View.GONE);
|
||||
|
||||
this.selected = Selected.BRUSH;
|
||||
} else {
|
||||
this.brushView.setBackground(null);
|
||||
this.brushView.setColorFilter(new PorterDuffColorFilter(foregroundUnselectedTint, PorterDuff.Mode.MULTIPLY));
|
||||
this.separatorView.setVisibility(View.GONE);
|
||||
this.undoView.setVisibility(View.GONE);
|
||||
|
||||
this.selected = Selected.NONE;
|
||||
}
|
||||
}
|
||||
|
||||
public void setTextSelected(boolean enabled) {
|
||||
if (enabled) {
|
||||
this.brushView.setBackground(null);
|
||||
this.brushView.setColorFilter(new PorterDuffColorFilter(foregroundUnselectedTint, PorterDuff.Mode.MULTIPLY));
|
||||
|
||||
this.textView.setBackground(background);
|
||||
this.textView.setColorFilter(new PorterDuffColorFilter(foregroundSelectedTint, PorterDuff.Mode.MULTIPLY));
|
||||
|
||||
this.stickerView.setBackground(null);
|
||||
this.stickerView.setColorFilter(new PorterDuffColorFilter(foregroundUnselectedTint, PorterDuff.Mode.MULTIPLY));
|
||||
|
||||
this.separatorView.setVisibility(View.VISIBLE);
|
||||
this.undoView.setVisibility(View.GONE);
|
||||
this.deleteView.setVisibility(View.VISIBLE);
|
||||
|
||||
this.selected = Selected.TEXT;
|
||||
} else {
|
||||
this.textView.setBackground(null);
|
||||
this.textView.setColorFilter(new PorterDuffColorFilter(foregroundUnselectedTint, PorterDuff.Mode.MULTIPLY));
|
||||
|
||||
this.separatorView.setVisibility(View.GONE);
|
||||
this.deleteView.setVisibility(View.GONE);
|
||||
|
||||
this.selected = Selected.NONE;
|
||||
}
|
||||
}
|
||||
|
||||
public void setStickerSelected(boolean enabled) {
|
||||
if (enabled) {
|
||||
this.brushView.setBackground(null);
|
||||
this.brushView.setColorFilter(new PorterDuffColorFilter(foregroundUnselectedTint, PorterDuff.Mode.MULTIPLY));
|
||||
|
||||
this.textView.setBackground(null);
|
||||
this.textView.setColorFilter(new PorterDuffColorFilter(foregroundUnselectedTint, PorterDuff.Mode.MULTIPLY));
|
||||
|
||||
this.separatorView.setVisibility(View.VISIBLE);
|
||||
this.undoView.setVisibility(View.GONE);
|
||||
this.deleteView.setVisibility(View.VISIBLE);
|
||||
|
||||
this.selected = Selected.STICKER;
|
||||
} else {
|
||||
this.separatorView.setVisibility(View.GONE);
|
||||
this.deleteView.setVisibility(View.GONE);
|
||||
|
||||
this.selected = Selected.NONE;
|
||||
}
|
||||
}
|
||||
|
||||
public void setNoneSelected() {
|
||||
setBrushSelected(false);
|
||||
setStickerSelected(false);
|
||||
setTextSelected(false);
|
||||
|
||||
this.selected = Selected.NONE;
|
||||
}
|
||||
|
||||
public interface ScribbleToolbarListener {
|
||||
public void onBrushSelected(boolean enabled);
|
||||
public void onPaintUndo();
|
||||
public void onTextSelected(boolean enabled);
|
||||
public void onStickerSelected(boolean enabled);
|
||||
public void onDeleteSelected();
|
||||
public void onSave();
|
||||
}
|
||||
}
|
@ -88,7 +88,7 @@ public class TextLayer extends Layer {
|
||||
|
||||
float FONT_SIZE_STEP = 0.008F;
|
||||
|
||||
float INITIAL_FONT_SIZE = 0.075F;
|
||||
float INITIAL_FONT_SIZE = 0.1F;
|
||||
int INITIAL_FONT_COLOR = 0xff000000;
|
||||
|
||||
float INITIAL_SCALE = 0.8F; // set the same to avoid text scaling
|
||||
|
@ -20,15 +20,16 @@ import android.graphics.Path;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffXfermode;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.Typeface;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* This class defines fields and methods for drawing.
|
||||
@ -37,6 +38,8 @@ public class CanvasView extends View {
|
||||
|
||||
private static final String TAG = CanvasView.class.getSimpleName();
|
||||
|
||||
public static final int DEFAULT_STROKE_WIDTH = 15;
|
||||
|
||||
// Enumeration for Mode
|
||||
public enum Mode {
|
||||
DRAW,
|
||||
@ -78,7 +81,7 @@ public class CanvasView extends View {
|
||||
private Paint.Style paintStyle = Paint.Style.STROKE;
|
||||
private int paintStrokeColor = Color.BLACK;
|
||||
private int paintFillColor = Color.BLACK;
|
||||
private float paintStrokeWidth = 15F;
|
||||
private float paintStrokeWidth = DEFAULT_STROKE_WIDTH;
|
||||
private int opacity = 255;
|
||||
private float blur = 0F;
|
||||
private Paint.Cap lineCap = Paint.Cap.ROUND;
|
||||
@ -143,7 +146,7 @@ public class CanvasView extends View {
|
||||
paint.setStyle(this.paintStyle);
|
||||
paint.setStrokeWidth(this.paintStrokeWidth);
|
||||
paint.setStrokeCap(this.lineCap);
|
||||
paint.setStrokeJoin(Paint.Join.MITER); // fixed
|
||||
paint.setStrokeJoin(Paint.Join.ROUND); // fixed
|
||||
|
||||
if (this.mode == Mode.ERASER) {
|
||||
// Eraser
|
||||
@ -275,7 +278,9 @@ public class CanvasView extends View {
|
||||
|
||||
switch (this.drawer) {
|
||||
case PEN :
|
||||
path.lineTo(x, y);
|
||||
for (int i = 0; i < event.getHistorySize(); i++) {
|
||||
path.lineTo(event.getHistoricalX(i), event.getHistoricalY(i));
|
||||
}
|
||||
break;
|
||||
case LINE :
|
||||
path.reset();
|
||||
@ -770,4 +775,14 @@ public class CanvasView extends View {
|
||||
return this.getBitmapAsByteArray(CompressFormat.PNG, 100);
|
||||
}
|
||||
|
||||
public @NonNull Set<Integer> getUniqueColors() {
|
||||
Set<Integer> colors = new LinkedHashSet<>();
|
||||
|
||||
for (int i = 1; i < paintLists.size() && i < historyPointer; i++) {
|
||||
int color = paintLists.get(i).getColor();
|
||||
colors.add(Color.rgb(Color.red(color), Color.green(color), Color.blue(color)));
|
||||
}
|
||||
|
||||
return colors;
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
package org.thoughtcrime.securesms.scribbles.widget;
|
||||
|
||||
import android.graphics.PorterDuff;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public class ColorPaletteAdapter extends RecyclerView.Adapter<ColorPaletteAdapter.ColorViewHolder> {
|
||||
|
||||
private final List<Integer> colors = new ArrayList<>();
|
||||
|
||||
private EventListener eventListener;
|
||||
|
||||
@Override
|
||||
public ColorViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
return new ColorViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_color, parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(ColorViewHolder holder, int position) {
|
||||
holder.bind(colors.get(position), eventListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return colors.size();
|
||||
}
|
||||
|
||||
public void setColors(@NonNull Collection<Integer> colors) {
|
||||
this.colors.clear();
|
||||
this.colors.addAll(colors);
|
||||
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void setEventListener(@Nullable EventListener eventListener) {
|
||||
this.eventListener = eventListener;
|
||||
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public interface EventListener {
|
||||
void onColorSelected(int color);
|
||||
}
|
||||
|
||||
static class ColorViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
ImageView foreground;
|
||||
|
||||
ColorViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
foreground = itemView.findViewById(R.id.palette_item_foreground);
|
||||
}
|
||||
|
||||
void bind(int color, @Nullable EventListener eventListener) {
|
||||
foreground.setColorFilter(color, PorterDuff.Mode.SRC_IN);
|
||||
|
||||
if (eventListener != null) {
|
||||
itemView.setOnClickListener(v -> eventListener.onColorSelected(color));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -36,6 +36,7 @@ import android.support.v4.view.ViewCompat;
|
||||
import android.text.Editable;
|
||||
import android.text.InputType;
|
||||
import android.text.Selection;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
@ -56,7 +57,9 @@ import org.thoughtcrime.securesms.scribbles.widget.entity.MotionEntity;
|
||||
import org.thoughtcrime.securesms.scribbles.widget.entity.TextEntity;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class MotionView extends FrameLayout implements TextWatcher {
|
||||
|
||||
@ -182,6 +185,18 @@ public class MotionView extends FrameLayout implements TextWatcher {
|
||||
}
|
||||
}
|
||||
|
||||
public @NonNull Set<Integer> getUniqueColors() {
|
||||
Set<Integer> colors = new LinkedHashSet<>();
|
||||
|
||||
for (MotionEntity entity : entities) {
|
||||
if (entity instanceof TextEntity) {
|
||||
colors.add(((TextEntity) entity).getLayer().getFont().getColor());
|
||||
}
|
||||
}
|
||||
|
||||
return colors;
|
||||
}
|
||||
|
||||
private void initEntityBorder(@NonNull MotionEntity entity ) {
|
||||
// init stroke
|
||||
int strokeSize = getResources().getDimensionPixelSize(R.dimen.scribble_stroke_size);
|
||||
@ -274,14 +289,17 @@ public class MotionView extends FrameLayout implements TextWatcher {
|
||||
}
|
||||
|
||||
private void selectEntity(@Nullable MotionEntity entity, boolean updateCallback) {
|
||||
if (selectedEntity != null) {
|
||||
if (selectedEntity != null && entity != selectedEntity) {
|
||||
selectedEntity.setIsSelected(false);
|
||||
|
||||
if (selectedEntity instanceof TextEntity) {
|
||||
editText.clearComposingText();
|
||||
editText.clearFocus();
|
||||
|
||||
InputMethodManager imm = (InputMethodManager)getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
if (TextUtils.isEmpty(((TextEntity) selectedEntity).getLayer().getText())) {
|
||||
deletedSelectedEntity();
|
||||
} else {
|
||||
editText.clearComposingText();
|
||||
editText.clearFocus();
|
||||
}
|
||||
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.hideSoftInputFromWindow(editText.getWindowToken(), 0);
|
||||
}
|
||||
|
||||
@ -413,6 +431,12 @@ public class MotionView extends FrameLayout implements TextWatcher {
|
||||
updateSelectionOnTap(e);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDown(MotionEvent e) {
|
||||
updateSelectionOnTap(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
|
||||
|
@ -21,18 +21,22 @@ import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
import com.bumptech.glide.request.target.SimpleTarget;
|
||||
import com.bumptech.glide.request.target.Target;
|
||||
import com.bumptech.glide.request.transition.Transition;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
|
||||
@ -43,12 +47,15 @@ import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class ScribbleView extends FrameLayout {
|
||||
|
||||
private static final String TAG = ScribbleView.class.getSimpleName();
|
||||
|
||||
public static final int DEFAULT_BRUSH_WIDTH = CanvasView.DEFAULT_STROKE_WIDTH;
|
||||
|
||||
private ImageView imageView;
|
||||
private MotionView motionView;
|
||||
private CanvasView canvasView;
|
||||
@ -77,7 +84,7 @@ public class ScribbleView extends FrameLayout {
|
||||
}
|
||||
|
||||
public void setImage(@NonNull GlideRequests glideRequests, @NonNull Uri uri) {
|
||||
this.imageUri = uri;
|
||||
this.imageUri = uri;
|
||||
|
||||
glideRequests.load(new DecryptableUri(uri))
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
@ -85,7 +92,6 @@ public class ScribbleView extends FrameLayout {
|
||||
.into(imageView);
|
||||
}
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
public @NonNull ListenableFuture<Bitmap> getRenderedImage(@NonNull GlideRequests glideRequests) {
|
||||
final SettableFuture<Bitmap> future = new SettableFuture<>();
|
||||
final Context context = getContext();
|
||||
@ -96,43 +102,33 @@ public class ScribbleView extends FrameLayout {
|
||||
return future;
|
||||
}
|
||||
|
||||
new AsyncTask<Void, Void, Bitmap>() {
|
||||
@Override
|
||||
protected @Nullable Bitmap doInBackground(Void... params) {
|
||||
try {
|
||||
int width = Target.SIZE_ORIGINAL;
|
||||
int height = Target.SIZE_ORIGINAL;
|
||||
int width = Target.SIZE_ORIGINAL;
|
||||
int height = Target.SIZE_ORIGINAL;
|
||||
|
||||
if (isLowMemory) {
|
||||
width = 768;
|
||||
height = 768;
|
||||
}
|
||||
if (isLowMemory) {
|
||||
width = 768;
|
||||
height = 768;
|
||||
}
|
||||
|
||||
return glideRequests.asBitmap()
|
||||
.load(new DecryptableUri(imageUri))
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.skipMemoryCache(true)
|
||||
.into(width, height)
|
||||
.get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
Log.w(TAG, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
glideRequests.asBitmap()
|
||||
.load(new DecryptableUri(imageUri))
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.skipMemoryCache(true)
|
||||
.override(width, height)
|
||||
.into(new SimpleTarget<Bitmap>() {
|
||||
@Override
|
||||
public void onResourceReady(@NonNull Bitmap bitmap, @Nullable Transition<? super Bitmap> transition) {
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
motionView.render(canvas);
|
||||
canvasView.render(canvas);
|
||||
future.set(bitmap);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(@Nullable Bitmap bitmap) {
|
||||
if (bitmap == null) {
|
||||
future.set(null);
|
||||
return;
|
||||
}
|
||||
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
motionView.render(canvas);
|
||||
canvasView.render(canvas);
|
||||
future.set(bitmap);
|
||||
}
|
||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
@Override
|
||||
public void onLoadFailed(@Nullable Drawable errorDrawable) {
|
||||
future.set(null);
|
||||
}
|
||||
});
|
||||
|
||||
return future;
|
||||
}
|
||||
@ -149,6 +145,18 @@ public class ScribbleView extends FrameLayout {
|
||||
this.motionView.setMotionViewCallback(callback);
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
public void setDrawingChangedListener(@Nullable DrawingChangedListener listener) {
|
||||
this.canvasView.setOnTouchListener((v, event) -> {
|
||||
if (event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL) {
|
||||
if (listener != null) {
|
||||
listener.onDrawingChanged();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
public void setDrawingMode(boolean enabled) {
|
||||
this.canvasView.setActive(enabled);
|
||||
if (enabled) this.motionView.unselectEntity();
|
||||
@ -157,6 +165,11 @@ public class ScribbleView extends FrameLayout {
|
||||
public void setDrawingBrushColor(int color) {
|
||||
this.canvasView.setPaintFillColor(color);
|
||||
this.canvasView.setPaintStrokeColor(color);
|
||||
this.canvasView.setOpacity(Color.alpha(color));
|
||||
}
|
||||
|
||||
public void setDrawingBrushWidth(int width) {
|
||||
this.canvasView.setPaintStrokeWidth(width);
|
||||
}
|
||||
|
||||
public void addEntityAndPosition(MotionEntity entity) {
|
||||
@ -183,6 +196,15 @@ public class ScribbleView extends FrameLayout {
|
||||
this.motionView.startEditing(entity);
|
||||
}
|
||||
|
||||
public @NonNull Set<Integer> getUniqueColors() {
|
||||
Set<Integer> colors = new LinkedHashSet<>();
|
||||
|
||||
colors.addAll(motionView.getUniqueColors());
|
||||
colors.addAll(canvasView.getUniqueColors());
|
||||
|
||||
return colors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMeasure(int width, int height) {
|
||||
super.onMeasure(width, height);
|
||||
@ -196,4 +218,7 @@ public class ScribbleView extends FrameLayout {
|
||||
MeasureSpec.makeMeasureSpec(imageView.getMeasuredHeight(), MeasureSpec.EXACTLY));
|
||||
}
|
||||
|
||||
public interface DrawingChangedListener {
|
||||
void onDrawingChanged();
|
||||
}
|
||||
}
|
||||
|
@ -43,8 +43,12 @@ import org.thoughtcrime.securesms.R;
|
||||
|
||||
public class VerticalSlideColorPicker extends View {
|
||||
|
||||
private static final float INDICATOR_TO_BAR_WIDTH_RATIO = 0.8f;
|
||||
|
||||
private Paint paint;
|
||||
private Paint strokePaint;
|
||||
private Paint indicatorStrokePaint;
|
||||
private Paint indicatorFillPaint;
|
||||
private Path path;
|
||||
private Bitmap bitmap;
|
||||
private Canvas bitmapCanvas;
|
||||
@ -59,8 +63,12 @@ public class VerticalSlideColorPicker extends View {
|
||||
|
||||
private int borderColor;
|
||||
private float borderWidth;
|
||||
private float indicatorRadius;
|
||||
private int[] colors;
|
||||
|
||||
private int touchY;
|
||||
private int activeColor;
|
||||
|
||||
public VerticalSlideColorPicker(Context context) {
|
||||
super(context);
|
||||
init();
|
||||
@ -74,9 +82,9 @@ public class VerticalSlideColorPicker extends View {
|
||||
try {
|
||||
int colorsResourceId = a.getResourceId(R.styleable.VerticalSlideColorPicker_pickerColors, R.array.scribble_colors);
|
||||
|
||||
colors = a.getResources().getIntArray(colorsResourceId);
|
||||
borderColor = a.getColor(R.styleable.VerticalSlideColorPicker_pickerBorderColor, Color.WHITE);
|
||||
borderWidth = a.getDimension(R.styleable.VerticalSlideColorPicker_pickerBorderWidth, 10f);
|
||||
colors = a.getResources().getIntArray(colorsResourceId);
|
||||
borderColor = a.getColor(R.styleable.VerticalSlideColorPicker_pickerBorderColor, Color.WHITE);
|
||||
borderWidth = a.getDimension(R.styleable.VerticalSlideColorPicker_pickerBorderWidth, 10f);
|
||||
|
||||
} finally {
|
||||
a.recycle();
|
||||
@ -110,6 +118,13 @@ public class VerticalSlideColorPicker extends View {
|
||||
strokePaint.setColor(borderColor);
|
||||
strokePaint.setAntiAlias(true);
|
||||
strokePaint.setStrokeWidth(borderWidth);
|
||||
|
||||
indicatorStrokePaint = new Paint(strokePaint);
|
||||
indicatorStrokePaint.setStrokeWidth(borderWidth / 2);
|
||||
|
||||
indicatorFillPaint = new Paint();
|
||||
indicatorFillPaint.setStyle(Paint.Style.FILL);
|
||||
indicatorFillPaint.setAntiAlias(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -126,20 +141,27 @@ public class VerticalSlideColorPicker extends View {
|
||||
bitmapCanvas.drawPath(path, paint);
|
||||
|
||||
canvas.drawBitmap(bitmap, 0, 0, null);
|
||||
|
||||
touchY = Math.max((int) colorPickerBody.top, touchY);
|
||||
|
||||
indicatorFillPaint.setColor(activeColor);
|
||||
canvas.drawCircle(centerX, touchY, indicatorRadius, indicatorFillPaint);
|
||||
canvas.drawCircle(centerX, touchY, indicatorRadius, indicatorStrokePaint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
touchY = (int) Math.min(event.getY(), colorPickerBody.bottom);
|
||||
touchY = (int) Math.max(colorPickerBody.top, touchY);
|
||||
|
||||
float yPos = Math.min(event.getY(), colorPickerBody.bottom);
|
||||
yPos = Math.max(colorPickerBody.top, yPos);
|
||||
|
||||
int selectedColor = bitmap.getPixel(viewWidth/2, (int) yPos);
|
||||
activeColor = bitmap.getPixel(viewWidth/2, touchY);
|
||||
|
||||
if (onColorChangeListener != null) {
|
||||
onColorChangeListener.onColorChange(selectedColor);
|
||||
onColorChangeListener.onColorChange(activeColor);
|
||||
}
|
||||
|
||||
invalidate();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -147,13 +169,16 @@ public class VerticalSlideColorPicker extends View {
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
super.onSizeChanged(w, h, oldw, oldh);
|
||||
|
||||
viewWidth = w;
|
||||
viewWidth = w;
|
||||
viewHeight = h;
|
||||
|
||||
centerX = viewWidth / 2;
|
||||
colorPickerRadius = (viewWidth / 2) - borderWidth;
|
||||
int barWidth = (int) (viewWidth * INDICATOR_TO_BAR_WIDTH_RATIO);
|
||||
|
||||
colorPickerBody = new RectF(centerX - colorPickerRadius, borderWidth + colorPickerRadius, centerX + colorPickerRadius, viewHeight - (borderWidth + colorPickerRadius));
|
||||
centerX = viewWidth / 2;
|
||||
indicatorRadius = (viewWidth / 2) - borderWidth;
|
||||
colorPickerRadius = (barWidth / 2) - borderWidth;
|
||||
|
||||
colorPickerBody = new RectF(centerX - colorPickerRadius, borderWidth + colorPickerRadius, centerX + colorPickerRadius, viewHeight - (borderWidth + colorPickerRadius));
|
||||
|
||||
LinearGradient gradient = new LinearGradient(0, colorPickerBody.top, 0, colorPickerBody.bottom, colors, null, Shader.TileMode.CLAMP);
|
||||
paint.setShader(gradient);
|
||||
@ -164,8 +189,6 @@ public class VerticalSlideColorPicker extends View {
|
||||
|
||||
bitmap = Bitmap.createBitmap(viewWidth, viewHeight, Bitmap.Config.ARGB_8888);
|
||||
bitmapCanvas = new Canvas(bitmap);
|
||||
|
||||
resetToDefault();
|
||||
}
|
||||
|
||||
public void setBorderColor(int borderColor) {
|
||||
@ -183,20 +206,29 @@ public class VerticalSlideColorPicker extends View {
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void resetToDefault() {
|
||||
public void setActiveColor(int color) {
|
||||
activeColor = color;
|
||||
|
||||
if (colorPickerBody != null) {
|
||||
touchY = (int) colorPickerBody.top;
|
||||
}
|
||||
|
||||
if (onColorChangeListener != null) {
|
||||
onColorChangeListener.onColorChange(Color.RED);
|
||||
onColorChangeListener.onColorChange(color);
|
||||
}
|
||||
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public int getActiveColor() {
|
||||
return activeColor;
|
||||
}
|
||||
|
||||
public void setOnColorChangeListener(OnColorChangeListener onColorChangeListener) {
|
||||
this.onColorChangeListener = onColorChangeListener;
|
||||
}
|
||||
|
||||
public interface OnColorChangeListener {
|
||||
|
||||
void onColorChange(int selectedColor);
|
||||
}
|
||||
}
|