resources) {
25 | this.resources = resources;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/element/shape/ArcElement.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.element.shape;
2 |
3 | import android.graphics.Path;
4 |
5 | public class ArcElement extends BoxShapeElement {
6 |
7 | @Override
8 | public Object clone() {
9 | ArcElement element = new ArcElement();
10 | cloneTo(element);
11 | return element;
12 | }
13 |
14 | @Override
15 | protected Path createShapePath() {
16 | Path path = new Path();
17 | path.moveTo(shapeVector.getPoint1().x, shapeVector.getPoint1().y);
18 | path.quadTo(shapeVector.getPoint1().x, shapeVector.getPoint2().y, shapeVector.getPoint2().x, shapeVector.getPoint2().y);
19 | return path;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/cidrawingsample/src/main/res/menu/menu_arrange.xml:
--------------------------------------------------------------------------------
1 |
4 |
7 |
10 |
13 |
16 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 |
15 |
16 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/element/PathElement.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.element;
2 |
3 | import android.graphics.Matrix;
4 | import android.graphics.Path;
5 |
6 | public class PathElement extends BasePathElement {
7 |
8 | public PathElement() {
9 | }
10 |
11 | public void setElementPath(Path elementPath) {
12 | this.elementPath = elementPath;
13 | updateBoundingBox();
14 | }
15 |
16 | @Override
17 | public void applyMatrixForData(Matrix matrix) {
18 | super.applyMatrixForData(matrix);
19 |
20 | elementPath.transform(matrix);
21 | }
22 |
23 | @Override
24 | public Object clone() {
25 | PathElement element = new PathElement();
26 | cloneTo(element);
27 | return element;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/view/DrawingViewProxy.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.view;
2 |
3 | import android.graphics.Canvas;
4 | import android.view.MotionEvent;
5 | import android.view.View;
6 |
7 | /**
8 | * A interface delegates the view behavior.
9 | */
10 | public interface DrawingViewProxy {
11 |
12 | /**
13 | * Delegates method: {@link View#onDraw(Canvas)}
14 | *
15 | * @param canvas the canvas on which the background will be drawn
16 | */
17 | void onDraw(Canvas canvas);
18 |
19 | /**
20 | * Delegates method: {@link View#onTouchEvent(MotionEvent)}
21 | *
22 | * @param event the motion event
23 | * @return True if the event was handled, false otherwise.
24 | */
25 | boolean onTouchEvent(MotionEvent event);
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/element/shape/IsoscelesTriangleElement.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.element.shape;
2 |
3 | import android.graphics.Path;
4 |
5 | public class IsoscelesTriangleElement extends BoxShapeElement {
6 |
7 | @Override
8 | public Object clone() {
9 | IsoscelesTriangleElement element = new IsoscelesTriangleElement();
10 | cloneTo(element);
11 | return element;
12 | }
13 |
14 | @Override
15 | protected Path createShapePath() {
16 | Path path = new Path();
17 | path.moveTo(shapeBox.left, shapeBox.bottom);
18 | path.lineTo(shapeBox.centerX(), shapeBox.top);
19 | path.lineTo(shapeBox.right, shapeBox.bottom);
20 | path.lineTo(shapeBox.left, shapeBox.bottom);
21 | return path;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/ConfigManagerImpl.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing;
2 |
3 | /**
4 | * The default implementation of {@link ConfigManager}.
5 | */
6 | public class ConfigManagerImpl implements ConfigManager {
7 |
8 | private boolean debugMode = false;
9 | private DrawingType drawingType = DrawingType.Vector;
10 |
11 | @Override
12 | public boolean isDebugMode() {
13 | return debugMode;
14 | }
15 |
16 | @Override
17 | public void setDebugMode(boolean debugMode) {
18 | this.debugMode = debugMode;
19 | }
20 |
21 | @Override
22 | public DrawingType getDrawingType() {
23 | return drawingType;
24 | }
25 |
26 | @Override
27 | public void setDrawingType(DrawingType type) {
28 | this.drawingType = type;
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/operation/ReshapeOperation.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.operation;
2 |
3 | import android.graphics.Matrix;
4 |
5 | public class ReshapeOperation extends SingleSelectedElementOperation {
6 |
7 | private Matrix displayMatrix;
8 |
9 | @Override
10 | public boolean doOperation() {
11 | displayMatrix = selectedElement.applyDisplayMatrixToData();
12 | selectedElement.resetReferencePoint();
13 | drawingBoard.getDrawingView().notifyViewUpdated();
14 | return true;
15 | }
16 |
17 | @Override
18 | public void undo() {
19 | if (selectedElement != null) {
20 | selectedElement.restoreDisplayMatrixFromData(displayMatrix);
21 | drawingBoard.getDrawingView().notifyViewUpdated();
22 | }
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/cidrawinglib/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/cidrawingsample/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/cidrawingsample/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
10 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/mode/DrawingMode.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.mode;
2 |
3 | import android.view.MotionEvent;
4 |
5 | /**
6 | * A drawing mode defines the behavior when interacting with the view.
7 | */
8 | public interface DrawingMode {
9 |
10 | /**
11 | * Sets drawing board id
12 | *
13 | * @param boardId board id
14 | */
15 | void setDrawingBoardId(String boardId);
16 |
17 | /**
18 | * Called when switch to this mode.
19 | */
20 | void onEnterMode();
21 |
22 | /**
23 | * Called when switch to other mode.
24 | */
25 | void onLeaveMode();
26 |
27 | /**
28 | * Delegates the view touch event.
29 | *
30 | * @param event the motion event
31 | * @return True if the event was handled, false otherwise.
32 | */
33 | boolean onTouchEvent(MotionEvent event);
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/operation/SingleSelectedElementOperation.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.operation;
2 |
3 | import com.mocircle.cidrawing.board.ElementManager;
4 | import com.mocircle.cidrawing.element.DrawElement;
5 |
6 | public abstract class SingleSelectedElementOperation extends AbstractOperation {
7 |
8 | protected ElementManager elementManager;
9 | protected DrawElement selectedElement;
10 |
11 | @Override
12 | public void setDrawingBoardId(String boardId) {
13 | super.setDrawingBoardId(boardId);
14 | elementManager = drawingBoard.getElementManager();
15 | }
16 |
17 | @Override
18 | public boolean isExecutable() {
19 | if (selectedElement == null) {
20 | selectedElement = elementManager.getSelection().getSingleElement();
21 | }
22 | return selectedElement != null;
23 | }
24 |
25 | }
--------------------------------------------------------------------------------
/cidrawinglib/src/androidTest/java/com/mocircle/cidrawing/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumented test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.mocircle.cidrawing.test", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/element/behavior/Resizable.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.element.behavior;
2 |
3 | import android.graphics.Canvas;
4 |
5 | public interface Resizable {
6 |
7 | enum AspectRatioResetMethod {
8 | WIDTH_FIRST,
9 | HEIGHT_FIRST,
10 | SMALL_FIRST,
11 | LARGE_FIRST
12 | }
13 |
14 | boolean isResizingEnabled();
15 |
16 | void setResizingEnabled(boolean resizingEnabled);
17 |
18 | boolean isLockAspectRatio();
19 |
20 | void setLockAspectRatio(boolean lockAspectRatio);
21 |
22 | void resetAspectRatio(AspectRatioResetMethod method);
23 |
24 | void resize(float sx, float sy, float px, float py);
25 |
26 | void resizeTo(float width, float height, float px, float py);
27 |
28 | void drawResizingHandle(Canvas canvas);
29 |
30 | ResizingDirection hitTestForResizingHandle(float x, float y);
31 | }
32 |
--------------------------------------------------------------------------------
/cidrawingsample/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
15 |
16 |
17 |
18 |
19 |
20 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/cidrawingsample/src/androidTest/java/com/mocircle/cidrawingsample/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawingsample;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumented test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.mocircle.cidrawingsample", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/board/LayerManager.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.board;
2 |
3 | public interface LayerManager {
4 |
5 | interface LayerChangeListener {
6 | void onLayerChanged();
7 | }
8 |
9 | Layer[] getLayers();
10 |
11 | Layer getCurrentLayer();
12 |
13 | void selectLayer(Layer selectedLayer);
14 |
15 | Layer selectFirstVisibleLayer();
16 |
17 | Layer createNewLayer();
18 |
19 | Layer createNewLayer(String name);
20 |
21 | void addLayer(Layer layer);
22 |
23 | void removeLayer(Layer layer);
24 |
25 | void removeAllLayers();
26 |
27 | void showLayer(Layer layer);
28 |
29 | void showAllLayers();
30 |
31 | void hideLayer(Layer layer);
32 |
33 | void hideAllLayers();
34 |
35 | void addLayerChangeListener(LayerChangeListener listener);
36 |
37 | void removeLayerChangeListener(LayerChangeListener listener);
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/board/ArrangeStrategy.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.board;
2 |
3 | /**
4 | * Indicate how to handle multiple elements arrangement.
5 | */
6 | public enum ArrangeStrategy {
7 |
8 | /**
9 | * If elements shift out of the list, this strategy will try to shift each element to the furthest
10 | * distance but still keep the same order between elements.
11 | * For example:
12 | *
13 | * Before arrange: [A,B,C,D,E]
14 | * Move: A,C -> 5 steps
15 | * After arrange: [B,D,E,A,C]
16 | *
17 | */
18 | AS_MUCH_AS_POSSIBLE,
19 |
20 | /**
21 | * If elements shift out of the list, this strategy will keep the same distance and order between elements.
22 | * For example:
23 | *
24 | * Before arrange: [A,B,C,D,E]
25 | * Move: A,C -> 5 steps
26 | * After arrange: [B,D,A,E,C]
27 | *
28 | */
29 | WITH_FIXED_DISTANCE
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/cidrawingsample/src/main/res/menu/menu_insert_shape.xml:
--------------------------------------------------------------------------------
1 |
4 |
7 |
10 |
13 |
16 |
19 |
22 |
25 |
28 |
29 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/operation/MovePointOperation.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.operation;
2 |
3 | import android.graphics.PointF;
4 |
5 | public class MovePointOperation extends AbstractOperation {
6 |
7 | private PointF point;
8 | private float deltaX;
9 | private float deltaY;
10 |
11 | public MovePointOperation(PointF point, float deltaX, float deltaY) {
12 | this.point = point;
13 | this.deltaX = deltaX;
14 | this.deltaY = deltaY;
15 | }
16 |
17 | @Override
18 | public boolean isExecutable() {
19 | return point != null;
20 | }
21 |
22 | @Override
23 | public boolean doOperation() {
24 | point.offset(deltaX, deltaY);
25 | drawingBoard.getDrawingView().notifyViewUpdated();
26 | return true;
27 | }
28 |
29 | @Override
30 | public void undo() {
31 | point.offset(-deltaX, -deltaY);
32 | drawingBoard.getDrawingView().notifyViewUpdated();
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/operation/SelectedElementsOperation.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.operation;
2 |
3 | import com.mocircle.cidrawing.board.ElementManager;
4 | import com.mocircle.cidrawing.element.DrawElement;
5 |
6 | import java.util.List;
7 |
8 | public abstract class SelectedElementsOperation extends AbstractOperation {
9 |
10 | protected ElementManager elementManager;
11 | protected List elements;
12 |
13 | @Override
14 | public void setDrawingBoardId(String boardId) {
15 | super.setDrawingBoardId(boardId);
16 | elementManager = drawingBoard.getElementManager();
17 | }
18 |
19 | @Override
20 | public boolean isExecutable() {
21 | if (elements == null) {
22 | // Get current selected elements
23 | elements = elementManager.getSelection().getElements();
24 | }
25 | return elements.size() >= minimumSelectedElements();
26 | }
27 |
28 | protected int minimumSelectedElements() {
29 | return 2;
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/cidrawingsample/src/main/res/menu/menu_more.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
8 |
12 |
16 |
20 |
24 |
28 |
--------------------------------------------------------------------------------
/cidrawinglib/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion 28
5 | buildToolsVersion "29.0.0"
6 |
7 |
8 | defaultConfig {
9 | minSdkVersion 15
10 | targetSdkVersion 28
11 | versionCode 1
12 | versionName "1.0"
13 |
14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
15 |
16 | }
17 |
18 | buildTypes {
19 | release {
20 | minifyEnabled false
21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
22 | }
23 | }
24 |
25 | }
26 |
27 | dependencies {
28 | implementation fileTree(dir: 'libs', include: ['*.jar'])
29 | implementation 'com.android.support:appcompat-v7:28.0.0'
30 |
31 | testImplementation 'junit:junit:4.12'
32 | testImplementation 'org.robolectric:robolectric:3.1.4'
33 |
34 | androidTestImplementation 'com.android.support.test:runner:1.0.2'
35 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
36 | }
37 |
--------------------------------------------------------------------------------
/cidrawingsample/src/main/res/layout/layer_item_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
16 |
17 |
25 |
26 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: android
2 |
3 | jdk:
4 | - oraclejdk8
5 |
6 | android:
7 | components:
8 | # Uncomment the lines below if you want to
9 | # use the latest revision of Android SDK Tools
10 | - platform-tools
11 | - tools
12 |
13 | # The BuildTools version used by your project
14 | - build-tools-29.0.0
15 |
16 | # The SDK version used to compile your project
17 | - android-28
18 |
19 | # Additional components
20 | - extra-google-m2repository
21 | - extra-android-m2repository
22 | - extra-android-support
23 |
24 | # Specify at least one system image,
25 | # if you need to run emulator(s) during your tests
26 | - sys-img-armeabi-v7a-android-28
27 |
28 | before_script:
29 | - chmod +x gradlew
30 | #- echo no | android create avd --force -n test -t android-23 --abi armeabi-v7a
31 | #- emulator -avd test -no-audio -no-window &
32 | #- android-wait-for-emulator
33 | #- adb shell input keyevent 82 &
34 |
35 | script:
36 | - ./gradlew assemble
37 | - ./gradlew test
38 | #- ./gradlew connectedAndroidTest
39 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/board/ElementManager.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.board;
2 |
3 | import com.mocircle.cidrawing.element.DrawElement;
4 |
5 | import java.util.List;
6 |
7 | public interface ElementManager extends LayerManager {
8 |
9 | DrawElement[] getVisibleObjects();
10 |
11 | DrawElement[] getVisibleElements();
12 |
13 | DrawElement[] getVisibleAdornments();
14 |
15 | DrawElement[] getCurrentObjects();
16 |
17 | DrawElement[] getCurrentElements();
18 |
19 | DrawElement[] getCurrentAdornments();
20 |
21 | void addElementToCurrentLayer(DrawElement element);
22 |
23 | void removeElementFromCurrentLayer(DrawElement element);
24 |
25 | void addAdornmentToCurrentLayer(DrawElement element);
26 |
27 | void removeAdornmentFromCurrentLayer(DrawElement element);
28 |
29 | void selectElement(DrawElement element);
30 |
31 | void selectElements(List elements);
32 |
33 | Selection getSelection();
34 |
35 | void clearSelection();
36 |
37 | DrawElement getFirstHitElement(float x, float y);
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/utils/MatrixUtils.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.utils;
2 |
3 | import android.graphics.Matrix;
4 |
5 | /**
6 | * Matrix utility class.
7 | */
8 | public final class MatrixUtils {
9 |
10 | private MatrixUtils() {
11 | }
12 |
13 | /**
14 | * Gets invert matrix.
15 | *
16 | * @param matrix original matrix
17 | * @return invert matrix
18 | */
19 | public static Matrix getInvertMatrix(Matrix matrix) {
20 | Matrix invertMatrix = new Matrix();
21 | matrix.invert(invertMatrix);
22 | return invertMatrix;
23 | }
24 |
25 | /**
26 | * Calculates the transformation matrix according to M(transformation) * M(source) = M(target).
27 | *
28 | * @param source source matrix
29 | * @param target target matrix
30 | * @return delta matrix
31 | */
32 | public static Matrix getTransformationMatrix(Matrix source, Matrix target) {
33 | Matrix delta = new Matrix();
34 | source.invert(delta);
35 | delta.postConcat(target);
36 | return delta;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/mode/eraser/ObjectEraserMode.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.mode.eraser;
2 |
3 | import com.mocircle.cidrawing.board.ElementManager;
4 | import com.mocircle.cidrawing.element.DrawElement;
5 | import com.mocircle.cidrawing.mode.BasePointMode;
6 | import com.mocircle.cidrawing.operation.OperationManager;
7 | import com.mocircle.cidrawing.operation.RemoveElementOperation;
8 |
9 | public class ObjectEraserMode extends BasePointMode {
10 |
11 | private ElementManager elementManager;
12 | private OperationManager operationManager;
13 |
14 | @Override
15 | public void setDrawingBoardId(String boardId) {
16 | super.setDrawingBoardId(boardId);
17 | elementManager = drawingBoard.getElementManager();
18 | operationManager = drawingBoard.getOperationManager();
19 | }
20 |
21 | @Override
22 | protected void onOverPoint(float x, float y) {
23 | DrawElement element = elementManager.getFirstHitElement(x, y);
24 | if (element != null) {
25 | operationManager.executeOperation(new RemoveElementOperation(element));
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/cidrawingsample/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 28
5 | buildToolsVersion "29.0.0"
6 |
7 |
8 | defaultConfig {
9 | applicationId "com.mocircle.cidrawingsample"
10 | minSdkVersion 15
11 | targetSdkVersion 28
12 | versionCode 1
13 | versionName "1.0"
14 |
15 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
16 |
17 | }
18 |
19 | buildTypes {
20 | release {
21 | minifyEnabled false
22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
23 | }
24 | }
25 |
26 | }
27 |
28 | dependencies {
29 | implementation fileTree(dir: 'libs', include: ['*.jar'])
30 | implementation project(':cidrawinglib')
31 |
32 | implementation 'com.android.support:appcompat-v7:28.0.0'
33 | implementation 'com.android.support:design:28.0.0'
34 | testImplementation 'junit:junit:4.12'
35 | androidTestImplementation 'com.android.support.test:runner:1.0.2'
36 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
37 | }
38 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/operation/DisplayTransformOperation.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.operation;
2 |
3 | import android.graphics.Matrix;
4 |
5 | import com.mocircle.cidrawing.element.DrawElement;
6 | import com.mocircle.cidrawing.utils.MatrixUtils;
7 |
8 | public class DisplayTransformOperation extends AbstractOperation {
9 |
10 | protected DrawElement element;
11 | protected Matrix deltaMatrix;
12 |
13 | public DisplayTransformOperation(DrawElement element, Matrix deltaMatrix) {
14 | this.element = element;
15 | this.deltaMatrix = deltaMatrix;
16 | }
17 |
18 | @Override
19 | public boolean isExecutable() {
20 | return element != null;
21 | }
22 |
23 | @Override
24 | public boolean doOperation() {
25 | element.getDisplayMatrix().postConcat(deltaMatrix);
26 | drawingBoard.getDrawingView().notifyViewUpdated();
27 | return true;
28 | }
29 |
30 | @Override
31 | public void undo() {
32 | element.getDisplayMatrix().postConcat(MatrixUtils.getInvertMatrix(deltaMatrix));
33 | drawingBoard.getDrawingView().notifyViewUpdated();
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/operation/DataTransformOperation.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.operation;
2 |
3 | import android.graphics.Matrix;
4 |
5 | import com.mocircle.cidrawing.element.DrawElement;
6 | import com.mocircle.cidrawing.utils.MatrixUtils;
7 |
8 | public class DataTransformOperation extends AbstractOperation {
9 |
10 | private DrawElement element;
11 | private Matrix deltaMatrix;
12 |
13 | public DataTransformOperation(DrawElement element, Matrix deltaMatrix) {
14 | this.element = element;
15 | this.deltaMatrix = deltaMatrix;
16 | }
17 |
18 | @Override
19 | public boolean isExecutable() {
20 | return element != null;
21 | }
22 |
23 | @Override
24 | public boolean doOperation() {
25 | element.applyMatrixForData(deltaMatrix);
26 | element.updateBoundingBox();
27 | drawingBoard.getDrawingView().notifyViewUpdated();
28 | return true;
29 | }
30 |
31 | @Override
32 | public void undo() {
33 | element.applyMatrixForData(MatrixUtils.getInvertMatrix(deltaMatrix));
34 | element.updateBoundingBox();
35 | drawingBoard.getDrawingView().notifyViewUpdated();
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/mode/AutoDetectedElementOperationMode.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.mode;
2 |
3 | import android.view.MotionEvent;
4 |
5 | public class AutoDetectedElementOperationMode extends ElementOperationMode {
6 |
7 | protected boolean autoDetectMode;
8 |
9 | public AutoDetectedElementOperationMode() {
10 | }
11 |
12 | public AutoDetectedElementOperationMode(boolean autoDetectMode) {
13 | this.autoDetectMode = autoDetectMode;
14 | }
15 |
16 | public boolean isAutoDetectMode() {
17 | return autoDetectMode;
18 | }
19 |
20 | public void setAutoDetectMode(boolean autoDetectMode) {
21 | this.autoDetectMode = autoDetectMode;
22 | }
23 |
24 | @Override
25 | public boolean onTouchEvent(MotionEvent event) {
26 | switch (event.getAction()) {
27 | case MotionEvent.ACTION_DOWN:
28 | if (autoDetectMode) {
29 | detectElement(event.getX(), event.getY());
30 | }
31 | }
32 | return super.onTouchEvent(event);
33 | }
34 |
35 | protected void detectElement(float x, float y) {
36 | setElement(elementManager.getFirstHitElement(x, y));
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/operation/InsertElementOperation.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.operation;
2 |
3 | import com.mocircle.cidrawing.board.ElementManager;
4 | import com.mocircle.cidrawing.element.DrawElement;
5 |
6 | public class InsertElementOperation extends AbstractOperation {
7 |
8 | private ElementManager elementManager;
9 | private DrawElement element;
10 |
11 | public InsertElementOperation(DrawElement element) {
12 | this.element = element;
13 | }
14 |
15 | @Override
16 | public void setDrawingBoardId(String boardId) {
17 | super.setDrawingBoardId(boardId);
18 | elementManager = drawingBoard.getElementManager();
19 | }
20 |
21 | @Override
22 | public boolean isExecutable() {
23 | return element != null;
24 | }
25 |
26 | @Override
27 | public boolean doOperation() {
28 | elementManager.addElementToCurrentLayer(element);
29 | drawingBoard.getDrawingView().notifyViewUpdated();
30 | return true;
31 | }
32 |
33 | @Override
34 | public void undo() {
35 | elementManager.removeElementFromCurrentLayer(element);
36 | drawingBoard.getDrawingView().notifyViewUpdated();
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/operation/RemoveElementOperation.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.operation;
2 |
3 | import com.mocircle.cidrawing.board.ElementManager;
4 | import com.mocircle.cidrawing.element.DrawElement;
5 |
6 | public class RemoveElementOperation extends AbstractOperation {
7 |
8 | private ElementManager elementManager;
9 | private DrawElement element;
10 |
11 | public RemoveElementOperation(DrawElement element) {
12 | this.element = element;
13 | }
14 |
15 | @Override
16 | public void setDrawingBoardId(String boardId) {
17 | super.setDrawingBoardId(boardId);
18 | elementManager = drawingBoard.getElementManager();
19 | }
20 |
21 | @Override
22 | public boolean isExecutable() {
23 | return element != null;
24 | }
25 |
26 | @Override
27 | public boolean doOperation() {
28 | elementManager.removeElementFromCurrentLayer(element);
29 | drawingBoard.getDrawingView().notifyViewUpdated();
30 | return true;
31 | }
32 |
33 | @Override
34 | public void undo() {
35 | elementManager.addElementToCurrentLayer(element);
36 | drawingBoard.getDrawingView().notifyViewUpdated();
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/element/behavior/Selectable.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.element.behavior;
2 |
3 | import android.graphics.Canvas;
4 | import android.graphics.Path;
5 |
6 | public interface Selectable {
7 |
8 | enum SelectionStyle {
9 | FULL,
10 | LIGHT
11 | }
12 |
13 | boolean isSelectionEnabled();
14 |
15 | void setSelectionEnabled(boolean selectionEnabled);
16 |
17 | boolean isSelected();
18 |
19 | void setSelected(boolean selected);
20 |
21 | void setSelected(boolean selected, SelectionStyle selectionStyle);
22 |
23 | SelectionStyle getSelectionStyle();
24 |
25 | void drawSelection(Canvas canvas);
26 |
27 | /**
28 | * Checks if the given point is inside the element's touch area.
29 | *
30 | * @param x axis x of given point
31 | * @param y axis y of given point
32 | * @return true if hit test successful, otherwise false.
33 | */
34 | boolean hitTestForSelection(float x, float y);
35 |
36 | /**
37 | * Checks if the given path is including the element's touch area.
38 | *
39 | * @param path given path
40 | * @return true if hit test successful, otherwise false.
41 | */
42 | boolean hitTestForSelection(Path path);
43 | }
44 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/mode/selection/ShapeSelectionMode.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.mode.selection;
2 |
3 | import android.graphics.Path;
4 | import android.view.MotionEvent;
5 |
6 | import com.mocircle.cidrawing.core.Vector2;
7 | import com.mocircle.cidrawing.element.shape.ShapeElement;
8 |
9 | public abstract class ShapeSelectionMode extends SelectionMode {
10 |
11 | private static final String TAG = "ShapeSelectionMode";
12 |
13 | public ShapeSelectionMode() {
14 | }
15 |
16 | @Override
17 | public boolean onTouchEvent(MotionEvent event) {
18 | switch (event.getAction()) {
19 | case MotionEvent.ACTION_MOVE:
20 | if (selectionElement instanceof ShapeElement) {
21 | ((ShapeElement) selectionElement).setupElementByVector(new Vector2(downX, downY, event.getX(), event.getY()));
22 | }
23 | return true;
24 | default:
25 | return super.onTouchEvent(event);
26 | }
27 | }
28 |
29 | @Override
30 | protected Path getSelectionPath() {
31 | if (selectionElement instanceof ShapeElement) {
32 | return ((ShapeElement) selectionElement).getElementPath();
33 | } else {
34 | return null;
35 | }
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/element/shape/LineElement.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.element.shape;
2 |
3 | import android.graphics.Path;
4 |
5 | public class LineElement extends BoxShapeElement {
6 |
7 | public LineElement() {
8 | }
9 |
10 | @Override
11 | public Path getTouchableArea() {
12 | Path path = new Path(super.getTouchableArea());
13 | if (shapeVector != null) {
14 | // Add a noise line to make the path easy to touch
15 | final int NOISE_SIZE = 10;
16 | if (Math.abs(shapeVector.getValueX()) < Math.abs(shapeVector.getValueY())) {
17 | path.lineTo(shapeVector.getPoint2().x + NOISE_SIZE, shapeVector.getPoint2().y);
18 | } else {
19 | path.lineTo(shapeVector.getPoint2().x, shapeVector.getPoint2().y + NOISE_SIZE);
20 | }
21 | }
22 | return path;
23 | }
24 |
25 | @Override
26 | public Object clone() {
27 | LineElement element = new LineElement();
28 | cloneTo(element);
29 | return element;
30 | }
31 |
32 | @Override
33 | protected Path createShapePath() {
34 | Path path = new Path();
35 | path.moveTo(shapeVector.getPoint1().x, shapeVector.getPoint1().y);
36 | path.lineTo(shapeVector.getPoint2().x, shapeVector.getPoint2().y);
37 | return path;
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/board/Selection.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.board;
2 |
3 | import com.mocircle.cidrawing.element.DrawElement;
4 | import com.mocircle.cidrawing.element.VirtualElement;
5 |
6 | import java.util.ArrayList;
7 | import java.util.List;
8 |
9 | /**
10 | * Element selection class.
11 | */
12 | public class Selection {
13 |
14 | private DrawElement element;
15 |
16 | Selection(DrawElement element) {
17 | this.element = element;
18 | }
19 |
20 | public boolean isEmptySelection() {
21 | return element == null;
22 | }
23 |
24 | public boolean isMultipleSelection() {
25 | return element instanceof VirtualElement;
26 | }
27 |
28 | public DrawElement getSingleElement() {
29 | if (isMultipleSelection()) {
30 | return null;
31 | } else {
32 | return element;
33 | }
34 | }
35 |
36 | public List getElements() {
37 | if (isEmptySelection()) {
38 | return new ArrayList<>();
39 | }
40 | if (isMultipleSelection()) {
41 | VirtualElement virtualElement = (VirtualElement) element;
42 | return virtualElement.getElements();
43 | } else {
44 | List elements = new ArrayList<>();
45 | elements.add(element);
46 | return elements;
47 | }
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/cidrawingsample/src/main/res/layout/drawer_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
14 |
15 |
21 |
22 |
28 |
29 |
30 |
35 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/utils/VectorUtils.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.utils;
2 |
3 | import com.mocircle.cidrawing.core.Vector2;
4 |
5 | public final class VectorUtils {
6 |
7 | private VectorUtils() {
8 | }
9 |
10 | /**
11 | * Change the vector to make two points of vector create a square based on point1.
12 | *
13 | * @param vector the given vector
14 | * @param smallerFirst true to create a smaller square, otherwise bigger square.
15 | */
16 | public static void shiftVectorAsSquare(Vector2 vector, boolean smallerFirst) {
17 | float deltaX = 0, deltaY = 0;
18 | float delta = Math.abs(Math.abs(vector.getValueX()) - Math.abs(vector.getValueY()));
19 | if (smallerFirst) {
20 | if (Math.abs(vector.getValueX()) < Math.abs(vector.getValueY())) {
21 | deltaY = vector.getValueY() > 0 ? -delta : delta;
22 | } else {
23 | deltaX = vector.getValueX() > 0 ? -delta : delta;
24 | }
25 | } else {
26 | if (Math.abs(vector.getValueX()) > Math.abs(vector.getValueY())) {
27 | deltaY = vector.getValueY() > 0 ? delta : -delta;
28 | } else {
29 | deltaX = vector.getValueX() > 0 ? delta : -delta;
30 | }
31 | }
32 | vector.getPoint2().x += deltaX;
33 | vector.getPoint2().y += deltaY;
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/ConfigManager.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing;
2 |
3 | /**
4 | * Defines the configurations in the drawing board
5 | */
6 | public interface ConfigManager {
7 |
8 | /**
9 | * Indicate the drawing board type
10 | */
11 | enum DrawingType {
12 |
13 | /**
14 | * Vector type means all elements added to the board are being managed and independent.
15 | * We can choose each element from that, and do operations for each element.
16 | */
17 | Vector,
18 |
19 | /**
20 | * Painting type means all elements are drawn to the board directly, we cannot figure the
21 | * elements out later. It will be treat as a new canvas each time.
22 | */
23 | Painting
24 | }
25 |
26 | /**
27 | * Indicates the debug mode status
28 | *
29 | * @return true if it's debug mode, otherwise false.
30 | */
31 | boolean isDebugMode();
32 |
33 | /**
34 | * Sets debug mode
35 | *
36 | * @param debugMode if it's debug mode
37 | */
38 | void setDebugMode(boolean debugMode);
39 |
40 | /**
41 | * Gets the drawing type
42 | *
43 | * @return drawing board type
44 | */
45 | DrawingType getDrawingType();
46 |
47 | /**
48 | * Sets drawing board type
49 | *
50 | * @param type drawing type
51 | */
52 | void setDrawingType(DrawingType type);
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/PaintingBehavior.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing;
2 |
3 | import android.graphics.Canvas;
4 | import android.graphics.PointF;
5 | import android.graphics.RectF;
6 |
7 | /**
8 | * A interface defines how to draw things and the related behaviors.
9 | */
10 | public interface PaintingBehavior {
11 |
12 | /**
13 | * Gets the box of a point
14 | *
15 | * @param point
16 | * @return
17 | */
18 | RectF getPointBox(PointF point);
19 |
20 | /**
21 | * Draws the element selection.
22 | *
23 | * @param canvas drawing canvas
24 | * @param box element bounding box
25 | */
26 | void drawSelection(Canvas canvas, RectF box);
27 |
28 | void drawReferencePoint(Canvas canvas, PointF point);
29 |
30 | void drawRotationHandle(Canvas canvas, RectF boundingBox);
31 |
32 | RectF getRotationHandleBox(RectF boundingBox);
33 |
34 | void drawResizingHandle(Canvas canvas, RectF boundingBox, boolean drawESWNHandles);
35 |
36 | RectF getNResizingHandleBox(RectF boundingBox);
37 |
38 | RectF getSResizingHandleBox(RectF boundingBox);
39 |
40 | RectF getWResizingHandleBox(RectF boundingBox);
41 |
42 | RectF getEResizingHandleBox(RectF boundingBox);
43 |
44 | RectF getNWResizingHandleBox(RectF boundingBox);
45 |
46 | RectF getNEResizingHandleBox(RectF boundingBox);
47 |
48 | RectF getSWResizingHandleBox(RectF boundingBox);
49 |
50 | RectF getSEResizingHandleBox(RectF boundingBox);
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/core/Vector2.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.core;
2 |
3 | import android.graphics.PointF;
4 | import android.graphics.RectF;
5 |
6 | /**
7 | * Vector defines in 2D.
8 | */
9 | public class Vector2 {
10 |
11 | private PointF point1;
12 | private PointF point2;
13 |
14 | public Vector2() {
15 | }
16 |
17 | public Vector2(PointF point1, PointF point2) {
18 | this.point1 = point1;
19 | this.point2 = point2;
20 | }
21 |
22 | public Vector2(float x1, float y1, float x2, float y2) {
23 | point1 = new PointF(x1, y1);
24 | point2 = new PointF(x2, y2);
25 | }
26 |
27 | public PointF getPoint1() {
28 | return point1;
29 | }
30 |
31 | public void setPoint1(PointF point1) {
32 | this.point1 = point1;
33 | }
34 |
35 | public PointF getPoint2() {
36 | return point2;
37 | }
38 |
39 | public void setPoint2(PointF point2) {
40 | this.point2 = point2;
41 | }
42 |
43 | public float getValueX() {
44 | return point2.x - point1.x;
45 | }
46 |
47 | public float getValueY() {
48 | return point2.y - point1.y;
49 | }
50 |
51 | public void offset(float dx, float dy) {
52 | point1.offset(dx, dy);
53 | point2.offset(dx, dy);
54 | }
55 |
56 | public RectF getRect() {
57 | RectF rect = new RectF(point1.x, point1.y, point2.x, point2.y);
58 | rect.sort();
59 | return rect;
60 | }
61 |
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/mode/stroke/SmoothStrokeMode.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.mode.stroke;
2 |
3 | import android.graphics.ComposePathEffect;
4 | import android.graphics.CornerPathEffect;
5 | import android.graphics.Paint;
6 |
7 | import com.mocircle.cidrawing.core.CiPaint;
8 |
9 | public class SmoothStrokeMode extends BaseStrokeMode {
10 |
11 | protected int smoothRadius = 100;
12 |
13 | public SmoothStrokeMode() {
14 | }
15 |
16 | public SmoothStrokeMode(int smoothRadius) {
17 | this.smoothRadius = smoothRadius;
18 | }
19 |
20 | public int getSmoothRadius() {
21 | return smoothRadius;
22 | }
23 |
24 | public void setSmoothRadius(int smoothRadius) {
25 | this.smoothRadius = smoothRadius;
26 | }
27 |
28 | protected CiPaint assignPaint() {
29 | CiPaint paint = new CiPaint(drawingContext.getPaint());
30 | paint.setStyle(Paint.Style.STROKE);
31 | paint.setAntiAlias(true);
32 | paint.setStrokeJoin(Paint.Join.ROUND);
33 | paint.setStrokeCap(Paint.Cap.ROUND);
34 | if (smoothRadius > 0) {
35 | CornerPathEffect effect = new CornerPathEffect(smoothRadius);
36 | if (paint.getPathEffect() == null) {
37 | paint.setPathEffect(effect);
38 | } else {
39 | ComposePathEffect composeEffect = new ComposePathEffect(paint.getPathEffect(), effect);
40 | paint.setPathEffect(composeEffect);
41 | }
42 | }
43 | return paint;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/view/DrawingViewProxyImpl.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.view;
2 |
3 | import android.graphics.Canvas;
4 | import android.view.MotionEvent;
5 |
6 | import com.mocircle.cidrawing.DrawingContext;
7 | import com.mocircle.cidrawing.board.ElementManager;
8 | import com.mocircle.cidrawing.element.DrawElement;
9 |
10 | /**
11 | * Default implementation for {@link DrawingViewProxy}
12 | */
13 | public class DrawingViewProxyImpl implements DrawingViewProxy {
14 |
15 | private DrawingView drawingView;
16 | private DrawingContext context;
17 | private ElementManager elementManager;
18 |
19 | public DrawingViewProxyImpl(DrawingView drawingView, DrawingContext context, ElementManager elementManager) {
20 | this.drawingView = drawingView;
21 | this.context = context;
22 | this.elementManager = elementManager;
23 | }
24 |
25 | @Override
26 | public void onDraw(Canvas canvas) {
27 | DrawElement[] elements = elementManager.getVisibleObjects();
28 | for (int i = 0; i < elements.length; i++) {
29 | elements[i].drawToCanvas(canvas);
30 | }
31 | }
32 |
33 | @Override
34 | public boolean onTouchEvent(MotionEvent event) {
35 | if (context.getDrawingMode() != null) {
36 | boolean result = context.getDrawingMode().onTouchEvent(event);
37 | if (result) {
38 | drawingView.notifyViewUpdated();
39 | }
40 | return result;
41 | }
42 | return false;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/cidrawinglib/src/test/java/com/mocircle/cidrawing/utils/ShapeUtilsTest.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.utils;
2 |
3 | import android.graphics.PointF;
4 |
5 | import com.mocircle.cidrawing.BuildConfig;
6 |
7 | import junit.framework.Assert;
8 |
9 | import org.junit.Test;
10 | import org.junit.runner.RunWith;
11 | import org.robolectric.RobolectricTestRunner;
12 | import org.robolectric.annotation.Config;
13 |
14 | @RunWith(RobolectricTestRunner.class)
15 | @Config(constants = BuildConfig.class, sdk = 21)
16 | public class ShapeUtilsTest {
17 |
18 | @Test
19 | public void testCalculateRotationDegree() {
20 | testCalculateRotationDegree(0, 0, 1, 1, 2, 2, 0);
21 | testCalculateRotationDegree(0, 0, 0, 1, 1, 1, -45);
22 | testCalculateRotationDegree(0, 0, 1, 0, 1, 1, 45);
23 | testCalculateRotationDegree(0, 0, 0, 1, 1, 0, -90);
24 | testCalculateRotationDegree(0, 0, 1, 0, 0, 1, 90);
25 | testCalculateRotationDegree(1, 1, 0, 2, 2, 1, -135);
26 | testCalculateRotationDegree(1, 1, 0, 2, 2, 0, -180);
27 | testCalculateRotationDegree(1, 1, 2, 1, 0, 1, 180);
28 | testCalculateRotationDegree(1, 1, 1, 0, 0, 1, -90);
29 | }
30 |
31 | private void testCalculateRotationDegree(float... values) {
32 | PointF center = new PointF(values[0], values[1]);
33 | PointF p1 = new PointF(values[2], values[3]);
34 | PointF p2 = new PointF(values[4], values[5]);
35 | float degree = ShapeUtils.calculateRotationDegree(center, p1, p2);
36 | Assert.assertEquals(values[6], degree);
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/element/shape/RightTriangleElement.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.element.shape;
2 |
3 | import android.graphics.Path;
4 |
5 | import com.mocircle.cidrawing.element.BaseElement;
6 |
7 | public class RightTriangleElement extends BoxShapeElement {
8 |
9 | private boolean leftRightAngle;
10 |
11 | public RightTriangleElement() {
12 | }
13 |
14 | public boolean isLeftRightAngle() {
15 | return leftRightAngle;
16 | }
17 |
18 | public void setLeftRightAngle(boolean leftRightAngle) {
19 | this.leftRightAngle = leftRightAngle;
20 | }
21 |
22 | @Override
23 | public Object clone() {
24 | RightTriangleElement element = new RightTriangleElement();
25 | cloneTo(element);
26 | return element;
27 | }
28 |
29 | @Override
30 | protected Path createShapePath() {
31 | Path path = new Path();
32 | path.moveTo(shapeBox.left, shapeBox.bottom);
33 | if (leftRightAngle) {
34 | path.lineTo(shapeBox.left, shapeBox.top);
35 | } else {
36 | path.lineTo(shapeBox.right, shapeBox.top);
37 | }
38 | path.lineTo(shapeBox.right, shapeBox.bottom);
39 | path.lineTo(shapeBox.left, shapeBox.bottom);
40 | return path;
41 | }
42 |
43 | @Override
44 | protected void cloneTo(BaseElement element) {
45 | super.cloneTo(element);
46 | if (element instanceof RightTriangleElement) {
47 | RightTriangleElement obj = (RightTriangleElement) element;
48 | obj.leftRightAngle = leftRightAngle;
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/mode/selection/LassoSelectionMode.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.mode.selection;
2 |
3 | import android.graphics.Path;
4 | import android.view.MotionEvent;
5 |
6 | import com.mocircle.cidrawing.element.DrawElement;
7 | import com.mocircle.cidrawing.element.StrokeElement;
8 |
9 | public class LassoSelectionMode extends SelectionMode {
10 |
11 | public LassoSelectionMode() {
12 | }
13 |
14 | @Override
15 | public boolean onTouchEvent(MotionEvent event) {
16 | switch (event.getAction()) {
17 | case MotionEvent.ACTION_MOVE:
18 | if (selectionElement instanceof StrokeElement) {
19 | ((StrokeElement) selectionElement).addPoint(event.getX(), event.getY());
20 | }
21 | return true;
22 | case MotionEvent.ACTION_UP:
23 | if (selectionElement instanceof StrokeElement) {
24 | ((StrokeElement) selectionElement).doneEditing();
25 | }
26 | return super.onTouchEvent(event);
27 | default:
28 | return super.onTouchEvent(event);
29 | }
30 | }
31 |
32 | protected DrawElement createSelectionElement() {
33 | StrokeElement element = new StrokeElement();
34 | element.setCloseStroke(true);
35 | return element;
36 | }
37 |
38 | @Override
39 | protected Path getSelectionPath() {
40 | if (selectionElement instanceof StrokeElement) {
41 | return ((StrokeElement) selectionElement).getElementPath();
42 | } else {
43 | return null;
44 | }
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/cidrawingsample/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
15 |
16 |
22 |
23 |
24 |
25 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/operation/GroupElementOperation.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.operation;
2 |
3 | import com.mocircle.cidrawing.element.DrawElement;
4 | import com.mocircle.cidrawing.element.GroupElement;
5 |
6 | import java.util.List;
7 |
8 | public class GroupElementOperation extends SelectedElementsOperation {
9 |
10 | private GroupElement groupElement;
11 |
12 | public GroupElementOperation() {
13 | }
14 |
15 | public GroupElementOperation(List elements) {
16 | this.elements = elements;
17 | }
18 |
19 | @Override
20 | public boolean doOperation() {
21 | groupElement = new GroupElement(elements);
22 | for (DrawElement element : elements) {
23 | elementManager.removeElementFromCurrentLayer(element);
24 | }
25 | elementManager.addElementToCurrentLayer(groupElement);
26 |
27 | // Re-select elements
28 | elementManager.clearSelection();
29 | elementManager.selectElement(groupElement);
30 | drawingBoard.getDrawingView().notifyViewUpdated();
31 | return true;
32 | }
33 |
34 | @Override
35 | public void undo() {
36 | if (elements != null) {
37 | elementManager.removeElementFromCurrentLayer(groupElement);
38 | for (DrawElement element : elements) {
39 | elementManager.addElementToCurrentLayer(element);
40 | }
41 |
42 | // Re-select elements
43 | elementManager.clearSelection();
44 | elementManager.selectElements(elements);
45 | drawingBoard.getDrawingView().notifyViewUpdated();
46 | }
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/element/BaseElement.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.element;
2 |
3 | import com.mocircle.cidrawing.persistence.Persistable;
4 | import com.mocircle.cidrawing.persistence.PersistenceException;
5 |
6 | import org.json.JSONException;
7 | import org.json.JSONObject;
8 |
9 | import java.io.Serializable;
10 | import java.util.HashMap;
11 | import java.util.Map;
12 |
13 | public abstract class BaseElement implements Serializable, Cloneable, Persistable {
14 |
15 | private static final String KEY_NAME = "name";
16 |
17 | protected String name;
18 |
19 | public BaseElement() {
20 | }
21 |
22 | public String getName() {
23 | return name;
24 | }
25 |
26 | public void setName(String name) {
27 | this.name = name;
28 | }
29 |
30 | @Override
31 | public JSONObject generateJson() {
32 | JSONObject object = new JSONObject();
33 | try {
34 | object.put(KEY_NAME, name);
35 | } catch (JSONException e) {
36 | throw new PersistenceException(e);
37 | }
38 | return object;
39 | }
40 |
41 | @Override
42 | public Map generateResources() {
43 | return new HashMap<>();
44 | }
45 |
46 | @Override
47 | public void loadFromJson(JSONObject object, Map resources) {
48 | if (object != null) {
49 | name = object.optString(KEY_NAME);
50 | }
51 | }
52 |
53 | @Override
54 | public void afterLoaded() {
55 | }
56 |
57 | @Override
58 | public abstract Object clone();
59 |
60 | protected void cloneTo(BaseElement element) {
61 | element.name = name;
62 | }
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/mode/BasePointMode.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.mode;
2 |
3 | import android.view.MotionEvent;
4 |
5 | import com.mocircle.cidrawing.utils.DrawUtils;
6 |
7 | public class BasePointMode extends AbstractDrawingMode {
8 |
9 | protected float downX;
10 | protected float downY;
11 |
12 | @Override
13 | public void setDrawingBoardId(String boardId) {
14 | super.setDrawingBoardId(boardId);
15 | }
16 |
17 | @Override
18 | public boolean onTouchEvent(MotionEvent event) {
19 | switch (event.getAction()) {
20 | case MotionEvent.ACTION_DOWN:
21 | downX = event.getX();
22 | downY = event.getY();
23 | onFirstPointDown(downX, downY);
24 | return true;
25 | case MotionEvent.ACTION_MOVE:
26 | onOverPoint(event.getX(), event.getY());
27 | downX = event.getX();
28 | downY = event.getY();
29 | return true;
30 | case MotionEvent.ACTION_UP:
31 | boolean singleTap = DrawUtils.isSingleTap(drawingBoard.getDrawingView().getContext(), downX, downY, event);
32 | onLastPointUp(event.getX(), event.getY(), singleTap);
33 | return true;
34 | case MotionEvent.ACTION_CANCEL:
35 | onPointCancelled();
36 | return true;
37 | }
38 | return false;
39 | }
40 |
41 | protected void onFirstPointDown(float x, float y) {
42 | }
43 |
44 | protected void onOverPoint(float x, float y) {
45 | }
46 |
47 | protected void onLastPointUp(float x, float y, boolean singleTap) {
48 | }
49 |
50 | protected void onPointCancelled() {
51 | }
52 |
53 | }
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/DrawingContext.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing;
2 |
3 | import android.graphics.Paint;
4 |
5 | import com.mocircle.cidrawing.core.CiPaint;
6 | import com.mocircle.cidrawing.mode.DrawingMode;
7 |
8 | import java.util.ArrayList;
9 | import java.util.List;
10 |
11 | public class DrawingContext {
12 |
13 | interface DrawingModeChangedListener {
14 |
15 | void onDrawingModeChanged();
16 | }
17 |
18 | private String boardId;
19 | private DrawingMode drawingMode;
20 | private List drawingModeChangedListeners = new ArrayList<>();
21 |
22 | private CiPaint paint;
23 |
24 | public DrawingContext(String boardId) {
25 | this.boardId = boardId;
26 | setupDefault();
27 | }
28 |
29 | public DrawingMode getDrawingMode() {
30 | return drawingMode;
31 | }
32 |
33 | public void setDrawingMode(DrawingMode drawingMode) {
34 | if (this.drawingMode != null) {
35 | this.drawingMode.onLeaveMode();
36 | }
37 | this.drawingMode = drawingMode;
38 | this.drawingMode.setDrawingBoardId(boardId);
39 | this.drawingMode.onEnterMode();
40 |
41 | // Notify drawing mode changed
42 | for (DrawingModeChangedListener listener : drawingModeChangedListeners) {
43 | listener.onDrawingModeChanged();
44 | }
45 | }
46 |
47 | public void addDrawingModeChangedListener(DrawingModeChangedListener listener) {
48 | drawingModeChangedListeners.add(listener);
49 | }
50 |
51 | public CiPaint getPaint() {
52 | if (paint == null) {
53 | paint = new CiPaint();
54 | }
55 | return paint;
56 | }
57 |
58 | private void setupDefault() {
59 | getPaint().setStyle(Paint.Style.STROKE);
60 | }
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/operation/FlipOperation.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.operation;
2 |
3 | import android.graphics.Matrix;
4 |
5 | import com.mocircle.cidrawing.utils.MatrixUtils;
6 |
7 | public class FlipOperation extends SingleSelectedElementOperation {
8 |
9 | public enum FlipType {
10 | Vertical,
11 | Horizontal
12 | }
13 |
14 | private FlipType flipType;
15 | private Matrix flipMatrix;
16 |
17 | public FlipOperation() {
18 | }
19 |
20 | public FlipOperation(FlipType flipType) {
21 | this.flipType = flipType;
22 | }
23 |
24 | public FlipType getFlipType() {
25 | return flipType;
26 | }
27 |
28 | public void setFlipType(FlipType flipType) {
29 | this.flipType = flipType;
30 | }
31 |
32 | @Override
33 | public boolean doOperation() {
34 | flipMatrix = getFlipMatrix();
35 | selectedElement.applyMatrixForData(flipMatrix);
36 | drawingBoard.getDrawingView().notifyViewUpdated();
37 | return true;
38 | }
39 |
40 | @Override
41 | public void undo() {
42 | if (selectedElement != null) {
43 | Matrix inverseFlipMatrix = MatrixUtils.getInvertMatrix(flipMatrix);
44 | selectedElement.applyMatrixForData(inverseFlipMatrix);
45 | drawingBoard.getDrawingView().notifyViewUpdated();
46 | }
47 | }
48 |
49 | private Matrix getFlipMatrix() {
50 | Matrix matrix = new Matrix();
51 | if (flipType == FlipType.Horizontal) {
52 | matrix.postScale(-1, 1, selectedElement.getReferencePoint().x, selectedElement.getReferencePoint().y);
53 | } else if (flipType == FlipType.Vertical) {
54 | matrix.postScale(1, -1, selectedElement.getReferencePoint().x, selectedElement.getReferencePoint().y);
55 | }
56 | return matrix;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/mode/transformation/DataTransformMode.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.mode.transformation;
2 |
3 | import android.graphics.Matrix;
4 | import android.view.MotionEvent;
5 |
6 | import com.mocircle.cidrawing.mode.AutoDetectedElementOperationMode;
7 | import com.mocircle.cidrawing.operation.DataTransformOperation;
8 | import com.mocircle.cidrawing.utils.MatrixUtils;
9 |
10 | public abstract class DataTransformMode extends AutoDetectedElementOperationMode {
11 |
12 | protected Matrix originalDataMatrix;
13 |
14 | public DataTransformMode() {
15 | }
16 |
17 | public DataTransformMode(boolean autoDetectMode) {
18 | super(autoDetectMode);
19 | }
20 |
21 | @Override
22 | public boolean onTouchEvent(MotionEvent event) {
23 | boolean result = super.onTouchEvent(event);
24 | if (element == null) {
25 | return result;
26 | }
27 | switch (event.getAction()) {
28 | case MotionEvent.ACTION_DOWN:
29 | originalDataMatrix = new Matrix(element.getDataMatrix());
30 | return true;
31 | case MotionEvent.ACTION_UP:
32 | Matrix deltaMatrix = MatrixUtils.getTransformationMatrix(originalDataMatrix, element.getDataMatrix());
33 | resetTransformation(deltaMatrix);
34 | operationManager.executeOperation(new DataTransformOperation(element, deltaMatrix));
35 | return true;
36 | case MotionEvent.ACTION_CANCEL:
37 | deltaMatrix = MatrixUtils.getTransformationMatrix(originalDataMatrix, element.getDataMatrix());
38 | resetTransformation(deltaMatrix);
39 | return true;
40 | }
41 | return result;
42 | }
43 |
44 | protected void resetTransformation(Matrix deltaMatrix) {
45 | element.applyMatrixForData(MatrixUtils.getInvertMatrix(deltaMatrix));
46 | element.updateBoundingBox();
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/mode/transformation/DisplayTransformMode.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.mode.transformation;
2 |
3 | import android.graphics.Matrix;
4 | import android.view.MotionEvent;
5 |
6 | import com.mocircle.cidrawing.mode.AutoDetectedElementOperationMode;
7 | import com.mocircle.cidrawing.operation.DisplayTransformOperation;
8 | import com.mocircle.cidrawing.utils.MatrixUtils;
9 |
10 | public abstract class DisplayTransformMode extends AutoDetectedElementOperationMode {
11 |
12 | protected Matrix originalDisplayMatrix;
13 |
14 | public DisplayTransformMode() {
15 | }
16 |
17 | public DisplayTransformMode(boolean autoDetectMode) {
18 | super(autoDetectMode);
19 | }
20 |
21 | @Override
22 | public boolean onTouchEvent(MotionEvent event) {
23 | boolean result = super.onTouchEvent(event);
24 | if (element == null) {
25 | return result;
26 | }
27 | switch (event.getAction()) {
28 | case MotionEvent.ACTION_DOWN:
29 | originalDisplayMatrix = new Matrix(element.getDisplayMatrix());
30 | return true;
31 | case MotionEvent.ACTION_UP:
32 | Matrix deltaMatrix = MatrixUtils.getTransformationMatrix(originalDisplayMatrix, element.getDisplayMatrix());
33 | resetTransformation(deltaMatrix);
34 | operationManager.executeOperation(new DisplayTransformOperation(element, deltaMatrix));
35 | return true;
36 | case MotionEvent.ACTION_CANCEL:
37 | deltaMatrix = MatrixUtils.getTransformationMatrix(originalDisplayMatrix, element.getDisplayMatrix());
38 | resetTransformation(deltaMatrix);
39 | return true;
40 | }
41 | return result;
42 | }
43 |
44 | protected void resetTransformation(Matrix deltaMatrix) {
45 | element.getDisplayMatrix().postConcat(MatrixUtils.getInvertMatrix(deltaMatrix));
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/cidrawingsample/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/element/BasePathElement.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.element;
2 |
3 | import android.graphics.Canvas;
4 | import android.graphics.Path;
5 | import android.graphics.RectF;
6 |
7 | public abstract class BasePathElement extends DrawElement {
8 |
9 | protected Path elementPath;
10 |
11 | public BasePathElement() {
12 | }
13 |
14 | public Path getElementPath() {
15 | return elementPath;
16 | }
17 |
18 | public Path getActualElementPath() {
19 | Path path = new Path(elementPath);
20 | path.transform(displayMatrix);
21 | return path;
22 | }
23 |
24 | @Override
25 | public Path getTouchableArea() {
26 | if (elementPath != null) {
27 | return elementPath;
28 | } else {
29 | return new Path();
30 | }
31 | }
32 |
33 | @Override
34 | public void updateBoundingBox() {
35 | if (elementPath != null) {
36 | RectF box = new RectF();
37 | elementPath.computeBounds(box, true);
38 | setBoundingBox(box);
39 | }
40 | }
41 |
42 | @Override
43 | public RectF getOuterBoundingBox() {
44 | if (elementPath != null) {
45 | Path path = new Path(elementPath);
46 | path.transform(getDisplayMatrix());
47 | RectF box = new RectF();
48 | path.computeBounds(box, true);
49 | return box;
50 | }
51 | return new RectF();
52 | }
53 |
54 | @Override
55 | public void drawElement(Canvas canvas) {
56 | if (elementPath != null) {
57 | canvas.drawPath(elementPath, paint);
58 | }
59 | }
60 |
61 | @Override
62 | protected void cloneTo(BaseElement element) {
63 | super.cloneTo(element);
64 | if (element instanceof BasePathElement) {
65 | BasePathElement obj = (BasePathElement) element;
66 | if (elementPath != null) {
67 | obj.elementPath = new Path(elementPath);
68 | }
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/android/logging/CircleLog.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 mocircle.com
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.mocircle.android.logging;
18 |
19 | import android.util.Log;
20 |
21 | /**
22 | * Simple wrapper for system log class.
23 | */
24 | public class CircleLog {
25 |
26 | public static void i(String tag, String msg) {
27 | Log.i(tag, msg);
28 | }
29 |
30 | public static void i(String tag, String msg, Throwable tr) {
31 | Log.i(tag, msg, tr);
32 | }
33 |
34 | public static void v(String tag, String msg) {
35 | Log.v(tag, msg);
36 | }
37 |
38 | public static void v(String tag, String msg, Throwable tr) {
39 | Log.v(tag, msg, tr);
40 | }
41 |
42 | public static void d(String tag, String msg) {
43 | Log.d(tag, msg);
44 | }
45 |
46 | public static void d(String tag, String msg, Throwable tr) {
47 | Log.d(tag, msg, tr);
48 | }
49 |
50 | public static void w(String tag, String msg) {
51 | Log.w(tag, msg);
52 | }
53 |
54 | public static void w(String tag, Throwable tr) {
55 | Log.w(tag, tr);
56 | }
57 |
58 | public static void w(String tag, String msg, Throwable tr) {
59 | Log.w(tag, msg, tr);
60 | }
61 |
62 | public static void e(String tag, String msg) {
63 | Log.e(tag, msg);
64 | }
65 |
66 | public static void e(String tag, String msg, Throwable tr) {
67 | Log.e(tag, msg, tr);
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/view/CiDrawingView.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.view;
2 |
3 | import android.annotation.TargetApi;
4 | import android.content.Context;
5 | import android.graphics.Canvas;
6 | import android.graphics.Paint;
7 | import android.os.Build;
8 | import android.util.AttributeSet;
9 | import android.view.MotionEvent;
10 | import android.view.View;
11 |
12 | /**
13 | * A view to provide drawing features.
14 | */
15 | public class CiDrawingView extends View implements DrawingView {
16 |
17 | protected DrawingViewProxy viewProxy;
18 |
19 | public CiDrawingView(Context context) {
20 | super(context);
21 | setupView();
22 | }
23 |
24 | public CiDrawingView(Context context, AttributeSet attrs) {
25 | super(context, attrs);
26 | setupView();
27 | }
28 |
29 | public CiDrawingView(Context context, AttributeSet attrs, int defStyleAttr) {
30 | super(context, attrs, defStyleAttr);
31 | setupView();
32 | }
33 |
34 | @TargetApi(Build.VERSION_CODES.LOLLIPOP)
35 | public CiDrawingView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
36 | super(context, attrs, defStyleAttr, defStyleRes);
37 | setupView();
38 | }
39 |
40 | @Override
41 | public void setViewProxy(DrawingViewProxy viewProxy) {
42 | this.viewProxy = viewProxy;
43 | }
44 |
45 | @Override
46 | public void notifyViewUpdated() {
47 | invalidate();
48 | }
49 |
50 | @Override
51 | public boolean onTouchEvent(MotionEvent event) {
52 | if (viewProxy != null) {
53 | if (viewProxy.onTouchEvent(event)) {
54 | return true;
55 | }
56 | }
57 | return super.onTouchEvent(event);
58 | }
59 |
60 | @Override
61 | protected void onDraw(Canvas canvas) {
62 | if (viewProxy != null) {
63 | viewProxy.onDraw(canvas);
64 | }
65 | }
66 |
67 | protected void setupView() {
68 | setLayerType(LAYER_TYPE_HARDWARE, null);
69 | }
70 |
71 | }
72 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/utils/ShapeUtils.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.utils;
2 |
3 | import android.graphics.Path;
4 | import android.graphics.PointF;
5 | import android.graphics.RectF;
6 | import android.graphics.Region;
7 |
8 | /**
9 | * A utility class about shape.
10 | */
11 | public final class ShapeUtils {
12 |
13 | private ShapeUtils() {
14 | }
15 |
16 | /**
17 | * Creates square based on circle.
18 | *
19 | * @param centerX x of center point
20 | * @param centerY y of center point
21 | * @param radius radius
22 | * @return square object
23 | */
24 | public static RectF createSquare(float centerX, float centerY, float radius) {
25 | return new RectF(centerX - radius, centerY - radius, centerX + radius, centerY + radius);
26 | }
27 |
28 | /**
29 | * Gets the angle from P(center, p1) to P(center, p2), the value range is [-180, 180].
30 | *
31 | * @param center center point
32 | * @param p1 point 1
33 | * @param p2 point 2
34 | * @return angle
35 | */
36 | public static float calculateRotationDegree(PointF center, PointF p1, PointF p2) {
37 | double angle1 = Math.atan2(p1.y - center.y, p1.x - center.x);
38 | double angle2 = Math.atan2(p2.y - center.y, p2.x - center.x);
39 | float angle = (float) Math.toDegrees(angle2 - angle1);
40 | if (angle > 180) {
41 | angle -= 360;
42 | } else if (angle < -180) {
43 | angle += 360;
44 | }
45 | return angle;
46 | }
47 |
48 | /**
49 | * Creates a simple region from the given path.
50 | *
51 | * @param path given path
52 | * @return region object
53 | */
54 | public static Region createRegionFromPath(Path path) {
55 | Region region = new Region();
56 | if (path != null) {
57 | RectF box = new RectF();
58 | path.computeBounds(box, true);
59 | region.setPath(path, new Region((int) box.left, (int) box.top, (int) box.right, (int) box.bottom));
60 | }
61 | return region;
62 | }
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/cidrawinglib/src/test/java/com/mocircle/cidrawing/utils/VectorUtilsTest.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.utils;
2 |
3 | import com.mocircle.cidrawing.BuildConfig;
4 | import com.mocircle.cidrawing.core.Vector2;
5 |
6 | import junit.framework.Assert;
7 |
8 | import org.junit.Test;
9 | import org.junit.runner.RunWith;
10 | import org.robolectric.RobolectricTestRunner;
11 | import org.robolectric.annotation.Config;
12 |
13 | @RunWith(RobolectricTestRunner.class)
14 | @Config(constants = BuildConfig.class, sdk = 21)
15 | public class VectorUtilsTest {
16 |
17 | @Test
18 | public void testShiftVectorLargerFirst() {
19 | testShiftVectorAsSquare(false, 0, 0, 1, 2, 2, 2);
20 | testShiftVectorAsSquare(false, 0, 0, 2, 1, 2, 2);
21 |
22 | testShiftVectorAsSquare(false, 0, 0, -1, 2, -2, 2);
23 | testShiftVectorAsSquare(false, 0, 0, -2, 1, -2, 2);
24 |
25 | testShiftVectorAsSquare(false, 0, 0, 1, -2, 2, -2);
26 | testShiftVectorAsSquare(false, 0, 0, 2, -1, 2, -2);
27 |
28 | testShiftVectorAsSquare(false, 0, 0, -1, -2, -2, -2);
29 | testShiftVectorAsSquare(false, 0, 0, -2, -1, -2, -2);
30 | }
31 |
32 | @Test
33 | public void testShiftVectorSmallerFirst() {
34 | testShiftVectorAsSquare(true, 0, 0, 1, 2, 1, 1);
35 | testShiftVectorAsSquare(true, 0, 0, 2, 1, 1, 1);
36 |
37 | testShiftVectorAsSquare(true, 0, 0, -1, 2, -1, 1);
38 | testShiftVectorAsSquare(true, 0, 0, -2, 1, -1, 1);
39 |
40 | testShiftVectorAsSquare(true, 0, 0, 1, -2, 1, -1);
41 | testShiftVectorAsSquare(true, 0, 0, 2, -1, 1, -1);
42 |
43 | testShiftVectorAsSquare(true, 0, 0, -1, -2, -1, -1);
44 | testShiftVectorAsSquare(true, 0, 0, -2, -1, -1, -1);
45 | }
46 |
47 | private void testShiftVectorAsSquare(boolean smallerFirst, float... values) {
48 | Vector2 vector = new Vector2(values[0], values[1], values[2], values[3]);
49 | VectorUtils.shiftVectorAsSquare(vector, smallerFirst);
50 | Assert.assertEquals(values[4], vector.getPoint2().x);
51 | Assert.assertEquals(values[5], vector.getPoint2().y);
52 | }
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/mode/ElementOperationMode.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.mode;
2 |
3 | import android.view.MotionEvent;
4 |
5 | import com.mocircle.cidrawing.PaintBuilder;
6 | import com.mocircle.cidrawing.board.ElementManager;
7 | import com.mocircle.cidrawing.core.CiPaint;
8 | import com.mocircle.cidrawing.element.DrawElement;
9 | import com.mocircle.cidrawing.operation.OperationManager;
10 |
11 | public class ElementOperationMode extends AbstractDrawingMode {
12 |
13 | protected PaintBuilder paintBuilder;
14 | protected ElementManager elementManager;
15 | protected OperationManager operationManager;
16 |
17 | protected DrawElement element;
18 | protected CiPaint originalPaint;
19 | protected CiPaint previewPaint;
20 |
21 | public ElementOperationMode() {
22 | }
23 |
24 | public DrawElement getElement() {
25 | return element;
26 | }
27 |
28 | public void setElement(DrawElement element) {
29 | this.element = element;
30 | if (element != null) {
31 | previewPaint = paintBuilder.createPreviewPaint(element.getPaint());
32 | }
33 | }
34 |
35 | @Override
36 | public void setDrawingBoardId(String boardId) {
37 | super.setDrawingBoardId(boardId);
38 | paintBuilder = drawingBoard.getPaintBuilder();
39 | elementManager = drawingBoard.getElementManager();
40 | operationManager = drawingBoard.getOperationManager();
41 | }
42 |
43 | @Override
44 | public boolean onTouchEvent(MotionEvent event) {
45 | switch (event.getAction()) {
46 | case MotionEvent.ACTION_DOWN:
47 | if (element != null) {
48 | originalPaint = new CiPaint(element.getPaint());
49 | element.setPaint(previewPaint);
50 | return true;
51 | }
52 | case MotionEvent.ACTION_UP:
53 | case MotionEvent.ACTION_CANCEL:
54 | if (element != null) {
55 | element.setPaint(originalPaint);
56 | return true;
57 | }
58 | }
59 | return false;
60 | }
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/mode/transformation/MoveMode.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.mode.transformation;
2 |
3 | import android.view.MotionEvent;
4 |
5 | import com.mocircle.android.logging.CircleLog;
6 | import com.mocircle.cidrawing.element.behavior.Selectable;
7 | import com.mocircle.cidrawing.mode.transformation.DisplayTransformMode;
8 |
9 | public class MoveMode extends DisplayTransformMode {
10 |
11 | private static final String TAG = "MoveMode";
12 |
13 | private float downX;
14 | private float downY;
15 |
16 | public MoveMode() {
17 | }
18 |
19 | public MoveMode(boolean autoDetectMode) {
20 | super(autoDetectMode);
21 | }
22 |
23 | @Override
24 | public void onLeaveMode() {
25 | if (element != null) {
26 | element.setSelected(false);
27 | }
28 | }
29 |
30 | @Override
31 | public boolean onTouchEvent(MotionEvent event) {
32 | boolean result = super.onTouchEvent(event);
33 | if (element == null || !element.isMovementEnabled()) {
34 | return result;
35 | }
36 |
37 | switch (event.getAction()) {
38 | case MotionEvent.ACTION_DOWN:
39 | downX = event.getX();
40 | downY = event.getY();
41 | return true;
42 | case MotionEvent.ACTION_MOVE:
43 | float offsetX = event.getX() - downX;
44 | float offsetY = event.getY() - downY;
45 | element.move(offsetX, offsetY);
46 | CircleLog.d(TAG, "Move element by " + offsetX + ", " + offsetY);
47 | CircleLog.v(TAG, "Element position: " + element.getLocX() + ", " + element.getLocY());
48 | downX = event.getX();
49 | downY = event.getY();
50 | return true;
51 | }
52 | return result;
53 | }
54 |
55 | @Override
56 | protected void detectElement(float x, float y) {
57 | super.detectElement(x, y);
58 | elementManager.clearSelection();
59 | if (element != null) {
60 | element.setSelected(true, Selectable.SelectionStyle.LIGHT);
61 | }
62 | }
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/element/shape/ShapeElement.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.element.shape;
2 |
3 | import android.graphics.Canvas;
4 | import android.graphics.Matrix;
5 | import android.graphics.Paint;
6 | import android.graphics.Path;
7 |
8 | import com.mocircle.cidrawing.core.Vector2;
9 | import com.mocircle.cidrawing.element.BasePathElement;
10 | import com.mocircle.cidrawing.element.behavior.SupportVector;
11 |
12 | import org.json.JSONObject;
13 |
14 | public abstract class ShapeElement extends BasePathElement implements SupportVector {
15 |
16 | public ShapeElement() {
17 | }
18 |
19 | @Override
20 | public void afterLoaded() {
21 | elementPath = createShapePath();
22 | updateBoundingBox();
23 | }
24 |
25 | @Override
26 | public void drawElement(Canvas canvas) {
27 | if (elementPath != null) {
28 | canvas.drawPath(elementPath, paint);
29 | if (paint.getStyle() == Paint.Style.FILL_AND_STROKE) {
30 | Integer originalColor = null;
31 | if (paint.getSecondaryColor() != null) {
32 | originalColor = paint.getColor();
33 | paint.setColor(paint.getSecondaryColor());
34 | }
35 | paint.setStyle(Paint.Style.FILL);
36 | canvas.drawPath(elementPath, paint);
37 | paint.setStyle(Paint.Style.FILL_AND_STROKE);
38 | if (originalColor != null) {
39 | paint.setColor(originalColor);
40 | }
41 | }
42 | }
43 | }
44 |
45 | @Override
46 | public void setupElementByVector(Vector2 vector) {
47 | retrieveAttributesFromVector(vector);
48 | elementPath = createShapePath();
49 | updateBoundingBox();
50 | }
51 |
52 | @Override
53 | public void applyMatrixForData(Matrix matrix) {
54 | super.applyMatrixForData(matrix);
55 |
56 | elementPath = createShapePath();
57 | elementPath.transform(dataMatrix);
58 | }
59 |
60 | protected abstract void retrieveAttributesFromVector(Vector2 vector);
61 |
62 | protected abstract Path createShapePath();
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/element/BoundsElement.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.element;
2 |
3 | import android.graphics.Matrix;
4 | import android.graphics.Path;
5 | import android.graphics.RectF;
6 |
7 | /**
8 | * A kind of element which is surrounded by the bounds.
9 | */
10 | public abstract class BoundsElement extends DrawElement {
11 |
12 | protected transient RectF originalBoundingBox;
13 | protected transient Path boundingPath;
14 |
15 | @Override
16 | public void applyMatrixForData(Matrix matrix) {
17 | super.applyMatrixForData(matrix);
18 | boundingPath.transform(matrix);
19 | }
20 |
21 | @Override
22 | public void updateBoundingBox() {
23 | if (boundingPath != null) {
24 | RectF box = new RectF();
25 | boundingPath.computeBounds(box, true);
26 | setBoundingBox(box);
27 | }
28 | }
29 |
30 | @Override
31 | public RectF getOuterBoundingBox() {
32 | if (boundingPath != null) {
33 | Path path = new Path(boundingPath);
34 | path.transform(getDisplayMatrix());
35 | RectF box = new RectF();
36 | path.computeBounds(box, true);
37 | return box;
38 | }
39 | return new RectF();
40 | }
41 |
42 | @Override
43 | public Path getTouchableArea() {
44 | if (boundingPath != null) {
45 | return boundingPath;
46 | } else {
47 | return new Path();
48 | }
49 | }
50 |
51 | @Override
52 | protected void cloneTo(BaseElement element) {
53 | super.cloneTo(element);
54 | if (element instanceof BoundsElement) {
55 | BoundsElement obj = (BoundsElement) element;
56 | if (originalBoundingBox != null) {
57 | obj.originalBoundingBox = new RectF(originalBoundingBox);
58 | }
59 | if (boundingPath != null) {
60 | obj.boundingPath = new Path(boundingPath);
61 | }
62 | }
63 | }
64 |
65 | protected void updateBoundingBoxWithDataMatrix() {
66 | boundingPath.transform(getDataMatrix());
67 | updateBoundingBox();
68 | }
69 |
70 | protected abstract void calculateBoundingBox();
71 |
72 | }
73 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/mode/transformation/RotateMode.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.mode.transformation;
2 |
3 | import android.graphics.PointF;
4 | import android.view.MotionEvent;
5 |
6 | import com.mocircle.android.logging.CircleLog;
7 | import com.mocircle.cidrawing.element.behavior.Selectable;
8 | import com.mocircle.cidrawing.utils.ShapeUtils;
9 |
10 | public class RotateMode extends DisplayTransformMode {
11 |
12 | private static final String TAG = "RotateMode";
13 |
14 | private float downX;
15 | private float downY;
16 | private PointF referencePoint;
17 |
18 | public RotateMode() {
19 | }
20 |
21 | public RotateMode(boolean autoDetectMode) {
22 | super(autoDetectMode);
23 | }
24 |
25 | @Override
26 | public void onLeaveMode() {
27 | if (element != null) {
28 | element.setSelected(false);
29 | }
30 | }
31 |
32 | @Override
33 | public boolean onTouchEvent(MotionEvent event) {
34 | boolean result = super.onTouchEvent(event);
35 | if (element == null || !element.isRotationEnabled()) {
36 | return result;
37 | }
38 |
39 | switch (event.getAction()) {
40 | case MotionEvent.ACTION_DOWN:
41 | downX = event.getX();
42 | downY = event.getY();
43 | referencePoint = element.getActualReferencePoint();
44 | return true;
45 | case MotionEvent.ACTION_MOVE:
46 | float degreeDelta = ShapeUtils.calculateRotationDegree(referencePoint,
47 | new PointF(downX, downY), new PointF(event.getX(), event.getY()));
48 | element.rotate(degreeDelta, referencePoint.x, referencePoint.y);
49 | downX = event.getX();
50 | downY = event.getY();
51 | CircleLog.d(TAG, "Rotate element by " + degreeDelta);
52 | return true;
53 | }
54 | return result;
55 | }
56 |
57 | @Override
58 | protected void detectElement(float x, float y) {
59 | super.detectElement(x, y);
60 | elementManager.clearSelection();
61 | if (element != null) {
62 | element.setSelected(true, Selectable.SelectionStyle.LIGHT);
63 | }
64 | }
65 |
66 | }
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/PaintBuilder.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing;
2 |
3 | import com.mocircle.cidrawing.core.CiPaint;
4 |
5 | /**
6 | * A interface defines all kinds of paint. Each method will return a new paint object.
7 | */
8 | public interface PaintBuilder {
9 |
10 | /**
11 | * Creates a new paint for drawing auxiliary line.
12 | *
13 | * @return the paint object created
14 | */
15 | CiPaint createDebugPaintForLine();
16 |
17 | /**
18 | * Creates a new paint for drawing auxiliary area.
19 | *
20 | * @return the paint object created
21 | */
22 | CiPaint createDebugPaintForArea();
23 |
24 | /**
25 | * Creates a paint for drawing element preview look.
26 | *
27 | * @param originalPaint original paint of the element
28 | * @return the paint object created
29 | */
30 | CiPaint createPreviewPaint(CiPaint originalPaint);
31 |
32 | /**
33 | * Creates a paint for drawing element preview look with fill effect.
34 | *
35 | * @param originalPaint original paint of the element
36 | * @return the paint object created
37 | */
38 | CiPaint createPreviewAreaPaint(CiPaint originalPaint);
39 |
40 | /**
41 | * Creates a paint used for rectangle selection tool.
42 | *
43 | * @return the paint object created
44 | */
45 | CiPaint createRectSelectionToolPaint();
46 |
47 | /**
48 | * Creates a paint for drawing selection bounds of an element.
49 | *
50 | * @return the paint object created
51 | */
52 | CiPaint createSelectionBoundPaint();
53 |
54 | /**
55 | * Creates a paint for drawing multiple selection inner area.
56 | *
57 | * @return the paint object created
58 | */
59 | CiPaint createSelectionAreaPaint();
60 |
61 | /**
62 | * Creates a paint for drawing resizing handle.
63 | *
64 | * @return the paint object created
65 | */
66 | CiPaint createResizingHandlePaint();
67 |
68 | /**
69 | * Creates a paint for drawing rotation handle.
70 | *
71 | * @return the paint object created
72 | */
73 | CiPaint createRotationHandlePaint();
74 |
75 | /**
76 | * Creates a paint for drawing reference point.
77 | *
78 | * @return the paint object created
79 | */
80 | CiPaint createReferencePointPaint();
81 |
82 | }
83 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/DrawingBoardManager.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing;
2 |
3 | import android.text.TextUtils;
4 |
5 | import org.json.JSONObject;
6 |
7 | import java.util.HashMap;
8 | import java.util.Map;
9 | import java.util.UUID;
10 |
11 | /**
12 | * A manager of the drawing board
13 | */
14 | public class DrawingBoardManager {
15 |
16 | private static DrawingBoardManager instance;
17 |
18 | private Map drawingBoardMap = new HashMap<>();
19 |
20 | /**
21 | * Get the singleton instance of {@link DrawingBoardManager}
22 | *
23 | * @return DrawingBoardManager instance
24 | */
25 | public static DrawingBoardManager getInstance() {
26 | if (instance == null) {
27 | instance = new DrawingBoardManager();
28 | }
29 | return instance;
30 | }
31 |
32 | private DrawingBoardManager() {
33 | }
34 |
35 | /**
36 | * Creates a new drawing board
37 | *
38 | * @return drawing board just created
39 | */
40 | public DrawingBoard createDrawingBoard() {
41 | DrawingBoard board = new DrawingBoardImpl(generateBoardId());
42 | drawingBoardMap.put(board.getBoardId(), board);
43 | return board;
44 | }
45 |
46 | /**
47 | * Pre-create a new drawing board from json data (boardId)
48 | *
49 | * @param object json data
50 | * @return drawing board
51 | */
52 | public DrawingBoard createDrawingBoard(JSONObject object) {
53 | String boardId = extractBoardId(object);
54 | if (!TextUtils.isEmpty(boardId)) {
55 | DrawingBoard board = new DrawingBoardImpl(boardId);
56 | drawingBoardMap.put(board.getBoardId(), board);
57 | return board;
58 | } else {
59 | return null;
60 | }
61 | }
62 |
63 | /**
64 | * Looks up the drawing board by unique id
65 | *
66 | * @param boardId board id
67 | * @return drawing board just found
68 | */
69 | public DrawingBoard findDrawingBoard(String boardId) {
70 | return drawingBoardMap.get(boardId);
71 | }
72 |
73 | private String generateBoardId() {
74 | return UUID.randomUUID().toString();
75 | }
76 |
77 | private String extractBoardId(JSONObject object) {
78 | return object.optString(DrawingBoardImpl.KEY_BOARD_ID);
79 | }
80 |
81 | }
82 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/operation/UngroupElementOperation.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.operation;
2 |
3 | import com.mocircle.cidrawing.board.ElementManager;
4 | import com.mocircle.cidrawing.element.DrawElement;
5 | import com.mocircle.cidrawing.element.GroupElement;
6 |
7 | public class UngroupElementOperation extends AbstractOperation {
8 |
9 | private ElementManager elementManager;
10 | private GroupElement groupElement;
11 |
12 | public UngroupElementOperation() {
13 | }
14 |
15 | public UngroupElementOperation(GroupElement groupElement) {
16 | this.groupElement = groupElement;
17 | }
18 |
19 | @Override
20 | public void setDrawingBoardId(String boardId) {
21 | super.setDrawingBoardId(boardId);
22 | elementManager = drawingBoard.getElementManager();
23 | }
24 |
25 | @Override
26 | public boolean isExecutable() {
27 | if (groupElement == null) {
28 | DrawElement element = elementManager.getSelection().getSingleElement();
29 | if (element instanceof GroupElement) {
30 | groupElement = (GroupElement) element;
31 | }
32 | }
33 | return groupElement != null;
34 | }
35 |
36 | @Override
37 | public boolean doOperation() {
38 | for (DrawElement element : groupElement.getElements()) {
39 | element.getDisplayMatrix().postConcat(groupElement.getDisplayMatrix());
40 | elementManager.addElementToCurrentLayer(element);
41 | }
42 | elementManager.removeElementFromCurrentLayer(groupElement);
43 |
44 | // Re-select elements
45 | elementManager.clearSelection();
46 | elementManager.selectElements(groupElement.getElements());
47 | drawingBoard.getDrawingView().notifyViewUpdated();
48 | return true;
49 | }
50 |
51 | @Override
52 | public void undo() {
53 | if (groupElement != null) {
54 | for (DrawElement element : groupElement.getElements()) {
55 | elementManager.removeElementFromCurrentLayer(element);
56 | }
57 | elementManager.addElementToCurrentLayer(groupElement);
58 |
59 | // Re-select elements
60 | elementManager.clearSelection();
61 | elementManager.selectElement(groupElement);
62 | drawingBoard.getDrawingView().notifyViewUpdated();
63 | }
64 | }
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/mode/stroke/BaseStrokeMode.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.mode.stroke;
2 |
3 | import com.mocircle.cidrawing.DrawingContext;
4 | import com.mocircle.cidrawing.board.ElementManager;
5 | import com.mocircle.cidrawing.core.CiPaint;
6 | import com.mocircle.cidrawing.element.StrokeElement;
7 | import com.mocircle.cidrawing.mode.BasePointMode;
8 | import com.mocircle.cidrawing.operation.InsertElementOperation;
9 | import com.mocircle.cidrawing.operation.OperationManager;
10 |
11 | public abstract class BaseStrokeMode extends BasePointMode {
12 |
13 | protected ElementManager elementManager;
14 | protected OperationManager operationManager;
15 | protected DrawingContext drawingContext;
16 |
17 | protected StrokeElement element;
18 | protected boolean immutable;
19 |
20 | public BaseStrokeMode() {
21 | }
22 |
23 | public boolean getStrokeImmutable() {
24 | return immutable;
25 | }
26 |
27 | public void setStrokeImmutable(boolean immutable) {
28 | this.immutable = immutable;
29 | if (element != null) {
30 | element.setSelectionEnabled(!immutable);
31 | }
32 | }
33 |
34 | @Override
35 | public void setDrawingBoardId(String boardId) {
36 | super.setDrawingBoardId(boardId);
37 | elementManager = drawingBoard.getElementManager();
38 | operationManager = drawingBoard.getOperationManager();
39 | drawingContext = drawingBoard.getDrawingContext();
40 | }
41 |
42 | @Override
43 | protected void onFirstPointDown(float x, float y) {
44 | element = new StrokeElement();
45 | element.setSelectionEnabled(!immutable);
46 | element.setPaint(assignPaint());
47 | elementManager.addElementToCurrentLayer(element);
48 | element.addPoint(x, y);
49 | }
50 |
51 | @Override
52 | protected void onOverPoint(float x, float y) {
53 | element.addPoint(x, y);
54 | }
55 |
56 | @Override
57 | protected void onLastPointUp(float x, float y, boolean singleTap) {
58 | element.addPoint(x, y);
59 | element.doneEditing();
60 | elementManager.removeElementFromCurrentLayer(element);
61 | operationManager.executeOperation(new InsertElementOperation(element));
62 | }
63 |
64 | @Override
65 | protected void onPointCancelled() {
66 | elementManager.removeElementFromCurrentLayer(element);
67 | }
68 |
69 | protected abstract CiPaint assignPaint();
70 | }
71 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/element/shape/BoxShapeElement.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.element.shape;
2 |
3 | import android.graphics.RectF;
4 |
5 | import com.mocircle.cidrawing.core.Vector2;
6 | import com.mocircle.cidrawing.element.BaseElement;
7 | import com.mocircle.cidrawing.persistence.ConvertUtils;
8 | import com.mocircle.cidrawing.persistence.PersistenceException;
9 |
10 | import org.json.JSONException;
11 | import org.json.JSONObject;
12 |
13 | import java.util.Map;
14 |
15 | /**
16 | * A kind of shape which is able to be drawn correctly with a given box.
17 | */
18 | public abstract class BoxShapeElement extends ShapeElement {
19 |
20 | private static final String KEY_SHAPE_VECTOR = "shapeVector";
21 | private static final String KEY_SHAPE_BOX = "shapeBox";
22 |
23 | protected Vector2 shapeVector;
24 | protected RectF shapeBox = new RectF();
25 |
26 | @Override
27 | public JSONObject generateJson() {
28 | JSONObject object = super.generateJson();
29 | try {
30 | object.put(KEY_SHAPE_VECTOR, ConvertUtils.vectorToJson(shapeVector));
31 | object.put(KEY_SHAPE_BOX, ConvertUtils.rectToJson(shapeBox));
32 | } catch (JSONException e) {
33 | throw new PersistenceException(e);
34 | }
35 | return object;
36 | }
37 |
38 | @Override
39 | public void loadFromJson(JSONObject object, Map resources) {
40 | super.loadFromJson(object, resources);
41 | if (object != null) {
42 | try {
43 | shapeVector = ConvertUtils.vectorFromJson(object.optJSONArray(KEY_SHAPE_VECTOR));
44 | shapeBox = ConvertUtils.rectFromJson(object.optJSONArray(KEY_SHAPE_BOX));
45 | } catch (JSONException e) {
46 | throw new PersistenceException(e);
47 | }
48 | }
49 | }
50 |
51 | @Override
52 | protected void retrieveAttributesFromVector(Vector2 vector) {
53 | shapeVector = vector;
54 | shapeBox = vector.getRect();
55 | }
56 |
57 | @Override
58 | protected void cloneTo(BaseElement element) {
59 | super.cloneTo(element);
60 | if (element instanceof BoxShapeElement) {
61 | BoxShapeElement obj = (BoxShapeElement) element;
62 | if (shapeVector != null) {
63 | obj.shapeVector = new Vector2(shapeVector.getPoint1(), shapeVector.getPoint2());
64 | }
65 | obj.shapeBox = new RectF(shapeBox);
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/mode/transformation/SkewMode.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.mode.transformation;
2 |
3 | import android.graphics.PointF;
4 | import android.graphics.RectF;
5 | import android.view.MotionEvent;
6 |
7 | import com.mocircle.android.logging.CircleLog;
8 | import com.mocircle.cidrawing.element.behavior.Selectable;
9 |
10 | public class SkewMode extends DataTransformMode {
11 |
12 | private static final String TAG = "SkewMode";
13 |
14 | private PointF referencePoint;
15 |
16 | private float downX;
17 | private float downY;
18 |
19 | public SkewMode() {
20 | }
21 |
22 | public SkewMode(boolean autoDetectMode) {
23 | super(autoDetectMode);
24 | }
25 |
26 | @Override
27 | public void onLeaveMode() {
28 | if (element != null) {
29 | element.setSelected(false);
30 | }
31 | }
32 |
33 | @Override
34 | public boolean onTouchEvent(MotionEvent event) {
35 | boolean result = super.onTouchEvent(event);
36 | if (element == null || !element.isSkewEnabled()) {
37 | return result;
38 | }
39 |
40 | switch (event.getAction()) {
41 | case MotionEvent.ACTION_DOWN:
42 | downX = event.getX();
43 | downY = event.getY();
44 | referencePoint = element.getReferencePoint();
45 | return true;
46 | case MotionEvent.ACTION_MOVE:
47 | skewElement(downX, downY, event.getX(), event.getY());
48 | downX = event.getX();
49 | downY = event.getY();
50 | return true;
51 | }
52 | return result;
53 | }
54 |
55 | private void skewElement(float x1, float y1, float x2, float y2) {
56 | float[] points = new float[4];
57 | element.getInvertedDisplayMatrix().mapPoints(points, new float[]{x1, y1, x2, y2});
58 | RectF box = element.getBoundingBox();
59 |
60 | float kx = (points[2] - points[0]) / box.height();
61 | float ky = (points[3] - points[1]) / box.width();
62 |
63 | element.skew(kx, ky, referencePoint.x, referencePoint.y);
64 | CircleLog.d(TAG, "Skew element by " + kx + ", " + ky);
65 | }
66 |
67 | @Override
68 | protected void detectElement(float x, float y) {
69 | super.detectElement(x, y);
70 | elementManager.clearSelection();
71 | if (element != null) {
72 | element.setSelected(true, Selectable.SelectionStyle.LIGHT);
73 | }
74 | }
75 |
76 | }
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/utils/DrawUtils.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.utils;
2 |
3 | import android.content.Context;
4 | import android.graphics.Rect;
5 | import android.view.MotionEvent;
6 | import android.view.ViewConfiguration;
7 |
8 | /**
9 | * A utility class for drawing.
10 | */
11 | public final class DrawUtils {
12 |
13 | private DrawUtils() {
14 | }
15 |
16 | /**
17 | * Checks if touch event is treat as single tap
18 | *
19 | * @param context android context
20 | * @param downX axis x of touch down
21 | * @param downY axis y of touch down
22 | * @param upEvent MotionEvent for touch up
23 | * @return true if it's single tap, otherwise false
24 | */
25 | public static boolean isSingleTap(Context context, float downX, float downY, MotionEvent upEvent) {
26 | return isSingleTap(context, upEvent.getDownTime(), upEvent.getEventTime(), downX, downY, upEvent.getX(), upEvent.getY());
27 | }
28 |
29 | /**
30 | * Checks if touch event is treat as single tap
31 | *
32 | * @param context android context
33 | * @param downTime the time when touch down
34 | * @param upTime the time when touch up
35 | * @param downX axis x of touch down
36 | * @param downY axis y of touch down
37 | * @param upX axis x of touch up
38 | * @param upY axis y of touch up
39 | * @return true if it's single tap, otherwise false
40 | */
41 | public static boolean isSingleTap(Context context, long downTime, long upTime, float downX, float downY, float upX, float upY) {
42 | if (upTime - downTime > ViewConfiguration.getTapTimeout()) {
43 | return false;
44 | }
45 | int touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
46 | final int deltaX = (int) (downX - upX);
47 | final int deltaY = (int) (downY - upY);
48 | int distance = (deltaX * deltaX) + (deltaY * deltaY);
49 | return distance <= touchSlop * touchSlop;
50 | }
51 |
52 | /**
53 | * Create a rectangle based on touch point as center point and touch slop as half side.
54 | *
55 | * @param context android context
56 | * @param x axis x of touch point
57 | * @param y axis y of touch point
58 | * @return rectangle for touch point
59 | */
60 | public static Rect createTouchSquare(Context context, int x, int y) {
61 | int touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
62 | return new Rect(x - touchSlop, y - touchSlop, x + touchSlop, y + touchSlop);
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/mode/InsertShapeMode.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.mode;
2 |
3 | import com.mocircle.cidrawing.core.CiPaint;
4 | import com.mocircle.cidrawing.core.Vector2;
5 | import com.mocircle.cidrawing.element.DrawElement;
6 | import com.mocircle.cidrawing.element.shape.ShapeElement;
7 |
8 | public class InsertShapeMode extends InsertVectorElementMode {
9 |
10 | private static final String TAG = "InsertShapeMode";
11 |
12 | private Class extends ShapeElement> shapeType;
13 | private ShapeElement shapeInstance;
14 |
15 | public InsertShapeMode() {
16 | }
17 |
18 | /**
19 | * Sets shape type, it has the lower priority then {@link #setShapeInstance(ShapeElement)},
20 | * which means if shape instance has been set, it will ignore shape type.
21 | *
22 | * @param shapeType shape type
23 | */
24 | public void setShapeType(Class extends ShapeElement> shapeType) {
25 | this.shapeType = shapeType;
26 | }
27 |
28 | /**
29 | * Sets shape instance, it will create shape according to this sample instance. And it has the
30 | * higher priority then {@link #setShapeType(Class)}, which means if we set shape type and shape
31 | * instance, shape instance will take effect.
32 | *
33 | * @param shapeInstance shape instance
34 | */
35 | public void setShapeInstance(ShapeElement shapeInstance) {
36 | this.shapeInstance = shapeInstance;
37 | }
38 |
39 | @Override
40 | protected DrawElement createPreviewElement() {
41 | previewElement = getShapeInstance();
42 | previewElement.setPaint(paintBuilder.createPreviewPaint(drawingContext.getPaint()));
43 | return previewElement;
44 | }
45 |
46 | @Override
47 | protected DrawElement createRealElement(Vector2 vector) {
48 | ShapeElement element = getShapeInstance();
49 | element.setPaint(new CiPaint(drawingContext.getPaint()));
50 | element.setupElementByVector(vector);
51 | return element;
52 | }
53 |
54 | private ShapeElement getShapeInstance() {
55 | if (shapeInstance != null) {
56 | return (ShapeElement) shapeInstance.clone();
57 | } else if (shapeType != null) {
58 | try {
59 | return shapeType.newInstance();
60 | } catch (InstantiationException e) {
61 | throw new RuntimeException("Cannot create shape.", e);
62 | } catch (IllegalAccessException e) {
63 | throw new RuntimeException("Cannot create shape.", e);
64 | }
65 | } else {
66 | throw new RuntimeException("Cannot find shape type or shape sample instance to create the new shape.");
67 | }
68 | }
69 |
70 | }
71 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # CiDrawing
2 |
3 | [](https://travis-ci.org/mocircle/cidrawing)
4 |
5 | CiDrawing is a vector graphics library for Android, it provides a custom view and together with a set of tools to manage vector graphics drawing.
6 |
7 | ## Supported Features
8 | ### Elements
9 | * Stroke element (Pen)
10 | * Group element (Group/Ungroup)
11 | * Shape element
12 | * Line
13 | * Arc
14 | * Rectangle
15 | * Square
16 | * Oval
17 | * Circle
18 | * Triangle (Isosceles triangle, Right triangle)
19 | * Photo element
20 | * Text element
21 |
22 | ### Functions
23 | * Different drawing type
24 | * Vector mode
25 | * Painting mode
26 | * Basic element transformation
27 | * Move
28 | * Rotate
29 | * Resize
30 | * Skew
31 | * Re-Shape (Reset transformation without change drawing)
32 | * Element selection (Single/Multiple)
33 | * Rectangle select
34 | * Oval select
35 | * Lasso select
36 | * Custom paint (Color, Width, Style, etc)
37 | * Multiple layer support
38 | * Unlimited undo/redo
39 | * Element group/ungroup
40 | * Eraser (Object eraser)
41 | * Path operation
42 | * Union
43 | * Intersect
44 | * Different/Reserved Differernt
45 | * Xor
46 | * Element alignment
47 | * Horizontal (Left, Center, Right)
48 | * Vertical (Top, Middle, Bottom)
49 | * Element flip (based on reference point)
50 | * Horizontal
51 | * Vertical
52 | * Element arrangement
53 | * Forward / Backward
54 | * Bring to Front / Send to Back
55 | * Import / Export
56 |
57 | ## How to Use
58 | Include view in your layout as:
59 | ```xml
60 |
64 | ```
65 | Create drawing board object and set up the view:
66 | ```java
67 | CiDrawingView drawingView = (CiDrawingView) findViewById(R.id.drawing_view);
68 | DrawingBoard drawingBoard = DrawingBoardManager.getInstance().createNewBoard();
69 | drawingBoard.setupDrawingView(drawingView);
70 | ```
71 |
72 | ## Sample Project
73 | Please check out the sample project at [CiDrawing Sample] (https://github.com/mocircle/cidrawing/tree/master/cidrawingsample)
74 |
75 | ### Screenshots
76 | 
77 | 
78 | 
79 |
80 | ## Add to your project
81 | Current CiDrawing is still under developing, not yet published. So please compile source code yourself.
82 |
83 | ## License
84 |
85 | CiDrawing is released under version 2.0 of the Apache License.
86 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/mode/transformation/MoveReferencePointMode.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.mode.transformation;
2 |
3 | import android.graphics.PointF;
4 | import android.view.MotionEvent;
5 |
6 | import com.mocircle.android.logging.CircleLog;
7 | import com.mocircle.cidrawing.mode.ElementOperationMode;
8 | import com.mocircle.cidrawing.operation.MovePointOperation;
9 |
10 | public class MoveReferencePointMode extends ElementOperationMode {
11 |
12 | private static final String TAG = "MoveReferencePointMode";
13 |
14 | private PointF originalReferencePoint;
15 |
16 | private float downX;
17 | private float downY;
18 |
19 | public MoveReferencePointMode() {
20 | }
21 |
22 | @Override
23 | public boolean onTouchEvent(MotionEvent event) {
24 | boolean result = super.onTouchEvent(event);
25 | if (element == null || !element.hasReferencePoint()) {
26 | return result;
27 | }
28 |
29 | switch (event.getAction()) {
30 | case MotionEvent.ACTION_DOWN:
31 | downX = event.getX();
32 | downY = event.getY();
33 | originalReferencePoint = new PointF(element.getReferencePoint().x, element.getReferencePoint().y);
34 | return true;
35 | case MotionEvent.ACTION_MOVE:
36 | float[] points = new float[4];
37 | element.getInvertedDisplayMatrix().mapPoints(points, new float[]{downX, downY, event.getX(), event.getY()});
38 | float dx = points[2] - points[0];
39 | float dy = points[3] - points[1];
40 | element.getReferencePoint().offset(dx, dy);
41 | CircleLog.d(TAG, "Move reference point by " + dx + ", " + dy);
42 | downX = event.getX();
43 | downY = event.getY();
44 | return true;
45 | case MotionEvent.ACTION_UP:
46 | float deltaX = element.getReferencePoint().x - originalReferencePoint.x;
47 | float deltaY = element.getReferencePoint().y - originalReferencePoint.y;
48 | resetPointOffset(deltaX, deltaY);
49 | operationManager.executeOperation(new MovePointOperation(element.getReferencePoint(), deltaX, deltaY));
50 | return true;
51 | case MotionEvent.ACTION_CANCEL:
52 | deltaX = element.getReferencePoint().x - originalReferencePoint.x;
53 | deltaY = element.getReferencePoint().y - originalReferencePoint.y;
54 | resetPointOffset(deltaX, deltaY);
55 | return true;
56 | }
57 | return false;
58 | }
59 |
60 | private void resetPointOffset(float deltaX, float deltaY) {
61 | element.getReferencePoint().offset(-deltaX, -deltaY);
62 | }
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/DrawingBoard.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing;
2 |
3 | import com.mocircle.cidrawing.board.ElementManager;
4 | import com.mocircle.cidrawing.operation.OperationManager;
5 | import com.mocircle.cidrawing.persistence.ExportData;
6 | import com.mocircle.cidrawing.view.DrawingView;
7 |
8 | import org.json.JSONObject;
9 |
10 | import java.util.Map;
11 |
12 | /**
13 | * Drawing board is a main interface to manage the drawing.
14 | */
15 | public interface DrawingBoard {
16 |
17 | /**
18 | * Gets the unique id of the board.
19 | *
20 | * @return board id
21 | */
22 | String getBoardId();
23 |
24 | /**
25 | * Connects drawing view with manager classes. It should be called at the beginning before using
26 | * view.
27 | *
28 | * @param view drawing view implementation
29 | */
30 | void setupDrawingView(DrawingView view);
31 |
32 | /**
33 | * Gets the drawing view object
34 | *
35 | * @return drawing view
36 | */
37 | DrawingView getDrawingView();
38 |
39 | /**
40 | * Gets the drawing context object.
41 | *
42 | * @return drawing context
43 | */
44 | DrawingContext getDrawingContext();
45 |
46 | /**
47 | * Gets element manager.
48 | *
49 | * @return element manager
50 | */
51 | ElementManager getElementManager();
52 |
53 | /**
54 | * Gets operation manager.
55 | *
56 | * @return operation manager
57 | */
58 | OperationManager getOperationManager();
59 |
60 | /**
61 | * Gets configuration manager.
62 | *
63 | * @return configuration manager
64 | */
65 | ConfigManager getConfigManager();
66 |
67 | /**
68 | * Gets paint builder.
69 | *
70 | * @return paint builder
71 | */
72 | PaintBuilder getPaintBuilder();
73 |
74 | /**
75 | * Replaces the paint builder used in the drawing board.
76 | *
77 | * @param paintBuilder paint builder
78 | */
79 | void setPaintBuilder(PaintBuilder paintBuilder);
80 |
81 | /**
82 | * Gets painting behavior instance.
83 | *
84 | * @return painting behavior
85 | */
86 | PaintingBehavior getPaintingBehavior();
87 |
88 | /**
89 | * Replaces the painting behavior implementation used in the drawing board.
90 | *
91 | * @param paintingBehavior painting behavior
92 | */
93 | void setPaintingBehavior(PaintingBehavior paintingBehavior);
94 |
95 | /**
96 | * Export drawing board content
97 | *
98 | * @return json data and external resources
99 | */
100 | ExportData exportData();
101 |
102 | /**
103 | * Load drawing board
104 | *
105 | * @param metaData json format meta data
106 | * @param resources resource mapping
107 | */
108 | void importData(JSONObject metaData, Map resources);
109 |
110 | }
111 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/persistence/PersistenceManager.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.persistence;
2 |
3 | import org.json.JSONArray;
4 | import org.json.JSONException;
5 | import org.json.JSONObject;
6 |
7 | import java.util.ArrayList;
8 | import java.util.Collection;
9 | import java.util.List;
10 | import java.util.Map;
11 | import java.util.UUID;
12 |
13 | public class PersistenceManager {
14 |
15 | private static final String KEY_PERSISTENCE_ID = "_persistenceId";
16 |
17 | public static String generateResourceName() {
18 | return UUID.randomUUID().toString();
19 | }
20 |
21 | public static JSONObject persistObject(Persistable persistable) {
22 | if (persistable == null) {
23 | return null;
24 | }
25 | JSONObject object = persistable.generateJson();
26 | try {
27 | object.put(KEY_PERSISTENCE_ID, persistable.getClass().getName());
28 | } catch (JSONException e) {
29 | throw new PersistenceException(e);
30 | }
31 | return object;
32 | }
33 |
34 | public static JSONArray persistObjects(Collection extends Persistable> persistables) {
35 | if (persistables == null) {
36 | return null;
37 | }
38 | JSONArray array = new JSONArray();
39 | for (Persistable element : persistables) {
40 | array.put(persistObject(element));
41 | }
42 | return array;
43 | }
44 |
45 | public static T buildObject(JSONObject object, Map resources) {
46 | String persistenceId = object.optString(KEY_PERSISTENCE_ID);
47 | try {
48 | Class clazz = Class.forName(persistenceId);
49 | Object instance = clazz.newInstance();
50 | if (instance instanceof Persistable) {
51 | Persistable persistable = (Persistable) instance;
52 | persistable.loadFromJson(object, resources);
53 | return (T) persistable;
54 | } else {
55 | throw new PersistenceException("Cannot create object from json");
56 | }
57 | } catch (ClassNotFoundException e) {
58 | throw new PersistenceException("Cannot create object from json", e);
59 | } catch (InstantiationException e) {
60 | throw new PersistenceException("Cannot create object from json", e);
61 | } catch (IllegalAccessException e) {
62 | throw new PersistenceException("Cannot create object from json", e);
63 | }
64 | }
65 |
66 | public static List buildObjects(JSONArray array, Map resources) throws JSONException {
67 | List objects = new ArrayList<>();
68 | if (array != null) {
69 | for (int i = 0; i < array.length(); i++) {
70 | T object = buildObject(array.getJSONObject(i), resources);
71 | objects.add(object);
72 | }
73 | }
74 | return objects;
75 | }
76 |
77 | }
78 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/operation/OperationManagerImpl.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.operation;
2 |
3 | import com.mocircle.android.logging.CircleLog;
4 |
5 | import java.util.List;
6 | import java.util.Stack;
7 |
8 | public class OperationManagerImpl implements OperationManager {
9 |
10 | private static final String TAG = "OperationManagerImpl";
11 |
12 | private Stack undoOperationList = new Stack<>();
13 | private Stack redoOperationList = new Stack<>();
14 |
15 | private String boardId;
16 |
17 | public OperationManagerImpl(String boardId) {
18 | this.boardId = boardId;
19 | }
20 |
21 | @Override
22 | public void executeOperation(DrawingOperation operation) {
23 | CircleLog.i(TAG, "Execute operation: " + operation.getClass().getSimpleName());
24 | operation.setDrawingBoardId(boardId);
25 | if (operation.isExecutable()) {
26 | boolean result = operation.doOperation();
27 | if (result) {
28 | undoOperationList.push(operation);
29 | redoOperationList.clear();
30 | }
31 | CircleLog.i(TAG, "Execute operation result: " + result);
32 | } else {
33 | CircleLog.i(TAG, "Operation is not executable now");
34 | }
35 | }
36 |
37 | @Override
38 | public List getOperationHistory() {
39 | return undoOperationList;
40 | }
41 |
42 | @Override
43 | public void undo() {
44 | if (!undoOperationList.isEmpty()) {
45 | DrawingOperation operation = undoOperationList.pop();
46 | if (operation != null) {
47 | operation.undo();
48 | redoOperationList.push(operation);
49 | }
50 | }
51 | }
52 |
53 | @Override
54 | public void undoToOperation(DrawingOperation operation) {
55 | for (int i = undoOperationList.size() - 1; i >= 0; i--) {
56 | if (undoOperationList.get(i) == operation) {
57 | int index = undoOperationList.size() - i;
58 | for (int j = 0; j < index; j++) {
59 | undo();
60 | }
61 | break;
62 | }
63 | }
64 | }
65 |
66 | @Override
67 | public void redo() {
68 | if (!redoOperationList.isEmpty()) {
69 | DrawingOperation operation = redoOperationList.pop();
70 | if (operation != null) {
71 | operation.redo();
72 | undoOperationList.push(operation);
73 | }
74 | }
75 | }
76 |
77 | @Override
78 | public void redoToOperation(DrawingOperation operation) {
79 | for (int i = redoOperationList.size() - 1; i >= 0; i--) {
80 | if (redoOperationList.get(i) == operation) {
81 | int index = redoOperationList.size() - i;
82 | for (int j = 0; j < index; j++) {
83 | redo();
84 | }
85 | break;
86 | }
87 | }
88 | }
89 |
90 | }
91 |
--------------------------------------------------------------------------------
/cidrawingsample/src/main/java/com/mocircle/cidrawingsample/LayerAdapter.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawingsample;
2 |
3 | import android.graphics.Color;
4 | import android.support.v7.widget.RecyclerView;
5 | import android.view.LayoutInflater;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 | import android.widget.CheckBox;
9 | import android.widget.CompoundButton;
10 | import android.widget.TextView;
11 |
12 | import com.mocircle.cidrawing.board.Layer;
13 |
14 | import java.util.ArrayList;
15 | import java.util.List;
16 |
17 | public class LayerAdapter extends RecyclerView.Adapter {
18 |
19 | public interface OnRecyclerViewItemClickListener {
20 | void onItemClick(View view, Layer layer);
21 | }
22 |
23 | private List layerList = new ArrayList<>();
24 | private OnRecyclerViewItemClickListener listener;
25 |
26 | public LayerAdapter() {
27 | }
28 |
29 | public void setLayers(List layerList) {
30 | this.layerList = layerList;
31 | }
32 |
33 | public void setOnItemClick(OnRecyclerViewItemClickListener listener) {
34 | this.listener = listener;
35 | }
36 |
37 | @Override
38 | public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
39 | View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.layer_item_layout, parent, false);
40 | ViewHolder vh = new ViewHolder(v);
41 | vh.nameText = (TextView) v.findViewById(R.id.name_text);
42 | vh.visibleBox = (CheckBox) v.findViewById(R.id.visible_checkbox);
43 | v.setOnClickListener(new View.OnClickListener() {
44 | @Override
45 | public void onClick(View v) {
46 | listener.onItemClick(v, (Layer) v.getTag());
47 | }
48 | });
49 | return vh;
50 | }
51 |
52 | @Override
53 | public void onBindViewHolder(ViewHolder holder, int position) {
54 | final Layer layer = layerList.get(position);
55 | holder.itemView.setTag(layer);
56 | holder.nameText.setText(layer.getName());
57 | holder.visibleBox.setChecked(layer.isVisible());
58 | holder.visibleBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
59 | @Override
60 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
61 | layer.setVisible(isChecked);
62 | notifyDataSetChanged();
63 | }
64 | });
65 | if (layer.isSelected()) {
66 | holder.itemView.setBackgroundColor(Color.parseColor("#eeeeee"));
67 | } else {
68 | holder.itemView.setBackgroundColor(0);
69 | }
70 | }
71 |
72 | @Override
73 | public int getItemCount() {
74 | return layerList.size();
75 | }
76 |
77 | public static class ViewHolder extends RecyclerView.ViewHolder {
78 |
79 | public TextView nameText;
80 | public CheckBox visibleBox;
81 |
82 | public ViewHolder(View itemView) {
83 | super(itemView);
84 | }
85 | }
86 |
87 | }
88 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/element/GroupElement.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.element;
2 |
3 | import android.graphics.Canvas;
4 | import android.graphics.Matrix;
5 | import android.graphics.Path;
6 | import android.graphics.RectF;
7 |
8 | import java.util.List;
9 |
10 | /**
11 | * A container element which holds sub elements, and it takes over the sub element's display and
12 | * transformations. Sub elements should not in drawing board, since they are managed by this
13 | * GroupElement.
14 | */
15 | public class GroupElement extends ElementGroup {
16 |
17 | public GroupElement() {
18 | super();
19 | }
20 |
21 | public GroupElement(List elements) {
22 | super(elements);
23 | }
24 |
25 | @Override
26 | public Object clone() {
27 | GroupElement element = new GroupElement();
28 | cloneTo(element);
29 | return element;
30 | }
31 |
32 | @Override
33 | public void drawElement(Canvas canvas) {
34 | for (DrawElement element : elements) {
35 | canvas.save();
36 | Matrix originalDisplay = new Matrix(element.getDisplayMatrix());
37 | canvas.concat(originalDisplay);
38 | element.drawElement(canvas);
39 | canvas.restore();
40 | }
41 | }
42 |
43 | @Override
44 | public void applyMatrixForData(Matrix matrix) {
45 | super.applyMatrixForData(matrix);
46 |
47 | for (DrawElement element : elements) {
48 |
49 | // Before applying data matrix, it should transfer the display matrices to data.
50 | // After that, it should revert back to original display matrix.
51 |
52 | Matrix parentInvertDisplay = new Matrix(getInvertedDisplayMatrix());
53 | Matrix originalDisplay = new Matrix(element.getDisplayMatrix());
54 | originalDisplay.postConcat(parentInvertDisplay);
55 | Matrix originalInvertDisplay = new Matrix();
56 | originalDisplay.invert(originalInvertDisplay);
57 |
58 | element.applyDisplayMatrixToData();
59 | element.applyMatrixForData(matrix);
60 | element.applyMatrixForData(originalInvertDisplay);
61 | element.getDisplayMatrix().postConcat(originalDisplay);
62 | element.updateBoundingBox();
63 | }
64 | recalculateBoundingBox();
65 | }
66 |
67 | @Override
68 | public RectF getOuterBoundingBox() {
69 | RectF box = new RectF();
70 | for (DrawElement element : elements) {
71 | DrawElement temp = (DrawElement) element.clone();
72 | temp.getDisplayMatrix().postConcat(getDisplayMatrix());
73 | box.union(temp.getOuterBoundingBox());
74 | }
75 | return box;
76 | }
77 |
78 | @Override
79 | public Path getTouchableArea() {
80 | Path path = new Path();
81 | for (DrawElement element : elements) {
82 | DrawElement temp = (DrawElement) element.clone();
83 | temp.applyDisplayMatrixToData();
84 | path.op(temp.getTouchableArea(), Path.Op.UNION);
85 | }
86 | return path;
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/operation/PathOperation.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.operation;
2 |
3 | import android.graphics.Path;
4 |
5 | import com.mocircle.cidrawing.element.BasePathElement;
6 | import com.mocircle.cidrawing.element.DrawElement;
7 | import com.mocircle.cidrawing.element.PathElement;
8 |
9 | import java.util.List;
10 |
11 | public class PathOperation extends SelectedElementsOperation {
12 |
13 | private PathElement pathElement;
14 | private Path.Op pathOp = Path.Op.UNION;
15 |
16 | public PathOperation() {
17 | }
18 |
19 | public PathOperation(List elements) {
20 | this.elements = elements;
21 | }
22 |
23 | public Path.Op getPathOp() {
24 | return pathOp;
25 | }
26 |
27 | public void setPathOp(Path.Op pathOp) {
28 | this.pathOp = pathOp;
29 | }
30 |
31 | @Override
32 | public boolean isExecutable() {
33 | super.isExecutable();
34 | int pathElementCount = 0;
35 | for (DrawElement element : elements) {
36 | if (element instanceof BasePathElement) {
37 | pathElementCount++;
38 | }
39 | }
40 | return pathElementCount >= 2;
41 | }
42 |
43 | @Override
44 | public boolean doOperation() {
45 | Path path = null;
46 | boolean firstElement = true;
47 | for (int i = 0; i < elements.size(); i++) {
48 | DrawElement element = elements.get(i);
49 | if (element instanceof BasePathElement) {
50 | if (firstElement) {
51 | path = ((BasePathElement) element).getActualElementPath();
52 | firstElement = false;
53 | } else {
54 | BasePathElement shape = (BasePathElement) element;
55 | path.op(shape.getActualElementPath(), pathOp);
56 | }
57 | }
58 | }
59 |
60 | // Create path element to hold the merged paths
61 | pathElement = new PathElement();
62 | pathElement.setElementPath(path);
63 | pathElement.setPaint(drawingBoard.getDrawingContext().getPaint());
64 |
65 | // Add path element
66 | for (DrawElement element : elements) {
67 | if (element instanceof BasePathElement) {
68 | elementManager.removeElementFromCurrentLayer(element);
69 | }
70 | }
71 | elementManager.addElementToCurrentLayer(pathElement);
72 |
73 | // Re-select elements
74 | elementManager.clearSelection();
75 | elementManager.selectElement(pathElement);
76 | drawingBoard.getDrawingView().notifyViewUpdated();
77 |
78 | return true;
79 | }
80 |
81 | @Override
82 | public void undo() {
83 | // Add path element
84 | for (DrawElement element : elements) {
85 | if (element instanceof BasePathElement) {
86 | elementManager.addElementToCurrentLayer(element);
87 | }
88 | }
89 | elementManager.removeElementFromCurrentLayer(pathElement);
90 |
91 | // Re-select elements
92 | elementManager.clearSelection();
93 | elementManager.selectElements(elements);
94 | drawingBoard.getDrawingView().notifyViewUpdated();
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/DefaultPaintBuilder.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing;
2 |
3 | import android.graphics.Color;
4 | import android.graphics.DashPathEffect;
5 | import android.graphics.Paint;
6 |
7 | import com.mocircle.cidrawing.core.CiPaint;
8 |
9 | /**
10 | * The default implementation of {@link PaintBuilder}.
11 | */
12 | public class DefaultPaintBuilder implements PaintBuilder {
13 |
14 | @Override
15 | public CiPaint createDebugPaintForLine() {
16 | CiPaint paint = new CiPaint();
17 | paint.setColor(Color.RED);
18 | paint.setStyle(Paint.Style.STROKE);
19 | paint.setStrokeWidth(1);
20 | return paint;
21 | }
22 |
23 | @Override
24 | public CiPaint createDebugPaintForArea() {
25 | CiPaint paint = new CiPaint();
26 | paint.setColor(Color.RED);
27 | paint.setAlpha(10);
28 | paint.setStyle(Paint.Style.FILL);
29 | return paint;
30 | }
31 |
32 | @Override
33 | public CiPaint createPreviewPaint(CiPaint originalPaint) {
34 | CiPaint paint = new CiPaint(originalPaint);
35 | paint.setAlpha(paint.getAlpha() / 2);
36 | return paint;
37 | }
38 |
39 | @Override
40 | public CiPaint createPreviewAreaPaint(CiPaint originalPaint) {
41 | CiPaint paint = new CiPaint(originalPaint);
42 | paint.setAlpha(10);
43 | paint.setStyle(Paint.Style.FILL);
44 | return paint;
45 | }
46 |
47 | @Override
48 | public CiPaint createRectSelectionToolPaint() {
49 | CiPaint paint = new CiPaint();
50 | paint.setColor(Color.BLUE);
51 | paint.setStyle(Paint.Style.STROKE);
52 | paint.setStrokeWidth(2);
53 | paint.setPathEffect(new DashPathEffect(new float[]{10, 10}, 0));
54 | return paint;
55 | }
56 |
57 | @Override
58 | public CiPaint createSelectionBoundPaint() {
59 | CiPaint paint = new CiPaint();
60 | paint.setColor(Color.BLUE);
61 | paint.setStyle(Paint.Style.STROKE);
62 | paint.setStrokeWidth(2);
63 | paint.setPathEffect(new DashPathEffect(new float[]{10, 10}, 0));
64 | return paint;
65 | }
66 |
67 | @Override
68 | public CiPaint createSelectionAreaPaint() {
69 | CiPaint paint = createSelectionBoundPaint();
70 | paint.setAlpha(10);
71 | paint.setStyle(Paint.Style.FILL);
72 | return paint;
73 | }
74 |
75 | @Override
76 | public CiPaint createResizingHandlePaint() {
77 | CiPaint paint = new CiPaint();
78 | paint.setColor(Color.BLUE);
79 | paint.setStyle(Paint.Style.FILL);
80 | return paint;
81 | }
82 |
83 | @Override
84 | public CiPaint createRotationHandlePaint() {
85 | CiPaint paint = new CiPaint();
86 | paint.setColor(Color.BLUE);
87 | paint.setStyle(Paint.Style.FILL);
88 | paint.setStrokeWidth(5);
89 | return paint;
90 | }
91 |
92 | @Override
93 | public CiPaint createReferencePointPaint() {
94 | CiPaint paint = new CiPaint();
95 | paint.setColor(Color.BLUE);
96 | paint.setStyle(Paint.Style.FILL);
97 | paint.setStrokeWidth(5);
98 | return paint;
99 | }
100 |
101 |
102 | }
103 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/element/VirtualElement.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.element;
2 |
3 | import android.graphics.Canvas;
4 | import android.graphics.Matrix;
5 | import android.graphics.RectF;
6 |
7 | import java.util.List;
8 |
9 | /**
10 | * A invisible element which holds elements, any transformation will represent to sub elements. This
11 | * virtual element just references to sub elements in the drawing board.
12 | */
13 | public class VirtualElement extends ElementGroup {
14 |
15 | public VirtualElement() {
16 | super();
17 | }
18 |
19 | public VirtualElement(List elements) {
20 | super(elements);
21 | }
22 |
23 | @Override
24 | public Object clone() {
25 | VirtualElement element = new VirtualElement();
26 | cloneTo(element);
27 | return element;
28 | }
29 |
30 | @Override
31 | public void drawElement(Canvas canvas) {
32 | // Draw nothing but debug info
33 | if (configManager.isDebugMode()) {
34 | canvas.drawRect(boundingBox, debugPaintForArea);
35 | }
36 | }
37 |
38 | @Override
39 | public void applyMatrixForData(Matrix matrix) {
40 | super.applyMatrixForData(matrix);
41 |
42 | for (DrawElement element : elements) {
43 |
44 | // Before applying data matrix, it should transfer the extra display matrices to data
45 | // which including element's display matrix and parent's display matrix.
46 |
47 | Matrix parentDisplay = new Matrix(getDisplayMatrix());
48 | Matrix parentInvertDisplay = new Matrix(getInvertedDisplayMatrix());
49 | Matrix originalDisplay = new Matrix(element.getDisplayMatrix());
50 | originalDisplay.postConcat(parentInvertDisplay);
51 | Matrix originalInvertDisplay = new Matrix();
52 | originalDisplay.invert(originalInvertDisplay);
53 |
54 | element.getDisplayMatrix().postConcat(parentInvertDisplay);
55 | element.applyDisplayMatrixToData();
56 | element.applyMatrixForData(matrix);
57 | element.applyMatrixForData(originalInvertDisplay);
58 | element.getDisplayMatrix().postConcat(originalDisplay);
59 | element.getDisplayMatrix().postConcat(parentDisplay);
60 | element.updateBoundingBox();
61 | }
62 | }
63 |
64 | @Override
65 | public RectF getOuterBoundingBox() {
66 | RectF box = new RectF();
67 | for (DrawElement element : elements) {
68 | box.union(element.getOuterBoundingBox());
69 | }
70 | return box;
71 | }
72 |
73 | @Override
74 | public void move(float x, float y) {
75 | super.move(x, y);
76 | for (DrawElement element : elements) {
77 | element.move(x, y);
78 | }
79 | }
80 |
81 | @Override
82 | public void moveTo(float locX, float locY) {
83 | super.moveTo(locX, locY);
84 | for (DrawElement element : elements) {
85 | element.moveTo(locX, locY);
86 | }
87 | }
88 |
89 | @Override
90 | public void rotate(float degree, float px, float py) {
91 | super.rotate(degree, px, py);
92 | for (DrawElement element : elements) {
93 | element.rotate(degree, px, py);
94 | }
95 | }
96 |
97 | }
98 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/element/TextElement.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.element;
2 |
3 | import android.graphics.Canvas;
4 | import android.graphics.Path;
5 | import android.graphics.Rect;
6 | import android.graphics.RectF;
7 |
8 | import com.mocircle.cidrawing.core.CiPaint;
9 | import com.mocircle.cidrawing.core.Vector2;
10 | import com.mocircle.cidrawing.element.behavior.SupportVector;
11 | import com.mocircle.cidrawing.persistence.PersistenceException;
12 |
13 | import org.json.JSONException;
14 | import org.json.JSONObject;
15 |
16 | import java.util.Map;
17 |
18 | public class TextElement extends BoundsElement implements SupportVector {
19 |
20 | private static final String KEY_TEXT = "text";
21 | private static final String KEY_SIZE = "textSize";
22 |
23 | protected String text = "";
24 | protected float textSize;
25 |
26 | public TextElement() {
27 | }
28 |
29 | public String getText() {
30 | return text;
31 | }
32 |
33 | public void setText(String text) {
34 | this.text = text;
35 | calculateBoundingBox();
36 | }
37 |
38 | public float getTextSize() {
39 | return textSize;
40 | }
41 |
42 | public void setTextSize(float textSize) {
43 | this.textSize = textSize;
44 | setupPaint();
45 | }
46 |
47 | @Override
48 | public JSONObject generateJson() {
49 | JSONObject object = super.generateJson();
50 | try {
51 | object.put(KEY_TEXT, text);
52 | object.put(KEY_SIZE, textSize);
53 | } catch (JSONException e) {
54 | throw new PersistenceException(e);
55 | }
56 | return object;
57 | }
58 |
59 | @Override
60 | public void loadFromJson(JSONObject object, Map resources) {
61 | super.loadFromJson(object, resources);
62 | if (object != null) {
63 | text = object.optString(KEY_TEXT, "");
64 | textSize = (float) object.optDouble(KEY_SIZE);
65 | }
66 | }
67 |
68 | @Override
69 | public void afterLoaded() {
70 | setupPaint();
71 | calculateBoundingBox();
72 | }
73 |
74 | @Override
75 | public void setPaint(CiPaint paint) {
76 | super.setPaint(paint);
77 | setupPaint();
78 | calculateBoundingBox();
79 | }
80 |
81 | @Override
82 | public void setupElementByVector(Vector2 vector) {
83 | RectF box = vector.getRect();
84 | move(box.left, box.top);
85 | }
86 |
87 | @Override
88 | public void drawElement(Canvas canvas) {
89 | canvas.save();
90 | canvas.concat(dataMatrix);
91 | canvas.drawText(text, 0, 0, paint);
92 | canvas.restore();
93 | }
94 |
95 | @Override
96 | public Object clone() {
97 | TextElement element = new TextElement();
98 | cloneTo(element);
99 | return element;
100 | }
101 |
102 | @Override
103 | protected void calculateBoundingBox() {
104 | Rect box = new Rect();
105 | paint.getTextBounds(text, 0, text.length(), box);
106 | originalBoundingBox = new RectF(box);
107 | boundingPath = new Path();
108 | boundingPath.addRect(originalBoundingBox, Path.Direction.CW);
109 | boundingPath.transform(dataMatrix);
110 | updateBoundingBox();
111 | }
112 |
113 | @Override
114 | protected void cloneTo(BaseElement element) {
115 | super.cloneTo(element);
116 | if (element instanceof TextElement) {
117 | TextElement obj = (TextElement) element;
118 | obj.text = text;
119 | obj.textSize = textSize;
120 | }
121 | }
122 |
123 | private void setupPaint() {
124 | paint.setTextSize(textSize);
125 | }
126 |
127 | }
128 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/operation/ArrangeOperation.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.operation;
2 |
3 | import com.mocircle.cidrawing.board.ArrangeStrategy;
4 | import com.mocircle.cidrawing.board.Layer;
5 | import com.mocircle.cidrawing.element.DrawElement;
6 |
7 | import java.util.HashMap;
8 | import java.util.List;
9 | import java.util.Map;
10 |
11 | public class ArrangeOperation extends SelectedElementsOperation {
12 |
13 | public enum ArrangeType {
14 | None,
15 | BringForward,
16 | BringToFront,
17 | SendBackward,
18 | SendToBack,
19 | }
20 |
21 | private ArrangeType arrangeType = ArrangeType.None;
22 | private ArrangeStrategy arrangeStrategy = ArrangeStrategy.WITH_FIXED_DISTANCE;
23 | private Map orderMap = new HashMap<>();
24 |
25 | public ArrangeOperation() {
26 | }
27 |
28 | public ArrangeOperation(List elements) {
29 | this.elements = elements;
30 | }
31 |
32 | public ArrangeType getArrangeType() {
33 | return arrangeType;
34 | }
35 |
36 | public void setArrangeType(ArrangeType arrangeType) {
37 | this.arrangeType = arrangeType;
38 | }
39 |
40 | public ArrangeStrategy getArrangeStrategy() {
41 | return arrangeStrategy;
42 | }
43 |
44 | public void setArrangeStrategy(ArrangeStrategy arrangeStrategy) {
45 | this.arrangeStrategy = arrangeStrategy;
46 | }
47 |
48 | @Override
49 | public boolean doOperation() {
50 | // Save current elements ordering
51 | Layer currentLayer = drawingBoard.getElementManager().getCurrentLayer();
52 | for (DrawElement element : elements) {
53 | orderMap.put(element, currentLayer.getElementOrder(element));
54 | }
55 | switch (arrangeType) {
56 | case BringForward:
57 | if (elements.size() == 1) {
58 | currentLayer.arrangeElement(elements.get(0), 1);
59 | } else {
60 | currentLayer.arrangeElements(elements, 1, arrangeStrategy);
61 | }
62 | break;
63 | case SendBackward:
64 | if (elements.size() == 1) {
65 | currentLayer.arrangeElement(elements.get(0), -1);
66 | } else {
67 | currentLayer.arrangeElements(elements, -1, arrangeStrategy);
68 | }
69 | break;
70 | case BringToFront:
71 | if (elements.size() == 1) {
72 | currentLayer.arrangeElementToFront(elements.get(0));
73 | } else {
74 | currentLayer.arrangeElementsToFront(elements, arrangeStrategy);
75 | }
76 | break;
77 | case SendToBack:
78 | if (elements.size() == 1) {
79 | currentLayer.arrangeElementToBack(elements.get(0));
80 | } else {
81 | currentLayer.arrangeElementsToBack(elements, arrangeStrategy);
82 | }
83 | break;
84 | }
85 | drawingBoard.getDrawingView().notifyViewUpdated();
86 | return true;
87 | }
88 |
89 | @Override
90 | public void undo() {
91 | Layer currentLayer = drawingBoard.getElementManager().getCurrentLayer();
92 | for (DrawElement element : elements) {
93 | int currentOrder = currentLayer.getElementOrder(element);
94 | int offset = orderMap.get(element) - currentOrder;
95 | currentLayer.arrangeElement(element, offset);
96 | }
97 | drawingBoard.getDrawingView().notifyViewUpdated();
98 | }
99 |
100 | protected int minimumSelectedElements() {
101 | return 1;
102 | }
103 |
104 | }
105 |
--------------------------------------------------------------------------------
/cidrawinglib/src/test/java/com/mocircle/cidrawing/utils/ListUtilsTest.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.utils;
2 |
3 | import com.mocircle.cidrawing.BuildConfig;
4 |
5 | import org.junit.Assert;
6 | import org.junit.Test;
7 | import org.junit.runner.RunWith;
8 | import org.robolectric.RobolectricTestRunner;
9 | import org.robolectric.annotation.Config;
10 |
11 | import java.util.Arrays;
12 | import java.util.List;
13 |
14 | @RunWith(RobolectricTestRunner.class)
15 | @Config(constants = BuildConfig.class, sdk = 21)
16 | public class ListUtilsTest {
17 |
18 | @Test
19 | public void testShiftItemNormal() {
20 | List list = Arrays.asList("A", "B", "C", "D", "E");
21 | ListUtils.shiftItem(list, 2, 1);
22 | Assert.assertArrayEquals(new String[]{"A", "B", "D", "C", "E"}, list.toArray());
23 |
24 | ListUtils.shiftItem(list, 2, -1);
25 | Assert.assertArrayEquals(new String[]{"A", "D", "B", "C", "E"}, list.toArray());
26 | }
27 |
28 | @Test
29 | public void testShiftItemOverflow() {
30 | List list = Arrays.asList("A", "B", "C", "D", "E");
31 | ListUtils.shiftItem(list, 1, 10);
32 | Assert.assertArrayEquals(new String[]{"A", "C", "D", "E", "B"}, list.toArray());
33 |
34 | ListUtils.shiftItem(list, 3, -10);
35 | Assert.assertArrayEquals(new String[]{"E", "A", "C", "D", "B"}, list.toArray());
36 | }
37 |
38 | @Test
39 | public void testShiftItemsWithFixedDistanceNormal() {
40 | List list = Arrays.asList("A", "B", "C", "D", "E");
41 | ListUtils.shiftItemsWithFixedDistance(list, new int[]{0, 2}, 1);
42 | Assert.assertArrayEquals(new String[]{"B", "A", "D", "C", "E"}, list.toArray());
43 |
44 | ListUtils.shiftItemsWithFixedDistance(list, new int[]{1, 3}, -1);
45 | Assert.assertArrayEquals(new String[]{"A", "B", "C", "D", "E"}, list.toArray());
46 | }
47 |
48 | @Test
49 | public void testShiftItemsWithFixedDistanceOverflow() {
50 | List list = Arrays.asList("A", "B", "C", "D", "E");
51 | ListUtils.shiftItemsWithFixedDistance(list, new int[]{0, 2}, 10);
52 | Assert.assertArrayEquals(new String[]{"B", "D", "A", "E", "C"}, list.toArray());
53 |
54 | ListUtils.shiftItemsWithFixedDistance(list, new int[]{1, 3}, -10);
55 | Assert.assertArrayEquals(new String[]{"D", "B", "E", "A", "C"}, list.toArray());
56 | }
57 |
58 | @Test
59 | public void testShiftItemsWithFixedDistanceOverflowCorner() {
60 | List list = Arrays.asList("A", "B", "C", "D", "E");
61 | ListUtils.shiftItemsWithFixedDistance(list, new int[]{1, 4}, 10);
62 | Assert.assertArrayEquals(new String[]{"A", "B", "C", "D", "E"}, list.toArray());
63 |
64 | ListUtils.shiftItemsWithFixedDistance(list, new int[]{0, 2}, -10);
65 | Assert.assertArrayEquals(new String[]{"A", "B", "C", "D", "E"}, list.toArray());
66 | }
67 |
68 | @Test
69 | public void testShiftItemsAsMuchAsPossibleNormal() {
70 | List list = Arrays.asList("A", "B", "C", "D", "E");
71 | ListUtils.shiftItemsAsMuchAsPossible(list, new int[]{0, 2}, 1);
72 | Assert.assertArrayEquals(new String[]{"B", "A", "D", "C", "E"}, list.toArray());
73 |
74 | ListUtils.shiftItemsAsMuchAsPossible(list, new int[]{1, 3}, -1);
75 | Assert.assertArrayEquals(new String[]{"A", "B", "C", "D", "E"}, list.toArray());
76 | }
77 |
78 | @Test
79 | public void testShiftItemsAsMuchAsPossibleOverflow() {
80 | List list = Arrays.asList("A", "B", "C", "D", "E");
81 | ListUtils.shiftItemsAsMuchAsPossible(list, new int[]{0, 2}, 10);
82 | Assert.assertArrayEquals(new String[]{"B", "D", "E", "A", "C"}, list.toArray());
83 |
84 | ListUtils.shiftItemsAsMuchAsPossible(list, new int[]{1, 3}, -10);
85 | Assert.assertArrayEquals(new String[]{"D", "A", "B", "E", "C"}, list.toArray());
86 | }
87 |
88 | }
89 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/mode/InsertVectorElementMode.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.mode;
2 |
3 | import android.view.MotionEvent;
4 |
5 | import com.mocircle.android.logging.CircleLog;
6 | import com.mocircle.cidrawing.DrawingContext;
7 | import com.mocircle.cidrawing.PaintBuilder;
8 | import com.mocircle.cidrawing.board.ElementManager;
9 | import com.mocircle.cidrawing.core.CiPaint;
10 | import com.mocircle.cidrawing.core.Vector2;
11 | import com.mocircle.cidrawing.element.DrawElement;
12 | import com.mocircle.cidrawing.element.behavior.SupportVector;
13 | import com.mocircle.cidrawing.element.shape.RectElement;
14 | import com.mocircle.cidrawing.operation.InsertElementOperation;
15 | import com.mocircle.cidrawing.operation.OperationManager;
16 |
17 | public class InsertVectorElementMode extends AbstractDrawingMode {
18 |
19 | private static final String TAG = "InsertVectorElementMode";
20 |
21 | protected ElementManager elementManager;
22 | protected DrawingContext drawingContext;
23 | protected OperationManager operationManager;
24 | protected PaintBuilder paintBuilder;
25 |
26 | protected float downX;
27 | protected float downY;
28 |
29 | protected DrawElement previewElement;
30 | protected DrawElement realElement;
31 |
32 | public InsertVectorElementMode() {
33 | }
34 |
35 | @Override
36 | public void setDrawingBoardId(String boardId) {
37 | super.setDrawingBoardId(boardId);
38 | elementManager = drawingBoard.getElementManager();
39 | drawingContext = drawingBoard.getDrawingContext();
40 | operationManager = drawingBoard.getOperationManager();
41 | paintBuilder = drawingBoard.getPaintBuilder();
42 | }
43 |
44 | @Override
45 | public boolean onTouchEvent(MotionEvent event) {
46 | switch (event.getAction()) {
47 | case MotionEvent.ACTION_DOWN:
48 | downX = event.getX();
49 | downY = event.getY();
50 | CircleLog.d(TAG, "Touch Down: x=" + downX + ", y=" + downY);
51 |
52 | previewElement = createPreviewElement();
53 | elementManager.addElementToCurrentLayer(previewElement);
54 | return true;
55 | case MotionEvent.ACTION_MOVE:
56 | if (previewElement != null) {
57 | ((SupportVector) previewElement).setupElementByVector(new Vector2(downX, downY, event.getX(), event.getY()));
58 | }
59 | return true;
60 | case MotionEvent.ACTION_UP:
61 | if (previewElement != null) {
62 | elementManager.removeElementFromCurrentLayer(previewElement);
63 |
64 | DrawElement element = createRealElement(new Vector2(downX, downY, event.getX(), event.getY()));
65 | operationManager.executeOperation(new InsertElementOperation(element));
66 | }
67 | return true;
68 | case MotionEvent.ACTION_CANCEL:
69 | elementManager.removeElementFromCurrentLayer(previewElement);
70 | return true;
71 | }
72 | return false;
73 | }
74 |
75 | protected void setVectorElement(DrawElement realElement) {
76 | this.realElement = realElement;
77 | if (!(realElement instanceof SupportVector)) {
78 | throw new IllegalArgumentException("Element must implement CreateByVector interface.");
79 | }
80 | }
81 |
82 | protected DrawElement createPreviewElement() {
83 | previewElement = new RectElement();
84 | previewElement.setPaint(paintBuilder.createPreviewAreaPaint(drawingContext.getPaint()));
85 | return previewElement;
86 | }
87 |
88 | protected DrawElement createRealElement(Vector2 vector) {
89 | DrawElement element = (DrawElement) realElement.clone();
90 | element.setPaint(new CiPaint(drawingContext.getPaint()));
91 | ((SupportVector) element).setupElementByVector(vector);
92 | return element;
93 | }
94 |
95 | }
96 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/core/CiPaint.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.core;
2 |
3 | import android.graphics.Paint;
4 | import android.graphics.PorterDuff;
5 | import android.graphics.PorterDuffXfermode;
6 | import android.graphics.Xfermode;
7 |
8 | import com.mocircle.android.logging.CircleLog;
9 | import com.mocircle.cidrawing.persistence.Persistable;
10 | import com.mocircle.cidrawing.persistence.PersistenceException;
11 |
12 | import org.json.JSONException;
13 | import org.json.JSONObject;
14 |
15 | import java.lang.reflect.Field;
16 | import java.util.Map;
17 |
18 | public class CiPaint extends Paint implements Persistable {
19 |
20 | private static final String TAG = "CiPaint";
21 |
22 | private static final String KEY_COLOR = "color";
23 | private static final String KEY_STROKE_WIDTH = "strokeWidth";
24 | private static final String KEY_FLAGS = "flags";
25 | private static final String KEY_ALPHA = "alpha";
26 | private static final String KEY_STYLE = "style";
27 | private static final String KEY_SECONDARY_COLOR = "secondaryColor";
28 | private static final String KEY_XFERMODE = "xfermode";
29 |
30 | private Integer secondaryColor;
31 |
32 | public CiPaint() {
33 | super();
34 | }
35 |
36 | public CiPaint(int flags) {
37 | super(flags);
38 | }
39 |
40 | public CiPaint(Paint paint) {
41 | super(paint);
42 | if (paint instanceof CiPaint) {
43 | CiPaint p = (CiPaint) paint;
44 | secondaryColor = p.secondaryColor;
45 | }
46 | }
47 |
48 | public Integer getSecondaryColor() {
49 | return secondaryColor;
50 | }
51 |
52 | public void setSecondaryColor(Integer color) {
53 | secondaryColor = color;
54 | }
55 |
56 | @Override
57 | public JSONObject generateJson() {
58 | JSONObject object = new JSONObject();
59 | try {
60 | object.put(KEY_COLOR, this.getColor());
61 | object.put(KEY_STROKE_WIDTH, this.getStrokeWidth());
62 | object.put(KEY_FLAGS, this.getFlags());
63 | object.put(KEY_ALPHA, this.getAlpha());
64 | object.put(KEY_STYLE, this.getStyle().toString());
65 | object.put(KEY_SECONDARY_COLOR, this.getSecondaryColor());
66 | object.put(KEY_XFERMODE, getXfermodeAsInt(this.getXfermode()));
67 | } catch (JSONException e) {
68 | throw new PersistenceException(e);
69 | }
70 | return object;
71 | }
72 |
73 | @Override
74 | public Map generateResources() {
75 | return null;
76 | }
77 |
78 | @Override
79 | public void loadFromJson(JSONObject object, Map resources) {
80 | if (object != null) {
81 | try {
82 | setColor(object.getInt(KEY_COLOR));
83 | setStrokeWidth((float) object.getDouble(KEY_STROKE_WIDTH));
84 | setFlags(object.getInt(KEY_FLAGS));
85 | setAlpha(object.getInt(KEY_ALPHA));
86 | setStyle(Style.valueOf(object.getString(KEY_STYLE)));
87 | if (object.has(KEY_SECONDARY_COLOR)) {
88 | secondaryColor = object.getInt(KEY_SECONDARY_COLOR);
89 | }
90 | if (object.has(KEY_XFERMODE)) {
91 | setXfermode(getXfermodeFromInt(object.getInt(KEY_XFERMODE)));
92 | }
93 | } catch (JSONException e) {
94 | throw new PersistenceException(e);
95 | }
96 | }
97 | }
98 |
99 | @Override
100 | public void afterLoaded() {
101 | }
102 |
103 |
104 | private int getXfermodeAsInt(Xfermode mode) {
105 | if (mode == null) {
106 | return -1;
107 | }
108 | try {
109 | Field field = Xfermode.class.getDeclaredField("porterDuffMode");
110 | field.setAccessible(true);
111 | return field.getInt(mode);
112 | } catch (NoSuchFieldException e) {
113 | CircleLog.w(TAG, e);
114 | } catch (IllegalAccessException e) {
115 | CircleLog.w(TAG, e);
116 | }
117 | return -1;
118 | }
119 |
120 | private Xfermode getXfermodeFromInt(int mode) {
121 | if (mode < 0) {
122 | return null;
123 | } else {
124 | PorterDuffXfermode xfermode = new PorterDuffXfermode(PorterDuff.Mode.values()[mode]);
125 | return xfermode;
126 | }
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/element/StrokeElement.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.element;
2 |
3 | import android.graphics.Matrix;
4 | import android.graphics.Path;
5 | import android.graphics.PointF;
6 |
7 | import com.mocircle.cidrawing.persistence.ConvertUtils;
8 | import com.mocircle.cidrawing.persistence.PersistenceException;
9 |
10 | import org.json.JSONException;
11 | import org.json.JSONObject;
12 |
13 | import java.util.ArrayList;
14 | import java.util.List;
15 | import java.util.Map;
16 |
17 | public class StrokeElement extends BasePathElement {
18 |
19 | private static final String KEY_POINTS = "points";
20 | private static final String KEY_CLOSE_STROKE = "closeStroke";
21 |
22 | private List points = new ArrayList<>();
23 | private boolean closeStroke;
24 |
25 | public StrokeElement() {
26 | }
27 |
28 | public boolean isCloseStroke() {
29 | return closeStroke;
30 | }
31 |
32 | public void setCloseStroke(boolean closeStroke) {
33 | this.closeStroke = closeStroke;
34 | }
35 |
36 | @Override
37 | public Object clone() {
38 | StrokeElement element = new StrokeElement();
39 | cloneTo(element);
40 | return element;
41 | }
42 |
43 | public void addPoint(float x, float y) {
44 | points.add(new PointF(x, y));
45 |
46 | // Sync to path
47 | if (elementPath == null) {
48 | elementPath = new Path();
49 | }
50 | if (points.size() == 1) {
51 | elementPath.moveTo(x, y);
52 | } else {
53 | elementPath.lineTo(x, y);
54 | }
55 | }
56 |
57 | public void doneEditing() {
58 | elementPath = createStrokePath();
59 | updateBoundingBox();
60 | }
61 |
62 | @Override
63 | public JSONObject generateJson() {
64 | JSONObject object = super.generateJson();
65 | try {
66 | object.put(KEY_POINTS, ConvertUtils.pointsToJson(points));
67 | object.put(KEY_CLOSE_STROKE, closeStroke);
68 | } catch (JSONException e) {
69 | throw new PersistenceException(e);
70 | }
71 | return object;
72 | }
73 |
74 | @Override
75 | public void loadFromJson(JSONObject object, Map resources) {
76 | super.loadFromJson(object, resources);
77 | if (object != null) {
78 | try {
79 | points = ConvertUtils.pointsFromJson(object.optJSONArray(KEY_POINTS));
80 | closeStroke = object.optBoolean(KEY_CLOSE_STROKE, false);
81 | } catch (JSONException e) {
82 | throw new PersistenceException(e);
83 | }
84 | }
85 | }
86 |
87 | @Override
88 | public void afterLoaded() {
89 | elementPath = createStrokePath();
90 | updateBoundingBox();
91 | }
92 |
93 | @Override
94 | public void applyMatrixForData(Matrix matrix) {
95 | super.applyMatrixForData(matrix);
96 |
97 | applyMatrixForPoints(matrix);
98 | elementPath = createStrokePath();
99 | }
100 |
101 | @Override
102 | protected void cloneTo(BaseElement element) {
103 | super.cloneTo(element);
104 | if (element instanceof StrokeElement) {
105 | StrokeElement obj = (StrokeElement) element;
106 | if (points != null) {
107 | obj.points = new ArrayList<>();
108 | for (PointF p : points) {
109 | obj.points.add(new PointF(p.x, p.y));
110 | }
111 | }
112 | obj.closeStroke = closeStroke;
113 | }
114 | }
115 |
116 | private Path createStrokePath() {
117 | Path path = new Path();
118 | for (int i = 0; i < points.size(); i++) {
119 | PointF p = points.get(i);
120 | if (i == 0) {
121 | path.moveTo(p.x, p.y);
122 | } else {
123 | path.lineTo(p.x, p.y);
124 | }
125 | }
126 | if (closeStroke) {
127 | path.close();
128 | }
129 | return path;
130 | }
131 |
132 | private void applyMatrixForPoints(Matrix matrix) {
133 | float[] newPoints = new float[points.size() * 2];
134 | int i = 0;
135 | for (PointF p : points) {
136 | newPoints[i] = p.x;
137 | newPoints[i + 1] = p.y;
138 | i += 2;
139 | }
140 | matrix.mapPoints(newPoints);
141 | for (int j = 0; j < newPoints.length; j += 2) {
142 | points.get(j / 2).set(newPoints[j], newPoints[j + 1]);
143 | }
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/cidrawingsample/src/main/res/layout/right_drawer_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
14 |
15 |
21 |
22 |
28 |
29 |
35 |
36 |
42 |
43 |
49 |
50 |
56 |
57 |
63 |
64 |
70 |
71 |
77 |
78 |
84 |
85 |
91 |
92 |
98 |
99 |
105 |
111 |
112 |
118 |
119 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/mode/PointerMode.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.mode;
2 |
3 | import android.view.MotionEvent;
4 |
5 | import com.mocircle.android.logging.CircleLog;
6 | import com.mocircle.cidrawing.board.ElementManager;
7 | import com.mocircle.cidrawing.element.DrawElement;
8 | import com.mocircle.cidrawing.element.behavior.ResizingDirection;
9 | import com.mocircle.cidrawing.mode.selection.RectSelectionMode;
10 | import com.mocircle.cidrawing.mode.selection.SelectionMode;
11 | import com.mocircle.cidrawing.mode.transformation.MoveMode;
12 | import com.mocircle.cidrawing.mode.transformation.MoveReferencePointMode;
13 | import com.mocircle.cidrawing.mode.transformation.ResizeMode;
14 | import com.mocircle.cidrawing.mode.transformation.RotateMode;
15 |
16 | public class PointerMode extends CompositeMode {
17 |
18 | private static final String TAG = "PointerMode";
19 |
20 | private ElementManager elementManager;
21 | private DrawingMode currentMode;
22 |
23 | private SelectionMode selectionMode = new RectSelectionMode();
24 | private MoveReferencePointMode moveReferencePointMode = new MoveReferencePointMode();
25 | private MoveMode moveMode = new MoveMode();
26 | private RotateMode rotateMode = new RotateMode();
27 | private ResizeMode resizeMode = new ResizeMode();
28 |
29 | public void setSelectionMode(SelectionMode selectionMode) {
30 | this.selectionMode = selectionMode;
31 | }
32 |
33 | @Override
34 | public void setDrawingBoardId(String boardId) {
35 | super.setDrawingBoardId(boardId);
36 |
37 | elementManager = drawingBoard.getElementManager();
38 |
39 | selectionMode.setDrawingBoardId(boardId);
40 | moveReferencePointMode.setDrawingBoardId(boardId);
41 | moveMode.setDrawingBoardId(boardId);
42 | rotateMode.setDrawingBoardId(boardId);
43 | resizeMode.setDrawingBoardId(boardId);
44 | }
45 |
46 | @Override
47 | public void onLeaveMode() {
48 | if (currentMode != null) {
49 | currentMode.onLeaveMode();
50 | }
51 | }
52 |
53 | @Override
54 | public boolean onTouchEvent(MotionEvent event) {
55 | switch (event.getAction()) {
56 | case MotionEvent.ACTION_DOWN:
57 | // Auto switch mode
58 | hitTestForSwitchingMode(event.getX(), event.getY());
59 |
60 | currentMode.onTouchEvent(event);
61 | break;
62 | case MotionEvent.ACTION_MOVE:
63 | currentMode.onTouchEvent(event);
64 | break;
65 | case MotionEvent.ACTION_UP:
66 | currentMode.onTouchEvent(event);
67 | break;
68 | case MotionEvent.ACTION_CANCEL:
69 | currentMode.onTouchEvent(event);
70 | break;
71 | }
72 | return true;
73 | }
74 |
75 |
76 | private void hitTestForSwitchingMode(float x, float y) {
77 | for (int i = elementManager.getCurrentObjects().length - 1; i >= 0; i--) {
78 | DrawElement element = elementManager.getCurrentObjects()[i];
79 | if (element.isSelected()) {
80 |
81 | if (element.hitTestForReferencePoint(x, y)) {
82 | moveReferencePointMode.setElement(element);
83 | currentMode = moveReferencePointMode;
84 | CircleLog.i(TAG, "Switch to ReferencePointMode");
85 | return;
86 | }
87 |
88 | if (element.hitTestForRotationHandle(x, y)) {
89 | rotateMode.setElement(element);
90 | currentMode = rotateMode;
91 | CircleLog.i(TAG, "Switch to RotateMode");
92 | return;
93 | }
94 |
95 | ResizingDirection direction = element.hitTestForResizingHandle(x, y);
96 | if (direction != ResizingDirection.NONE) {
97 | resizeMode.setElement(element);
98 | resizeMode.setResizingDirection(direction);
99 | currentMode = resizeMode;
100 | CircleLog.i(TAG, "Switch to ResizeMode");
101 | return;
102 | }
103 |
104 | if (element.hitTestForSelection(x, y)) {
105 | moveMode.setElement(element);
106 | currentMode = moveMode;
107 | CircleLog.i(TAG, "Switch to MoveMode");
108 | return;
109 | }
110 | }
111 | }
112 | currentMode = selectionMode;
113 | CircleLog.i(TAG, "Switch to SelectionMode");
114 | }
115 |
116 | }
117 |
--------------------------------------------------------------------------------
/cidrawinglib/src/main/java/com/mocircle/cidrawing/mode/selection/SelectionMode.java:
--------------------------------------------------------------------------------
1 | package com.mocircle.cidrawing.mode.selection;
2 |
3 | import android.graphics.Path;
4 | import android.view.MotionEvent;
5 |
6 | import com.mocircle.cidrawing.PaintBuilder;
7 | import com.mocircle.cidrawing.board.ElementManager;
8 | import com.mocircle.cidrawing.core.CiPaint;
9 | import com.mocircle.cidrawing.element.DrawElement;
10 | import com.mocircle.cidrawing.element.VirtualElement;
11 | import com.mocircle.cidrawing.mode.AbstractDrawingMode;
12 | import com.mocircle.cidrawing.utils.DrawUtils;
13 |
14 | import java.util.ArrayList;
15 | import java.util.List;
16 |
17 | public abstract class SelectionMode extends AbstractDrawingMode {
18 |
19 | private static final String TAG = "SelectionMode";
20 |
21 | protected ElementManager elementManager;
22 | protected PaintBuilder paintBuilder;
23 | protected CiPaint selectionPaint;
24 |
25 | protected float downX;
26 | protected float downY;
27 |
28 | protected DrawElement selectionElement;
29 | protected VirtualElement virtualElement;
30 |
31 | public SelectionMode() {
32 | }
33 |
34 | @Override
35 | public void setDrawingBoardId(String boardId) {
36 | super.setDrawingBoardId(boardId);
37 | elementManager = drawingBoard.getElementManager();
38 | paintBuilder = drawingBoard.getPaintBuilder();
39 | selectionPaint = paintBuilder.createRectSelectionToolPaint();
40 | }
41 |
42 | @Override
43 | public void onLeaveMode() {
44 | elementManager.clearSelection();
45 | drawingBoard.getDrawingView().notifyViewUpdated();
46 | }
47 |
48 | @Override
49 | public boolean onTouchEvent(MotionEvent event) {
50 | switch (event.getAction()) {
51 | case MotionEvent.ACTION_DOWN:
52 | downX = event.getX();
53 | downY = event.getY();
54 |
55 | selectionElement = createSelectionElement();
56 | selectionElement.setPaint(selectionPaint);
57 | elementManager.addAdornmentToCurrentLayer(selectionElement);
58 | return true;
59 | case MotionEvent.ACTION_UP:
60 | elementManager.removeAdornmentFromCurrentLayer(selectionElement);
61 | elementManager.clearSelection();
62 |
63 | if (DrawUtils.isSingleTap(drawingBoard.getDrawingView().getContext(), downX, downY, event)) {
64 |
65 | // Single selection
66 | for (int i = elementManager.getCurrentObjects().length - 1; i >= 0; i--) {
67 | DrawElement element = elementManager.getCurrentObjects()[i];
68 | if (element.isSelectionEnabled()) {
69 | if (element.hitTestForSelection(downX, downY)) {
70 | // Only allow one element selected
71 | element.setSelected(true);
72 | break;
73 | }
74 | }
75 | }
76 |
77 | } else {
78 |
79 | // Multiple selection
80 | List selectedElements = new ArrayList<>();
81 | for (int i = elementManager.getCurrentObjects().length - 1; i >= 0; i--) {
82 | DrawElement element = elementManager.getCurrentObjects()[i];
83 | element.setSelected(false);
84 | if (element.isSelectionEnabled() && element.hitTestForSelection(getSelectionPath())) {
85 | selectedElements.add(element);
86 | }
87 | }
88 | if (selectedElements.size() == 1) {
89 | // Multiple selection with single element
90 | selectedElements.get(0).setSelected(true);
91 | } else if (selectedElements.size() > 1) {
92 | // Real multiple selection
93 | virtualElement = new VirtualElement(selectedElements);
94 | virtualElement.setSelected(true);
95 | elementManager.addAdornmentToCurrentLayer(virtualElement);
96 | }
97 | }
98 |
99 | return true;
100 | case MotionEvent.ACTION_CANCEL:
101 | elementManager.removeAdornmentFromCurrentLayer(selectionElement);
102 | return true;
103 | }
104 | return false;
105 | }
106 |
107 | protected abstract DrawElement createSelectionElement();
108 |
109 | protected abstract Path getSelectionPath();
110 |
111 | }
112 |
--------------------------------------------------------------------------------