├── drawingapp ├── __init__.py ├── __main__.py └── app.py ├── requirements-dev.txt ├── screenshot.png ├── setup.py ├── .gitignore ├── README.md └── LICENSE /drawingapp/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | briefcase>=0.1.6 2 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eliasdorneles/drawingapp-voc/HEAD/screenshot.png -------------------------------------------------------------------------------- /drawingapp/__main__.py: -------------------------------------------------------------------------------- 1 | from drawingapp.app import main 2 | 3 | 4 | if __name__ == '__main__': 5 | main() 6 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from setuptools import setup, find_packages 3 | 4 | 5 | setup( 6 | name='drawingapp', 7 | version='0.0.1', 8 | description='An app for drawing stuff', 9 | author='Elias Dorneles', 10 | author_email='eliasdorneles@gmail.com', 11 | license='BSD license', 12 | packages=find_packages( 13 | exclude=['docs', 'tests', 'android'] 14 | ), 15 | classifiers=[ 16 | 'Development Status :: 1 - Planning', 17 | 'License :: OSI Approved :: BSD license', 18 | ], 19 | install_requires=[ 20 | ], 21 | options={ 22 | 'app': { 23 | 'formal_name': 'Drawing App', 24 | 'bundle': 'org.pybee.elias' 25 | }, 26 | } 27 | ) 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # OSX useful to ignore 7 | *.DS_Store 8 | .AppleDouble 9 | .LSOverride 10 | 11 | # Thumbnails 12 | ._* 13 | 14 | # Files that might appear in the root of a volume 15 | .DocumentRevisions-V100 16 | .fseventsd 17 | .Spotlight-V100 18 | .TemporaryItems 19 | .Trashes 20 | .VolumeIcon.icns 21 | .com.apple.timemachine.donotpresent 22 | 23 | # Directories potentially created on remote AFP share 24 | .AppleDB 25 | .AppleDesktop 26 | Network Trash Folder 27 | Temporary Items 28 | .apdisk 29 | 30 | # C extensions 31 | *.so 32 | 33 | # Distribution / packaging 34 | .Python 35 | env/ 36 | build/ 37 | develop-eggs/ 38 | dist/ 39 | downloads/ 40 | eggs/ 41 | .eggs/ 42 | lib/ 43 | lib64/ 44 | parts/ 45 | sdist/ 46 | var/ 47 | *.egg-info/ 48 | .installed.cfg 49 | *.egg 50 | 51 | # IntelliJ Idea family of suites 52 | .idea 53 | *.iml 54 | ## File-based project format: 55 | *.ipr 56 | *.iws 57 | ## mpeltonen/sbt-idea plugin 58 | .idea_modules/ 59 | 60 | android 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Drawing App 2 | =========== 3 | 4 | This is a native Android app written in Python, using the [BeeWare](http://pybee.org/) suite. 5 | 6 | The Python code is compiled to Java bytecode using [VOC](http://pybee.org/voc), 7 | and the Android APK is packaged using [briefcase](https://github.com/pybee/briefcase). 8 | 9 | **Requirements** 10 | 11 | * JDK 12 | * Android SDK 13 | 14 | ## How to run 15 | 16 | Ensure that you have [Android SDK](https://developer.android.com/studio/index.html#downloads) installed. 17 | 18 | Install the Python development requirements: 19 | 20 | pip install -r requirements-dev.txt 21 | 22 | [Plug in your Android device](https://developer.android.com/training/basics/firstapp/running-app.html) or [start an emulator](https://developer.android.com/studio/run/emulator-commandline.html). 23 | 24 | Build and run the app: 25 | 26 | python setup.py android --start 27 | 28 | ![App screenshot](screenshot.png) 29 | 30 | 31 | ## Want to know more? 32 | 33 | If you liked this, you might like: 34 | 35 | * [TicTacToe native Android app written in Python](https://github.com/eliasdorneles/tictactoe-voc) 36 | * [Template for native Android apps written in Python](https://github.com/eliasdorneles/beeware-android-template) 37 | * [Video of a talk explaining how VOC works](https://www.youtube.com/watch?v=9c4DEYIXYCM) 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Copyright (c) 2017, Elias Dorneles 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, 6 | are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, this 12 | list of conditions and the following disclaimer in the documentation and/or 13 | other materials provided with the distribution. 14 | 15 | * Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from this 17 | software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 20 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 22 | IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 23 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 24 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 26 | OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 27 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 28 | OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /drawingapp/app.py: -------------------------------------------------------------------------------- 1 | import android 2 | from android.widget import LinearLayout, TextView, Button 3 | import android.content.Context 4 | from android.graphics import Bitmap, Canvas, Color, Paint, Path, PorterDuff 5 | from android.view import MotionEvent, Gravity 6 | import android.view 7 | 8 | class ButtonClick(implements=android.view.View[OnClickListener]): 9 | def __init__(self, callback, *args, **kwargs): 10 | self.callback = callback 11 | self.args = args 12 | self.kwargs = kwargs 13 | 14 | def onClick(self, view: android.view.View) -> void: 15 | self.callback(*self.args, **self.kwargs) 16 | 17 | class DrawingView(extends=android.view.View): 18 | @super({context: android.content.Context}) 19 | def __init__(self, context): 20 | self.setBackgroundColor(0xfffefefe) 21 | self.brushSize = 20.0 22 | self.drawPath = Path() 23 | self.drawPaint = Paint() 24 | self.porterDuff = PorterDuff() 25 | self.canvasPaint = None 26 | self.paintColor = 0xff55aa00 27 | self.canvasBitmap = None 28 | self.drawCanvas = None 29 | self.setupDrawing() 30 | 31 | def setupDrawing(self): 32 | self.drawPaint.setColor(self.paintColor) 33 | self.drawPaint.setAntiAlias(True) 34 | self.drawPaint.setStrokeWidth(self.brushSize) 35 | self.drawPaint.setStyle(Paint.Style.STROKE) 36 | self.drawPaint.setStrokeJoin(Paint.Join.ROUND) 37 | self.drawPaint.setStrokeCap(Paint.Cap.ROUND) 38 | 39 | self.canvasPaint = Paint(Paint.DITHER_FLAG) 40 | 41 | def onSizeChanged(self, w: int, h: int, oldw: int, oldh: int) -> void: 42 | print('onSizeChange running') 43 | # super.onSizeChanged(w, h, oldw, oldh); 44 | self.canvasBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888) 45 | self.drawCanvas = Canvas(self.canvasBitmap) 46 | 47 | def onDraw(self, canvas: android.graphics.Canvas) -> void: 48 | print('onDraw running') 49 | canvas.drawBitmap(self.canvasBitmap, 0, 0, self.canvasPaint) 50 | canvas.drawPath(self.drawPath, self.drawPaint) 51 | 52 | def onTouchEvent(self, event: android.view.MotionEvent) -> bool: 53 | print('onTouchEvent running') 54 | touchX = event.getX() 55 | touchY = event.getY() 56 | action = event.getAction() 57 | if action == MotionEvent.ACTION_DOWN: 58 | self.drawPath.moveTo(touchX, touchY) 59 | elif action == MotionEvent.ACTION_MOVE: 60 | self.drawPath.lineTo(touchX, touchY) 61 | elif action == MotionEvent.ACTION_UP: 62 | self.drawCanvas.drawPath(self.drawPath, self.drawPaint) 63 | self.drawPath.reset() 64 | self.invalidate() 65 | return True 66 | 67 | def changeColor(self, color: int) -> void: 68 | print('changing color final step') 69 | self.paintColor = color 70 | self.drawPaint.setColor(self.paintColor) 71 | 72 | def change_brushsize(self, size: int) -> void: 73 | self.brushSize = size 74 | self.drawPaint.setStrokeWidth(self.brushSize) 75 | 76 | def reset(self) -> void: 77 | print('reseting') 78 | self.drawCanvas.drawColor(0, self.porterDuff.Mode.CLEAR) 79 | self.invalidate() 80 | 81 | class MainApp: 82 | def __init__(self): 83 | self._activity = android.PythonActivity.setListener(self) 84 | 85 | def onCreate(self): 86 | drawingView = DrawingView(self._activity) 87 | 88 | hlayout = LinearLayout(self._activity) 89 | hlayout.setOrientation(LinearLayout.HORIZONTAL) 90 | hlayout.setGravity(Gravity.CENTER) 91 | 92 | small_brush = Button(self._activity) 93 | small_brush.setText('Small') 94 | small_brush.setOnClickListener(ButtonClick(drawingView.change_brushsize, 5.0)) 95 | hlayout.addView(small_brush) 96 | 97 | medium_brush = Button(self._activity) 98 | medium_brush.setText('Medium') 99 | medium_brush.setOnClickListener(ButtonClick(drawingView.change_brushsize, 10.0)) 100 | hlayout.addView(medium_brush) 101 | 102 | large_brush = Button(self._activity) 103 | large_brush.setText('Large') 104 | large_brush.setOnClickListener(ButtonClick(drawingView.change_brushsize, 20.0)) 105 | hlayout.addView(large_brush) 106 | 107 | reset_button = Button(self._activity) 108 | reset_button.setText('Reset') 109 | reset_button.setOnClickListener(ButtonClick(drawingView.reset)) 110 | hlayout.addView(reset_button) 111 | 112 | # colors 113 | 114 | hlayout1 = LinearLayout(self._activity) 115 | hlayout1.setOrientation(LinearLayout.HORIZONTAL) 116 | hlayout1.setGravity(Gravity.CENTER) 117 | 118 | red_color = Button(self._activity) 119 | red_color.setText('Red') 120 | red_color.getBackground().setColorFilter(0xffff4444, PorterDuff.Mode.MULTIPLY) 121 | red_color.setOnClickListener(ButtonClick(drawingView.changeColor, 0xffff4444)) 122 | hlayout1.addView(red_color) 123 | 124 | orange_color = Button(self._activity) 125 | orange_color.setText('Orange') 126 | orange_color.getBackground().setColorFilter(0xffffBB33, PorterDuff.Mode.MULTIPLY) 127 | orange_color.setOnClickListener(ButtonClick(drawingView.changeColor, 0xffffBB33)) 128 | hlayout1.addView(orange_color) 129 | 130 | green_color = Button(self._activity) 131 | green_color.setText('Green') 132 | green_color.getBackground().setColorFilter(0xff55aa00, PorterDuff.Mode.MULTIPLY) 133 | green_color.setOnClickListener(ButtonClick(drawingView.changeColor, 0xff55aa00)) 134 | hlayout1.addView(green_color) 135 | 136 | blue_color = Button(self._activity) 137 | blue_color.setText('Blue') 138 | blue_color.getBackground().setColorFilter(0xff33b5e5, PorterDuff.Mode.MULTIPLY) 139 | blue_color.setOnClickListener(ButtonClick(drawingView.changeColor, 0xff33b5e5)) 140 | hlayout1.addView(blue_color) 141 | 142 | vlayout = LinearLayout(self._activity) 143 | vlayout.setOrientation(LinearLayout.VERTICAL) 144 | 145 | vlayout.addView(hlayout) 146 | vlayout.addView(hlayout1) 147 | vlayout.addView(drawingView) 148 | 149 | self._activity.setContentView(vlayout) 150 | 151 | def main(): 152 | MainApp() 153 | --------------------------------------------------------------------------------