1 /* 2 * Copyright 2019 The Android Open Source Project 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 package com.plausiblesoftware.drumthumper 17 18 import android.content.Context 19 import android.graphics.Canvas 20 import android.graphics.Color 21 import android.graphics.Paint 22 import android.graphics.RectF 23 import android.util.AttributeSet 24 import android.util.TypedValue 25 import android.view.MotionEvent 26 import android.view.View 27 28 class TriggerPad: View { 29 30 private val mDrawRect = RectF() 31 private val mPaint = Paint() 32 33 private val mUpColor = Color.LTGRAY 34 private val mDownColor = Color.DKGRAY 35 private var mIsDown = false 36 37 private var mText = "DrumPad" 38 private var mTextSizeSp = 28.0f 39 40 private val mTextColor = Color.BLACK 41 42 val DISPLAY_MASK = 0x00000003 43 val DISPLAY_RECT = 0x00000000 44 val DISPLAY_CIRCLE = 0x00000001 45 val DISPLAY_ROUND_RECT = 0x00000002 46 47 private var mDisplayFlags = DISPLAY_ROUND_RECT 48 49 interface DrumPadTriggerListener { triggerDownnull50 fun triggerDown(pad: TriggerPad) 51 fun triggerUp(pad: TriggerPad) 52 } 53 54 var mListeners = ArrayList<DrumPadTriggerListener>() 55 56 constructor(context: Context) : super(context) 57 58 constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { 59 extractAttributes(attrs) 60 } 61 62 constructor(context: Context, attrs: AttributeSet, defStyle: Int): super(context, attrs, defStyle) { 63 extractAttributes(attrs) 64 } 65 66 // 67 // Attributes 68 // extractAttributesnull69 private fun extractAttributes(attrs: AttributeSet) { 70 val xmlns = "http://schemas.android.com/apk/res/android" 71 val textVal = attrs.getAttributeValue(xmlns, "text") 72 if (textVal != null) { 73 mText = textVal 74 } 75 } 76 77 // 78 // Layout Routines 79 // calcTextSizeInPixelsnull80 private fun calcTextSizeInPixels(): Float { 81 return TypedValue.applyDimension( 82 TypedValue.COMPLEX_UNIT_SP, 83 mTextSizeSp, 84 resources.displayMetrics 85 ) 86 } 87 onSizeChangednull88 override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { 89 val padLeft = paddingLeft 90 val padRight = paddingRight 91 val padTop = paddingTop 92 val padBottom = paddingBottom 93 94 mDrawRect.set(padLeft.toFloat(), 95 padTop.toFloat(), 96 w - padRight.toFloat(), 97 h - padBottom.toFloat()) 98 99 // mTextSize = mDrawRect.bottom / 4.0f 100 } 101 onMeasurenull102 override fun onMeasure (widthMeasureSpec: Int, heightMeasureSpec: Int) { 103 val width = MeasureSpec.getSize(widthMeasureSpec) 104 105 val padTop = paddingTop 106 val padBottom = paddingBottom 107 108 val heightMode = MeasureSpec.getMode(heightMeasureSpec) 109 var height = MeasureSpec.getSize(heightMeasureSpec) 110 111 val textSizePixels = calcTextSizeInPixels() 112 when (heightMode) { 113 MeasureSpec.AT_MOST -> run { 114 // mText = "AT_MOST" 115 val newHeight = (textSizePixels.toInt() * 2) + padTop + padBottom 116 height = minOf(height, newHeight) } 117 118 MeasureSpec.EXACTLY -> run { 119 /*mText = "EXACTLY"*/ } 120 121 MeasureSpec.UNSPECIFIED -> run { 122 // mText = "UNSPECIFIED" 123 height = textSizePixels.toInt() } 124 } 125 126 setMeasuredDimension(width, height) 127 } 128 129 // 130 // Drawing Routines 131 // onDrawnull132 override fun onDraw(canvas: Canvas) { 133 // Face 134 if (mIsDown) { 135 mPaint.color = mDownColor 136 } else { 137 mPaint.color = mUpColor 138 } 139 140 when (mDisplayFlags and DISPLAY_MASK) { 141 DISPLAY_RECT -> canvas.drawRect(mDrawRect, mPaint) 142 143 DISPLAY_CIRCLE -> run { 144 val midX = mDrawRect.left + mDrawRect.width() / 2.0f 145 val midY = mDrawRect.top + mDrawRect.height() / 2.0f 146 val radius = minOf(mDrawRect.height() / 2.0f, mDrawRect.width() / 2.0f) 147 canvas.drawCircle(midX, midY, radius - 5.0f, mPaint) 148 } 149 150 DISPLAY_ROUND_RECT -> run { 151 val rad = minOf(mDrawRect.width() / 8.0f, mDrawRect.height() / 8.0f) 152 canvas.drawRoundRect(mDrawRect, rad, rad, mPaint) 153 } 154 } 155 156 // Text 157 val midX = mDrawRect.width() / 2 158 mPaint.textSize = calcTextSizeInPixels() 159 val textWidth = mPaint.measureText(mText) 160 mPaint.color = mTextColor 161 val textSizePixels = calcTextSizeInPixels() 162 canvas.drawText(mText, mDrawRect.left + midX - textWidth / 2, 163 mDrawRect.bottom/2 + textSizePixels/2, mPaint) 164 165 } 166 167 // 168 // Input Routines 169 // onTouchEventnull170 override fun onTouchEvent(event: MotionEvent): Boolean { 171 if (event.actionMasked == MotionEvent.ACTION_DOWN || 172 event.actionMasked == MotionEvent.ACTION_POINTER_DOWN) { 173 mIsDown = true; 174 triggerDown() 175 invalidate() 176 return true 177 } else if (event.actionMasked == MotionEvent.ACTION_UP) { 178 mIsDown = false; 179 triggerUp() 180 invalidate() 181 return true 182 } 183 184 return false 185 } 186 187 // 188 // Event Listeners 189 // addListenernull190 fun addListener(listener: DrumPadTriggerListener) { 191 mListeners.add(listener) 192 } 193 triggerDownnull194 private fun triggerDown() { 195 for( listener in mListeners) { 196 listener.triggerDown(this) 197 } 198 } 199 triggerUpnull200 private fun triggerUp() { 201 for( listener in mListeners) { 202 listener.triggerUp(this) 203 } 204 } 205 } 206