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"/>
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
<activity android:name=".scribbles.ScribbleActivity"
|
<activity android:name=".scribbles.ScribbleActivity"
|
||||||
android:theme="@style/TextSecure.LightNoActionBar"
|
android:theme="@style/TextSecure.ScribbleTheme"
|
||||||
android:windowSoftInputMode="stateHidden"
|
android:windowSoftInputMode="stateHidden"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
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"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:id="@+id/activity_main"
|
android:id="@+id/activity_main"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical"
|
android:background="@color/black"
|
||||||
tools:context="org.thoughtcrime.securesms.scribbles.ScribbleActivity">
|
tools:context="org.thoughtcrime.securesms.scribbles.ScribbleActivity">
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.scribbles.ScribbleToolbar
|
<org.thoughtcrime.securesms.scribbles.widget.ScribbleView
|
||||||
android:id="@+id/toolbar"
|
android:id="@+id/scribble_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="@color/white"/>
|
android:layout_gravity="center"
|
||||||
|
android:gravity="center" />
|
||||||
|
|
||||||
<FrameLayout android:layout_weight="1"
|
<org.thoughtcrime.securesms.scribbles.ScribbleHud
|
||||||
android:layout_width="match_parent"
|
android:id="@+id/scribble_hud"
|
||||||
android:layout_height="0dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_gravity="center"
|
android:layout_height="wrap_content" />
|
||||||
android:background="@color/grey_300"
|
|
||||||
android:gravity="center">
|
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.scribbles.widget.ScribbleView
|
</FrameLayout>
|
||||||
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>
|
|
||||||
|
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"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<merge tools:parentTag="FrameLayout"
|
<merge tools:parentTag="FrameLayout"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<ImageView android:id="@+id/image_view"
|
<ImageView android:id="@+id/image_view"
|
||||||
android:layout_width="wrap_content"
|
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>
|
</integer-array>
|
||||||
|
|
||||||
<array name="scribble_colors">
|
<array name="scribble_colors">
|
||||||
<item>#000000</item>
|
<item>#ffffff</item>
|
||||||
<item>#ff0000</item>
|
<item>#ff0000</item>
|
||||||
<item>#ffff00</item>
|
|
||||||
<item>#00ffff</item>
|
|
||||||
<item>#ff00ff</item>
|
<item>#ff00ff</item>
|
||||||
|
<item>#0000ff</item>
|
||||||
|
<item>#00ffff</item>
|
||||||
|
<item>#00ff00</item>
|
||||||
|
<item>#ffff00</item>
|
||||||
|
<item>#ff5500</item>
|
||||||
|
<item>#000000</item>
|
||||||
</array>
|
</array>
|
||||||
|
|
||||||
<string-array name="pref_message_font_size_entries">
|
<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_light">#400099cc</color>
|
||||||
<color name="import_export_touch_highlight_dark">#40ffffff</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="transparent">#00FFFFFF</color>
|
||||||
|
|
||||||
<color name="MediaOverview_Media_selected_overlay">#88000000</color>
|
<color name="MediaOverview_Media_selected_overlay">#88000000</color>
|
||||||
|
@ -58,7 +58,7 @@
|
|||||||
<dimen name="onboarding_title_size">34sp</dimen>
|
<dimen name="onboarding_title_size">34sp</dimen>
|
||||||
<dimen name="onboarding_subtitle_size">20sp</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>
|
<dimen name="floating_action_button_margin">16dp</dimen>
|
||||||
|
|
||||||
|
@ -373,4 +373,7 @@
|
|||||||
<item name="android:windowBackground">@drawable/permission_rationale_dialog_corners</item>
|
<item name="android:windowBackground">@drawable/permission_rationale_dialog_corners</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="TextSecure.ScribbleTheme" parent="TextSecure.DarkNoActionBar">
|
||||||
|
</style>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -5,7 +5,6 @@ import android.annotation.TargetApi;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.BitmapFactory;
|
import android.graphics.BitmapFactory;
|
||||||
import android.graphics.Color;
|
|
||||||
import android.graphics.PointF;
|
import android.graphics.PointF;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
@ -18,7 +17,6 @@ import android.view.View;
|
|||||||
|
|
||||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity;
|
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity;
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||||
import org.thoughtcrime.securesms.providers.PersistentBlobProvider;
|
import org.thoughtcrime.securesms.providers.PersistentBlobProvider;
|
||||||
@ -40,41 +38,37 @@ import java.io.IOException;
|
|||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
|
@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();
|
private static final String TAG = ScribbleActivity.class.getName();
|
||||||
|
|
||||||
public static final int SELECT_STICKER_REQUEST_CODE = 123;
|
public static final int SELECT_STICKER_REQUEST_CODE = 123;
|
||||||
public static final int SCRIBBLE_REQUEST_CODE = 31424;
|
public static final int SCRIBBLE_REQUEST_CODE = 31424;
|
||||||
|
|
||||||
private VerticalSlideColorPicker colorPicker;
|
private ScribbleHud scribbleHud;
|
||||||
private ScribbleToolbar toolbar;
|
private ScribbleView scribbleView;
|
||||||
private ScribbleView scribbleView;
|
private GlideRequests glideRequests;
|
||||||
private GlideRequests glideRequests;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState, boolean ready) {
|
protected void onCreate(Bundle savedInstanceState, boolean ready) {
|
||||||
setContentView(R.layout.scribble_activity);
|
setContentView(R.layout.scribble_activity);
|
||||||
|
|
||||||
this.glideRequests = GlideApp.with(this);
|
this.glideRequests = GlideApp.with(this);
|
||||||
|
this.scribbleHud = findViewById(R.id.scribble_hud);
|
||||||
this.scribbleView = findViewById(R.id.scribble_view);
|
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);
|
scribbleHud.setEventListener(this);
|
||||||
this.toolbar.setToolColor(Color.RED);
|
|
||||||
|
|
||||||
scribbleView.setMotionViewCallback(motionViewCallback);
|
scribbleView.setMotionViewCallback(motionViewCallback);
|
||||||
|
scribbleView.setDrawingChangedListener(() -> scribbleHud.setColorPalette(scribbleView.getUniqueColors()));
|
||||||
scribbleView.setDrawingMode(false);
|
scribbleView.setDrawingMode(false);
|
||||||
scribbleView.setImage(glideRequests, getIntent().getData());
|
scribbleView.setImage(glideRequests, getIntent().getData());
|
||||||
|
|
||||||
colorPicker.setOnColorChangeListener(this);
|
if (Build.VERSION.SDK_INT >= 19) {
|
||||||
colorPicker.setVisibility(View.GONE);
|
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN |
|
||||||
|
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY |
|
||||||
setSupportActionBar(toolbar);
|
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
|
||||||
|
}
|
||||||
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
|
|
||||||
getSupportActionBar().setTitle(null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addSticker(final Bitmap pica) {
|
private void addSticker(final Bitmap pica) {
|
||||||
@ -96,6 +90,7 @@ public class ScribbleActivity extends PassphraseRequiredActionBarActivity implem
|
|||||||
textEntity.getLayer().getFont().setColor(selectedColor);
|
textEntity.getLayer().getFont().setColor(selectedColor);
|
||||||
textEntity.updateEntity();
|
textEntity.updateEntity();
|
||||||
scribbleView.invalidate();
|
scribbleView.invalidate();
|
||||||
|
scribbleHud.setColorPalette(scribbleView.getUniqueColors());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startTextEntityEditing() {
|
private void startTextEntityEditing() {
|
||||||
@ -119,23 +114,21 @@ public class ScribbleActivity extends PassphraseRequiredActionBarActivity implem
|
|||||||
TextEntity textEntity = new TextEntity(textLayer, scribbleView.getWidth(), scribbleView.getHeight());
|
TextEntity textEntity = new TextEntity(textLayer, scribbleView.getWidth(), scribbleView.getHeight());
|
||||||
scribbleView.addEntityAndPosition(textEntity);
|
scribbleView.addEntityAndPosition(textEntity);
|
||||||
|
|
||||||
// move text sticker up so that its not hidden under keyboard
|
|
||||||
PointF center = textEntity.absoluteCenter();
|
PointF center = textEntity.absoluteCenter();
|
||||||
center.y = center.y * 0.5F;
|
center.y = center.y * 0.5F;
|
||||||
textEntity.moveCenterTo(center);
|
textEntity.moveCenterTo(center);
|
||||||
|
|
||||||
// redraw
|
|
||||||
scribbleView.invalidate();
|
scribbleView.invalidate();
|
||||||
|
|
||||||
startTextEntityEditing();
|
startTextEntityEditing();
|
||||||
changeTextEntityColor(toolbar.getToolColor());
|
changeTextEntityColor(scribbleHud.getActiveColor());
|
||||||
}
|
}
|
||||||
|
|
||||||
private TextLayer createTextLayer() {
|
private TextLayer createTextLayer() {
|
||||||
TextLayer textLayer = new TextLayer();
|
TextLayer textLayer = new TextLayer();
|
||||||
Font font = new Font();
|
Font font = new Font();
|
||||||
|
|
||||||
font.setColor(TextLayer.Limits.INITIAL_FONT_COLOR);
|
font.setColor(scribbleHud.getActiveColor());
|
||||||
font.setSize(TextLayer.Limits.INITIAL_FONT_SIZE);
|
font.setSize(TextLayer.Limits.INITIAL_FONT_SIZE);
|
||||||
|
|
||||||
textLayer.setFont(font);
|
textLayer.setFont(font);
|
||||||
@ -150,7 +143,6 @@ public class ScribbleActivity extends PassphraseRequiredActionBarActivity implem
|
|||||||
if (resultCode == RESULT_OK) {
|
if (resultCode == RESULT_OK) {
|
||||||
if (requestCode == SELECT_STICKER_REQUEST_CODE) {
|
if (requestCode == SELECT_STICKER_REQUEST_CODE) {
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
toolbar.setStickerSelected(true);
|
|
||||||
final String stickerFile = data.getStringExtra(StickerSelectActivity.EXTRA_STICKER_FILE);
|
final String stickerFile = data.getStringExtra(StickerSelectActivity.EXTRA_STICKER_FILE);
|
||||||
|
|
||||||
new AsyncTask<Void, Void, Bitmap>() {
|
new AsyncTask<Void, Void, Bitmap>() {
|
||||||
@ -176,44 +168,52 @@ public class ScribbleActivity extends PassphraseRequiredActionBarActivity implem
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBrushSelected(boolean enabled) {
|
public void onModeStarted(@NonNull ScribbleHud.Mode mode) {
|
||||||
scribbleView.setDrawingMode(enabled);
|
switch (mode) {
|
||||||
colorPicker.setVisibility(enabled ? View.VISIBLE : View.GONE);
|
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
|
@Override
|
||||||
public void onPaintUndo() {
|
public void onColorChange(int color) {
|
||||||
|
scribbleView.setDrawingBrushColor(color);
|
||||||
|
changeTextEntityColor(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUndo() {
|
||||||
scribbleView.undoDrawing();
|
scribbleView.undoDrawing();
|
||||||
|
scribbleHud.setColorPalette(scribbleView.getUniqueColors());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTextSelected(boolean enabled) {
|
public void onDelete() {
|
||||||
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() {
|
|
||||||
scribbleView.deleteSelected();
|
scribbleView.deleteSelected();
|
||||||
colorPicker.setVisibility(View.GONE);
|
scribbleHud.setColorPalette(scribbleView.getUniqueColors());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -250,14 +250,14 @@ public class ScribbleActivity extends PassphraseRequiredActionBarActivity implem
|
|||||||
@Override
|
@Override
|
||||||
public void onEntitySelected(@Nullable MotionEntity entity) {
|
public void onEntitySelected(@Nullable MotionEntity entity) {
|
||||||
if (entity == null) {
|
if (entity == null) {
|
||||||
toolbar.setNoneSelected();
|
scribbleHud.enterMode(ScribbleHud.Mode.NONE);
|
||||||
colorPicker.setVisibility(View.GONE);
|
|
||||||
} else if (entity instanceof TextEntity) {
|
} else if (entity instanceof TextEntity) {
|
||||||
toolbar.setTextSelected(true);
|
int textColor = ((TextEntity) entity).getLayer().getFont().getColor();
|
||||||
colorPicker.setVisibility(View.VISIBLE);
|
|
||||||
|
scribbleHud.enterMode(ScribbleHud.Mode.TEXT);
|
||||||
|
scribbleHud.setActiveColor(textColor);
|
||||||
} else {
|
} else {
|
||||||
toolbar.setStickerSelected(true);
|
scribbleHud.enterMode(ScribbleHud.Mode.STICKER);
|
||||||
colorPicker.setVisibility(View.GONE);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -266,14 +266,4 @@ public class ScribbleActivity extends PassphraseRequiredActionBarActivity implem
|
|||||||
startTextEntityEditing();
|
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 FONT_SIZE_STEP = 0.008F;
|
||||||
|
|
||||||
float INITIAL_FONT_SIZE = 0.075F;
|
float INITIAL_FONT_SIZE = 0.1F;
|
||||||
int INITIAL_FONT_COLOR = 0xff000000;
|
int INITIAL_FONT_COLOR = 0xff000000;
|
||||||
|
|
||||||
float INITIAL_SCALE = 0.8F; // set the same to avoid text scaling
|
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.PorterDuff;
|
||||||
import android.graphics.PorterDuffXfermode;
|
import android.graphics.PorterDuffXfermode;
|
||||||
import android.graphics.RectF;
|
import android.graphics.RectF;
|
||||||
import android.graphics.Typeface;
|
import android.support.annotation.NonNull;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.util.Log;
|
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class defines fields and methods for drawing.
|
* 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();
|
private static final String TAG = CanvasView.class.getSimpleName();
|
||||||
|
|
||||||
|
public static final int DEFAULT_STROKE_WIDTH = 15;
|
||||||
|
|
||||||
// Enumeration for Mode
|
// Enumeration for Mode
|
||||||
public enum Mode {
|
public enum Mode {
|
||||||
DRAW,
|
DRAW,
|
||||||
@ -78,7 +81,7 @@ public class CanvasView extends View {
|
|||||||
private Paint.Style paintStyle = Paint.Style.STROKE;
|
private Paint.Style paintStyle = Paint.Style.STROKE;
|
||||||
private int paintStrokeColor = Color.BLACK;
|
private int paintStrokeColor = Color.BLACK;
|
||||||
private int paintFillColor = Color.BLACK;
|
private int paintFillColor = Color.BLACK;
|
||||||
private float paintStrokeWidth = 15F;
|
private float paintStrokeWidth = DEFAULT_STROKE_WIDTH;
|
||||||
private int opacity = 255;
|
private int opacity = 255;
|
||||||
private float blur = 0F;
|
private float blur = 0F;
|
||||||
private Paint.Cap lineCap = Paint.Cap.ROUND;
|
private Paint.Cap lineCap = Paint.Cap.ROUND;
|
||||||
@ -143,7 +146,7 @@ public class CanvasView extends View {
|
|||||||
paint.setStyle(this.paintStyle);
|
paint.setStyle(this.paintStyle);
|
||||||
paint.setStrokeWidth(this.paintStrokeWidth);
|
paint.setStrokeWidth(this.paintStrokeWidth);
|
||||||
paint.setStrokeCap(this.lineCap);
|
paint.setStrokeCap(this.lineCap);
|
||||||
paint.setStrokeJoin(Paint.Join.MITER); // fixed
|
paint.setStrokeJoin(Paint.Join.ROUND); // fixed
|
||||||
|
|
||||||
if (this.mode == Mode.ERASER) {
|
if (this.mode == Mode.ERASER) {
|
||||||
// Eraser
|
// Eraser
|
||||||
@ -275,7 +278,9 @@ public class CanvasView extends View {
|
|||||||
|
|
||||||
switch (this.drawer) {
|
switch (this.drawer) {
|
||||||
case PEN :
|
case PEN :
|
||||||
path.lineTo(x, y);
|
for (int i = 0; i < event.getHistorySize(); i++) {
|
||||||
|
path.lineTo(event.getHistoricalX(i), event.getHistoricalY(i));
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case LINE :
|
case LINE :
|
||||||
path.reset();
|
path.reset();
|
||||||
@ -770,4 +775,14 @@ public class CanvasView extends View {
|
|||||||
return this.getBitmapAsByteArray(CompressFormat.PNG, 100);
|
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.Editable;
|
||||||
import android.text.InputType;
|
import android.text.InputType;
|
||||||
import android.text.Selection;
|
import android.text.Selection;
|
||||||
|
import android.text.TextUtils;
|
||||||
import android.text.TextWatcher;
|
import android.text.TextWatcher;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.util.Log;
|
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 org.thoughtcrime.securesms.scribbles.widget.entity.TextEntity;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
public class MotionView extends FrameLayout implements TextWatcher {
|
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 ) {
|
private void initEntityBorder(@NonNull MotionEntity entity ) {
|
||||||
// init stroke
|
// init stroke
|
||||||
int strokeSize = getResources().getDimensionPixelSize(R.dimen.scribble_stroke_size);
|
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) {
|
private void selectEntity(@Nullable MotionEntity entity, boolean updateCallback) {
|
||||||
if (selectedEntity != null) {
|
if (selectedEntity != null && entity != selectedEntity) {
|
||||||
selectedEntity.setIsSelected(false);
|
selectedEntity.setIsSelected(false);
|
||||||
|
|
||||||
if (selectedEntity instanceof TextEntity) {
|
if (selectedEntity instanceof TextEntity) {
|
||||||
editText.clearComposingText();
|
if (TextUtils.isEmpty(((TextEntity) selectedEntity).getLayer().getText())) {
|
||||||
editText.clearFocus();
|
deletedSelectedEntity();
|
||||||
|
} else {
|
||||||
InputMethodManager imm = (InputMethodManager)getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
|
editText.clearComposingText();
|
||||||
|
editText.clearFocus();
|
||||||
|
}
|
||||||
|
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||||
imm.hideSoftInputFromWindow(editText.getWindowToken(), 0);
|
imm.hideSoftInputFromWindow(editText.getWindowToken(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -413,6 +431,12 @@ public class MotionView extends FrameLayout implements TextWatcher {
|
|||||||
updateSelectionOnTap(e);
|
updateSelectionOnTap(e);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onDown(MotionEvent e) {
|
||||||
|
updateSelectionOnTap(e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
|
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
|
||||||
|
@ -21,18 +21,22 @@ import android.annotation.TargetApi;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.util.Log;
|
import android.view.MotionEvent;
|
||||||
|
import android.view.View;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
|
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
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.target.Target;
|
||||||
|
import com.bumptech.glide.request.transition.Transition;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
|
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.ListenableFuture;
|
||||||
import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
|
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 {
|
public class ScribbleView extends FrameLayout {
|
||||||
|
|
||||||
private static final String TAG = ScribbleView.class.getSimpleName();
|
private static final String TAG = ScribbleView.class.getSimpleName();
|
||||||
|
|
||||||
|
public static final int DEFAULT_BRUSH_WIDTH = CanvasView.DEFAULT_STROKE_WIDTH;
|
||||||
|
|
||||||
private ImageView imageView;
|
private ImageView imageView;
|
||||||
private MotionView motionView;
|
private MotionView motionView;
|
||||||
private CanvasView canvasView;
|
private CanvasView canvasView;
|
||||||
@ -77,7 +84,7 @@ public class ScribbleView extends FrameLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setImage(@NonNull GlideRequests glideRequests, @NonNull Uri uri) {
|
public void setImage(@NonNull GlideRequests glideRequests, @NonNull Uri uri) {
|
||||||
this.imageUri = uri;
|
this.imageUri = uri;
|
||||||
|
|
||||||
glideRequests.load(new DecryptableUri(uri))
|
glideRequests.load(new DecryptableUri(uri))
|
||||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||||
@ -85,7 +92,6 @@ public class ScribbleView extends FrameLayout {
|
|||||||
.into(imageView);
|
.into(imageView);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("StaticFieldLeak")
|
|
||||||
public @NonNull ListenableFuture<Bitmap> getRenderedImage(@NonNull GlideRequests glideRequests) {
|
public @NonNull ListenableFuture<Bitmap> getRenderedImage(@NonNull GlideRequests glideRequests) {
|
||||||
final SettableFuture<Bitmap> future = new SettableFuture<>();
|
final SettableFuture<Bitmap> future = new SettableFuture<>();
|
||||||
final Context context = getContext();
|
final Context context = getContext();
|
||||||
@ -96,43 +102,33 @@ public class ScribbleView extends FrameLayout {
|
|||||||
return future;
|
return future;
|
||||||
}
|
}
|
||||||
|
|
||||||
new AsyncTask<Void, Void, Bitmap>() {
|
int width = Target.SIZE_ORIGINAL;
|
||||||
@Override
|
int height = Target.SIZE_ORIGINAL;
|
||||||
protected @Nullable Bitmap doInBackground(Void... params) {
|
|
||||||
try {
|
|
||||||
int width = Target.SIZE_ORIGINAL;
|
|
||||||
int height = Target.SIZE_ORIGINAL;
|
|
||||||
|
|
||||||
if (isLowMemory) {
|
if (isLowMemory) {
|
||||||
width = 768;
|
width = 768;
|
||||||
height = 768;
|
height = 768;
|
||||||
}
|
}
|
||||||
|
|
||||||
return glideRequests.asBitmap()
|
glideRequests.asBitmap()
|
||||||
.load(new DecryptableUri(imageUri))
|
.load(new DecryptableUri(imageUri))
|
||||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||||
.skipMemoryCache(true)
|
.skipMemoryCache(true)
|
||||||
.into(width, height)
|
.override(width, height)
|
||||||
.get();
|
.into(new SimpleTarget<Bitmap>() {
|
||||||
} catch (InterruptedException | ExecutionException e) {
|
@Override
|
||||||
Log.w(TAG, e);
|
public void onResourceReady(@NonNull Bitmap bitmap, @Nullable Transition<? super Bitmap> transition) {
|
||||||
return null;
|
Canvas canvas = new Canvas(bitmap);
|
||||||
}
|
motionView.render(canvas);
|
||||||
}
|
canvasView.render(canvas);
|
||||||
|
future.set(bitmap);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPostExecute(@Nullable Bitmap bitmap) {
|
public void onLoadFailed(@Nullable Drawable errorDrawable) {
|
||||||
if (bitmap == null) {
|
future.set(null);
|
||||||
future.set(null);
|
}
|
||||||
return;
|
});
|
||||||
}
|
|
||||||
|
|
||||||
Canvas canvas = new Canvas(bitmap);
|
|
||||||
motionView.render(canvas);
|
|
||||||
canvasView.render(canvas);
|
|
||||||
future.set(bitmap);
|
|
||||||
}
|
|
||||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
|
||||||
|
|
||||||
return future;
|
return future;
|
||||||
}
|
}
|
||||||
@ -149,6 +145,18 @@ public class ScribbleView extends FrameLayout {
|
|||||||
this.motionView.setMotionViewCallback(callback);
|
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) {
|
public void setDrawingMode(boolean enabled) {
|
||||||
this.canvasView.setActive(enabled);
|
this.canvasView.setActive(enabled);
|
||||||
if (enabled) this.motionView.unselectEntity();
|
if (enabled) this.motionView.unselectEntity();
|
||||||
@ -157,6 +165,11 @@ public class ScribbleView extends FrameLayout {
|
|||||||
public void setDrawingBrushColor(int color) {
|
public void setDrawingBrushColor(int color) {
|
||||||
this.canvasView.setPaintFillColor(color);
|
this.canvasView.setPaintFillColor(color);
|
||||||
this.canvasView.setPaintStrokeColor(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) {
|
public void addEntityAndPosition(MotionEntity entity) {
|
||||||
@ -183,6 +196,15 @@ public class ScribbleView extends FrameLayout {
|
|||||||
this.motionView.startEditing(entity);
|
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
|
@Override
|
||||||
public void onMeasure(int width, int height) {
|
public void onMeasure(int width, int height) {
|
||||||
super.onMeasure(width, height);
|
super.onMeasure(width, height);
|
||||||
@ -196,4 +218,7 @@ public class ScribbleView extends FrameLayout {
|
|||||||
MeasureSpec.makeMeasureSpec(imageView.getMeasuredHeight(), MeasureSpec.EXACTLY));
|
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 {
|
public class VerticalSlideColorPicker extends View {
|
||||||
|
|
||||||
|
private static final float INDICATOR_TO_BAR_WIDTH_RATIO = 0.8f;
|
||||||
|
|
||||||
private Paint paint;
|
private Paint paint;
|
||||||
private Paint strokePaint;
|
private Paint strokePaint;
|
||||||
|
private Paint indicatorStrokePaint;
|
||||||
|
private Paint indicatorFillPaint;
|
||||||
private Path path;
|
private Path path;
|
||||||
private Bitmap bitmap;
|
private Bitmap bitmap;
|
||||||
private Canvas bitmapCanvas;
|
private Canvas bitmapCanvas;
|
||||||
@ -59,8 +63,12 @@ public class VerticalSlideColorPicker extends View {
|
|||||||
|
|
||||||
private int borderColor;
|
private int borderColor;
|
||||||
private float borderWidth;
|
private float borderWidth;
|
||||||
|
private float indicatorRadius;
|
||||||
private int[] colors;
|
private int[] colors;
|
||||||
|
|
||||||
|
private int touchY;
|
||||||
|
private int activeColor;
|
||||||
|
|
||||||
public VerticalSlideColorPicker(Context context) {
|
public VerticalSlideColorPicker(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
init();
|
init();
|
||||||
@ -74,9 +82,9 @@ public class VerticalSlideColorPicker extends View {
|
|||||||
try {
|
try {
|
||||||
int colorsResourceId = a.getResourceId(R.styleable.VerticalSlideColorPicker_pickerColors, R.array.scribble_colors);
|
int colorsResourceId = a.getResourceId(R.styleable.VerticalSlideColorPicker_pickerColors, R.array.scribble_colors);
|
||||||
|
|
||||||
colors = a.getResources().getIntArray(colorsResourceId);
|
colors = a.getResources().getIntArray(colorsResourceId);
|
||||||
borderColor = a.getColor(R.styleable.VerticalSlideColorPicker_pickerBorderColor, Color.WHITE);
|
borderColor = a.getColor(R.styleable.VerticalSlideColorPicker_pickerBorderColor, Color.WHITE);
|
||||||
borderWidth = a.getDimension(R.styleable.VerticalSlideColorPicker_pickerBorderWidth, 10f);
|
borderWidth = a.getDimension(R.styleable.VerticalSlideColorPicker_pickerBorderWidth, 10f);
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
a.recycle();
|
a.recycle();
|
||||||
@ -110,6 +118,13 @@ public class VerticalSlideColorPicker extends View {
|
|||||||
strokePaint.setColor(borderColor);
|
strokePaint.setColor(borderColor);
|
||||||
strokePaint.setAntiAlias(true);
|
strokePaint.setAntiAlias(true);
|
||||||
strokePaint.setStrokeWidth(borderWidth);
|
strokePaint.setStrokeWidth(borderWidth);
|
||||||
|
|
||||||
|
indicatorStrokePaint = new Paint(strokePaint);
|
||||||
|
indicatorStrokePaint.setStrokeWidth(borderWidth / 2);
|
||||||
|
|
||||||
|
indicatorFillPaint = new Paint();
|
||||||
|
indicatorFillPaint.setStyle(Paint.Style.FILL);
|
||||||
|
indicatorFillPaint.setAntiAlias(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -126,20 +141,27 @@ public class VerticalSlideColorPicker extends View {
|
|||||||
bitmapCanvas.drawPath(path, paint);
|
bitmapCanvas.drawPath(path, paint);
|
||||||
|
|
||||||
canvas.drawBitmap(bitmap, 0, 0, null);
|
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
|
@Override
|
||||||
public boolean onTouchEvent(MotionEvent event) {
|
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);
|
activeColor = bitmap.getPixel(viewWidth/2, touchY);
|
||||||
yPos = Math.max(colorPickerBody.top, yPos);
|
|
||||||
|
|
||||||
int selectedColor = bitmap.getPixel(viewWidth/2, (int) yPos);
|
|
||||||
|
|
||||||
if (onColorChangeListener != null) {
|
if (onColorChangeListener != null) {
|
||||||
onColorChangeListener.onColorChange(selectedColor);
|
onColorChangeListener.onColorChange(activeColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
invalidate();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,13 +169,16 @@ public class VerticalSlideColorPicker extends View {
|
|||||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||||
super.onSizeChanged(w, h, oldw, oldh);
|
super.onSizeChanged(w, h, oldw, oldh);
|
||||||
|
|
||||||
viewWidth = w;
|
viewWidth = w;
|
||||||
viewHeight = h;
|
viewHeight = h;
|
||||||
|
|
||||||
centerX = viewWidth / 2;
|
int barWidth = (int) (viewWidth * INDICATOR_TO_BAR_WIDTH_RATIO);
|
||||||
colorPickerRadius = (viewWidth / 2) - borderWidth;
|
|
||||||
|
|
||||||
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);
|
LinearGradient gradient = new LinearGradient(0, colorPickerBody.top, 0, colorPickerBody.bottom, colors, null, Shader.TileMode.CLAMP);
|
||||||
paint.setShader(gradient);
|
paint.setShader(gradient);
|
||||||
@ -164,8 +189,6 @@ public class VerticalSlideColorPicker extends View {
|
|||||||
|
|
||||||
bitmap = Bitmap.createBitmap(viewWidth, viewHeight, Bitmap.Config.ARGB_8888);
|
bitmap = Bitmap.createBitmap(viewWidth, viewHeight, Bitmap.Config.ARGB_8888);
|
||||||
bitmapCanvas = new Canvas(bitmap);
|
bitmapCanvas = new Canvas(bitmap);
|
||||||
|
|
||||||
resetToDefault();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setBorderColor(int borderColor) {
|
public void setBorderColor(int borderColor) {
|
||||||
@ -183,20 +206,29 @@ public class VerticalSlideColorPicker extends View {
|
|||||||
invalidate();
|
invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void resetToDefault() {
|
public void setActiveColor(int color) {
|
||||||
|
activeColor = color;
|
||||||
|
|
||||||
|
if (colorPickerBody != null) {
|
||||||
|
touchY = (int) colorPickerBody.top;
|
||||||
|
}
|
||||||
|
|
||||||
if (onColorChangeListener != null) {
|
if (onColorChangeListener != null) {
|
||||||
onColorChangeListener.onColorChange(Color.RED);
|
onColorChangeListener.onColorChange(color);
|
||||||
}
|
}
|
||||||
|
|
||||||
invalidate();
|
invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getActiveColor() {
|
||||||
|
return activeColor;
|
||||||
|
}
|
||||||
|
|
||||||
public void setOnColorChangeListener(OnColorChangeListener onColorChangeListener) {
|
public void setOnColorChangeListener(OnColorChangeListener onColorChangeListener) {
|
||||||
this.onColorChangeListener = onColorChangeListener;
|
this.onColorChangeListener = onColorChangeListener;
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface OnColorChangeListener {
|
public interface OnColorChangeListener {
|
||||||
|
|
||||||
void onColorChange(int selectedColor);
|
void onColorChange(int selectedColor);
|
||||||
}
|
}
|
||||||
}
|
}
|