diff --git a/libnavui-androidauto/CHANGELOG.md b/libnavui-androidauto/CHANGELOG.md index 4e602998884..e51f0bae1fc 100644 --- a/libnavui-androidauto/CHANGELOG.md +++ b/libnavui-androidauto/CHANGELOG.md @@ -6,6 +6,7 @@ Mapbox welcomes participation and contributions from everyone. #### Features #### Bug fixes and improvements - Fixed an issue with speed limit widget that caused it to turn into a back rectangle. [#6064](https://github.com/mapbox/mapbox-navigation-android/pull/6064) +- Fixed an issue with compass and logo widgets that caused them to draw behind location puck. [#6076](https://github.com/mapbox/mapbox-navigation-android/pull/6076) ## androidauto-v0.4.0 - Jul 12, 2022 ### Changelog diff --git a/libnavui-androidauto/src/androidTest/java/com/mapbox/androidauto/car/navigation/speedlimit/SpeedLimitRendererTest.kt b/libnavui-androidauto/src/androidTest/java/com/mapbox/androidauto/car/navigation/speedlimit/SpeedLimitRendererTest.kt index c442bf9560d..4a7c3978b64 100644 --- a/libnavui-androidauto/src/androidTest/java/com/mapbox/androidauto/car/navigation/speedlimit/SpeedLimitRendererTest.kt +++ b/libnavui-androidauto/src/androidTest/java/com/mapbox/androidauto/car/navigation/speedlimit/SpeedLimitRendererTest.kt @@ -29,13 +29,11 @@ class SpeedLimitRendererTest { "test_speed_limit_images" ) - private val speedLimitWidget = SpeedLimitWidget() - @Test fun speed_limit_120_speed_150() { bitmapUtils.assertBitmapsSimilar( testName, - speedLimitWidget.drawSpeedLimitSign(speedLimit = 120, speed = 150) + SpeedLimitWidget.drawSpeedLimitSign(speedLimit = 120, speed = 150), ) } @@ -43,7 +41,7 @@ class SpeedLimitRendererTest { fun speed_limit_120_speed_90() { bitmapUtils.assertBitmapsSimilar( testName, - speedLimitWidget.drawSpeedLimitSign(speedLimit = 120, speed = 90) + SpeedLimitWidget.drawSpeedLimitSign(speedLimit = 120, speed = 90), ) } @@ -51,7 +49,7 @@ class SpeedLimitRendererTest { fun speed_limit_65_speed_90() { bitmapUtils.assertBitmapsSimilar( testName, - speedLimitWidget.drawSpeedLimitSign(speedLimit = 65, speed = 90) + SpeedLimitWidget.drawSpeedLimitSign(speedLimit = 65, speed = 90), ) } @@ -59,7 +57,7 @@ class SpeedLimitRendererTest { fun speed_limit_65_speed_30() { bitmapUtils.assertBitmapsSimilar( testName, - speedLimitWidget.drawSpeedLimitSign(speedLimit = 65, speed = 30) + SpeedLimitWidget.drawSpeedLimitSign(speedLimit = 65, speed = 30), ) } @@ -67,7 +65,7 @@ class SpeedLimitRendererTest { fun speed_limit_5_speed_30() { bitmapUtils.assertBitmapsSimilar( testName, - speedLimitWidget.drawSpeedLimitSign(speedLimit = 5, speed = 30) + SpeedLimitWidget.drawSpeedLimitSign(speedLimit = 5, speed = 30), ) } @@ -75,7 +73,7 @@ class SpeedLimitRendererTest { fun speed_limit_5_speed_0() { bitmapUtils.assertBitmapsSimilar( testName, - speedLimitWidget.drawSpeedLimitSign(speedLimit = 5, speed = 0) + SpeedLimitWidget.drawSpeedLimitSign(speedLimit = 5, speed = 0), ) } @@ -83,7 +81,7 @@ class SpeedLimitRendererTest { fun speed_limit_unknown_speed_5() { bitmapUtils.assertBitmapsSimilar( testName, - speedLimitWidget.drawSpeedLimitSign(speedLimit = null, speed = 5) + SpeedLimitWidget.drawSpeedLimitSign(speedLimit = null, speed = 5), ) } } diff --git a/libnavui-androidauto/src/main/java/com/mapbox/androidauto/car/map/widgets/ImageOverlayHost.kt b/libnavui-androidauto/src/main/java/com/mapbox/androidauto/car/map/widgets/ImageOverlayHost.kt deleted file mode 100644 index 02b96eace7b..00000000000 --- a/libnavui-androidauto/src/main/java/com/mapbox/androidauto/car/map/widgets/ImageOverlayHost.kt +++ /dev/null @@ -1,455 +0,0 @@ -package com.mapbox.androidauto.car.map.widgets - -import android.graphics.Bitmap -import android.opengl.GLES20 -import android.opengl.GLUtils -import android.opengl.Matrix -import com.mapbox.common.Logger -import com.mapbox.maps.BuildConfig -import com.mapbox.maps.CustomLayerHost -import com.mapbox.maps.CustomLayerRenderParameters -import com.mapbox.navigation.utils.internal.logE -import java.nio.ByteBuffer -import java.nio.ByteOrder -import java.nio.FloatBuffer - -data class Margin( - val marginLeft: Float = 0f, - val marginTop: Float = 0f, - val marginRight: Float = 0f, - val marginBottom: Float = 0f, -) - -open class ImageOverlayHost( - private var bitmap: Bitmap, - private val position: WidgetPosition = WidgetPosition.BOTTOM_LEFT, - private var margins: Margin = Margin(), - var shouldRender: Boolean = true -) : CustomLayerHost { - private var width = 0 - private var height = 0 - - private var program = 0 - private var vertexPositionHandle = 0 - private var texturePositionHandle = 0 - private var textureHandle = 0 - private var screenMatrixHandle = 0 - private var rotationMatrixHandle = 0 - private var translateMatrixHandle = 0 - private val textures = intArrayOf(0) - - private var vertexShader = 0 - private var fragmentShader = 0 - - private lateinit var screenMatrixData: FloatArray - private lateinit var screenMatrixBuffer: FloatBuffer - - private val rotationMatrix = FloatArray(MATRIX_SIZE).apply { - Matrix.setIdentityM(this, 0) - } - - private val translateMatrix = FloatArray(MATRIX_SIZE) - - private lateinit var vertexPositionData: FloatArray - private lateinit var vertexPositionBuffer: FloatBuffer - - init { - Matrix.setIdentityM(rotationMatrix, 0) - Matrix.setIdentityM(translateMatrix, 0) - } - - private val texturePositionData = floatArrayOf( - 0f, 0f, - 0f, 1f, - 1f, 0f, - 1f, 1f - ) - - private fun onSizeChanged(width: Int, height: Int, margins: Margin) { - if (this.width == width && this.height == height && this.margins == margins) return - this.width = width - this.height = height - this.margins = margins - logE( - TAG, - "onSizeChanged-> bitmap size: ${bitmap.width}," + - " ${bitmap.height}; screen size: $width, $height" - ) - val heightOffset = when (position) { - WidgetPosition.BOTTOM_LEFT -> - height.toFloat() - bitmap.height.toFloat() / 2f - margins.marginBottom - WidgetPosition.BOTTOM_RIGHT -> - height.toFloat() - bitmap.height.toFloat() / 2f - margins.marginBottom - WidgetPosition.TOP_LEFT -> margins.marginTop + bitmap.height.toFloat() / 2f - WidgetPosition.TOP_RIGHT -> margins.marginTop + bitmap.height.toFloat() / 2f - } - val widthOffset = when (position) { - WidgetPosition.TOP_RIGHT -> - width.toFloat() - bitmap.width.toFloat() / 2f - margins.marginRight - WidgetPosition.BOTTOM_RIGHT -> - width.toFloat() - bitmap.width.toFloat() / 2f - margins.marginRight - WidgetPosition.TOP_LEFT -> margins.marginLeft + bitmap.width.toFloat() / 2f - WidgetPosition.BOTTOM_LEFT -> margins.marginLeft + bitmap.width.toFloat() / 2f - } - - Matrix.setIdentityM(translateMatrix, 0) - Matrix.translateM( - translateMatrix, - 0, - widthOffset, - heightOffset, - 0f - ) - - // The screen matrix - // - // First of all, the only coordinate system that OpenGL understands - // put the center of the screen at the 0,0 position. The maximum value of - // the X axis is 1 (rightmost part of the screen) and the minimum is -1 - // (leftmost part of the screen). The same thing goes for the Y axis, - // where 1 is the top of the screen and -1 the bottom. - // - // However, when you're doing a 2d application you often need to think in 'pixels' - // (or something like that). If you have a 300x300 screen, you want to see the center - // at 150,150 not 0,0! - // - // The solution to this 'problem' is to multiply a matrix with your position to - // another matrix that will convert 'your' coordinates to the one OpenGL expects. - // There's no magic in this, only a bit of math. Try to multiply the uScreen matrix - // to the 150,150 position in a sheet of paper and look at the results. - // - // IMPORTANT: When trying to calculate the matrix on paper, you should treat the - // uScreen ROWS as COLUMNS and vice versa. This happens because OpenGL expect the - // matrix values ordered in a more efficient way, that unfortunately is different - // from the mathematical notation :( - screenMatrixData = floatArrayOf( - 2f / width.toFloat(), 0f, 0f, 0f, - 0f, 2f / -height.toFloat(), 0f, 0f, - 0f, 0f, 0f, 0f, - -1f, 1f, 0f, 1f - ) - - // initialize vertex byte buffer for shape coordinates - // (number of coordinate values * 4 bytes per float) - screenMatrixBuffer = screenMatrixData.toFloatBuffer() - - vertexPositionData = floatArrayOf( - -bitmap.width.toFloat() / 2f, -bitmap.height.toFloat() / 2f, - -bitmap.width.toFloat() / 2f, bitmap.height.toFloat() / 2f, - bitmap.width.toFloat() / 2f, -bitmap.height.toFloat() / 2f, - bitmap.width.toFloat() / 2f, bitmap.height.toFloat() / 2f, - ) - - // initialize vertex byte buffer for shape coordinates - // (number of coordinate values * 4 bytes per float) - vertexPositionBuffer = vertexPositionData.toFloatBuffer() - } - - override fun initialize() { - // load and compile shaders - vertexShader = loadShader( - GLES20.GL_VERTEX_SHADER, - VERTEX_SHADER_CODE - ).also { checkCompileStatus(it) } - - fragmentShader = loadShader( - GLES20.GL_FRAGMENT_SHADER, - FRAGMENT_SHADER_CODE - ).also { checkCompileStatus(it) } - - // create empty OpenGL ES Program - program = GLES20.glCreateProgram().also { - checkError("glCreateProgram") - // add the vertex shader to program - GLES20.glAttachShader(it, vertexShader).also { checkError("glAttachShader") } - - // add the fragment shader to program - GLES20.glAttachShader(it, fragmentShader).also { checkError("glAttachShader") } - - // creates OpenGL ES program executables - GLES20.glLinkProgram(it).also { - checkError("glLinkProgram") - } - } - - // get handle to screen matrix(fragment shader's uScreen member) - screenMatrixHandle = - GLES20.glGetUniformLocation(program, "uScreen") - .also { checkError("glGetAttribLocation") } - - // get handle to rotation matrix(fragment shader's uRotation member) - rotationMatrixHandle = - GLES20.glGetUniformLocation(program, "uRotation") - .also { checkError("glGetAttribLocation") } - - translateMatrixHandle = - GLES20.glGetUniformLocation(program, "uTranslate") - .also { checkError("glGetAttribLocation") } - - // get handle to vertex shader's aPosition member - vertexPositionHandle = - GLES20.glGetAttribLocation(program, "aPosition") - .also { checkError("glGetAttribLocation") } - - // get handle to texture coordinate(vertex shader's aCoordinate member) - texturePositionHandle = - GLES20.glGetAttribLocation(program, "aCoordinate") - .also { checkError("glGetAttribLocation") } - - // get handle to fragment shader's vColor member - textureHandle = - GLES20.glGetUniformLocation(program, "vTexture") - .also { checkError("glGetAttribLocation") } - } - - override fun render(parameters: CustomLayerRenderParameters) { - if (!shouldRender) return - onSizeChanged(parameters.width.toInt(), parameters.height.toInt(), margins) - if (program != 0) { - // Add program to OpenGL ES environment - GLES20.glUseProgram(program).also { - checkError("glUseProgram") - } - GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, texturePositionHandle).also { - checkError("glBindBuffer") - } - - GLES20.glUniformMatrix4fv( - screenMatrixHandle, - screenMatrixBuffer.limit() / screenMatrixData.size, - false, - screenMatrixBuffer - ) - - val rotationBuffer = rotationMatrix.toFloatBuffer() - GLES20.glUniformMatrix4fv( - rotationMatrixHandle, - rotationBuffer.limit() / rotationMatrix.size, - false, - rotationBuffer - ) - - val translateBuffer = translateMatrix.toFloatBuffer() - GLES20.glUniformMatrix4fv( - translateMatrixHandle, - rotationBuffer.limit() / translateMatrix.size, - false, - translateBuffer - ) - - createTexture() - - // Activate the first texture (GL_TEXTURE0) and bind it to our handle - GLES20.glActiveTexture(GLES20.GL_TEXTURE0) - GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[0]) - - // Textures - GLES20.glUniform1i(textureHandle, 0) - - // Enable a handle to the vertices - GLES20.glEnableVertexAttribArray(vertexPositionHandle) - .also { checkError("glEnableVertexAttribArray") } - - // Prepare the vertex coordinate data - GLES20.glVertexAttribPointer( - vertexPositionHandle, COORDS_PER_VERTEX, - GLES20.GL_FLOAT, false, - VERTEX_STRIDE, vertexPositionBuffer - ).also { checkError("glVertexAttribPointer") } - - // Enable a handle to the tex position - GLES20.glEnableVertexAttribArray(texturePositionHandle) - .also { checkError("glEnableVertexAttribArray") } - - // Prepare the texture coordinate data - GLES20.glVertexAttribPointer( - texturePositionHandle, COORDS_PER_VERTEX, - GLES20.GL_FLOAT, false, - VERTEX_STRIDE, texturePositionData.toFloatBuffer() - ).also { checkError("glVertexAttribPointer") } - - // Draw the background - GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, VERTEX_COUNT) - .also { checkError("glDrawArrays") } - } - } - - override fun contextLost() { - Logger.w(TAG, "contextLost") - program = 0 - } - - override fun deinitialize() { - if (program != 0) { - // Disable vertex array - GLES20.glDisableVertexAttribArray(vertexPositionHandle) - GLES20.glDisableVertexAttribArray(texturePositionHandle) - GLES20.glDisableVertexAttribArray(textureHandle) - GLES20.glDisableVertexAttribArray(screenMatrixHandle) - GLES20.glDisableVertexAttribArray(rotationMatrixHandle) - GLES20.glDisableVertexAttribArray(translateMatrixHandle) - GLES20.glDetachShader(program, vertexShader) - GLES20.glDetachShader(program, fragmentShader) - GLES20.glDeleteShader(vertexShader) - GLES20.glDeleteShader(fragmentShader) - GLES20.glDeleteTextures(textures.size, textures, 0) - GLES20.glDeleteProgram(program) - program = 0 - } - } - - private fun checkCompileStatus(shader: Int) { - if (BuildConfig.DEBUG) { - val isCompiled = IntArray(1) - GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, isCompiled, 0) - if (isCompiled[0] == GLES20.GL_FALSE) { - val infoLog = GLES20.glGetShaderInfoLog(program) - Logger.e(TAG, "checkCompileStatus error: $infoLog") - } - } - } - - private fun createTexture() { - if (!bitmap.isRecycled) { - Logger.d(TAG, "createTexture") - // generate texture - GLES20.glGenTextures(1, textures, 0) - // generate texture - GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[0]) - // set the color filter is reduced pixel color closest to the coordinates of a pixel in - // texture drawn as required - GLES20.glTexParameteri( - GLES20.GL_TEXTURE_2D, - GLES20.GL_TEXTURE_MIN_FILTER, - GLES20.GL_NEAREST - ) - // set the amplification filter using texture coordinates to the nearest number of colors, - // obtained by the weighted averaging algorithm requires pixel color drawn - GLES20.glTexParameteri( - GLES20.GL_TEXTURE_2D, - GLES20.GL_TEXTURE_MAG_FILTER, - GLES20.GL_LINEAR - ) - // Set the circumferential direction S, texture coordinates taken to [1 / 2n, 1-1 / 2n]. - // Will never lead to integration and border - GLES20.glTexParameteri( - GLES20.GL_TEXTURE_2D, - GLES20.GL_TEXTURE_WRAP_S, - GLES20.GL_CLAMP_TO_EDGE - ) - // Set the circumferential direction T, taken to texture coordinates [1 / 2n, 1-1 / 2n]. - // Will never lead to integration and border - GLES20.glTexParameteri( - GLES20.GL_TEXTURE_2D, - GLES20.GL_TEXTURE_WRAP_T, - GLES20.GL_CLAMP_TO_EDGE - ) - // The parameters specified above, generates a 2D texture - GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0) - bitmap.recycle() - } - } - - fun updateBitmap(bitmap: Bitmap) { - shouldRender = true - this.bitmap = bitmap - onSizeChanged(width, height, margins) - } - - fun rotate(bearing: Float) { - Matrix.setIdentityM(rotationMatrix, 0) - Matrix.rotateM(rotationMatrix, 0, bearing, 0f, 0f, 1f) - } - - fun updateMargins(margins: Margin) { - onSizeChanged(width, height, margins) - } - - companion object { - private const val TAG = "ImageOverlayHost" - private const val MATRIX_SIZE = 16 - - // number of coordinates per vertex in this array - private const val COORDS_PER_VERTEX = 2 - private const val VERTEX_STRIDE = COORDS_PER_VERTEX * BYTES_PER_FLOAT // 4 bytes per vertex - private const val VERTEX_COUNT = 4 // 4 vertex in total - - private val VERTEX_SHADER_CODE = """ - uniform mat4 uScreen; - uniform mat4 uTranslate; - uniform mat4 uRotation; - attribute vec2 aPosition; - attribute vec2 aCoordinate; - varying vec2 vTexCoord; - void main() { - vTexCoord = aCoordinate; - gl_Position = uScreen * uTranslate * uRotation * vec4(aPosition, 0.0, 1.0); - } - """.trimIndent() - - private val FRAGMENT_SHADER_CODE = """ - precision mediump float; - uniform sampler2D vTexture; - varying vec2 vTexCoord; - void main() { - gl_FragColor = texture2D(vTexture, vTexCoord); - } - """.trimIndent() - - private fun loadShader(type: Int, shaderCode: String): Int { - // create a vertex shader type (GLES20.GL_VERTEX_SHADER) - // or a fragment shader type (GLES20.GL_FRAGMENT_SHADER) - return GLES20.glCreateShader(type).also { shader -> - - // add the source code to the shader and compile it - GLES20.glShaderSource(shader, shaderCode) - GLES20.glCompileShader(shader) - } - } - - private fun checkError(cmd: String? = null) { - if (BuildConfig.DEBUG) { - when (val error = GLES20.glGetError()) { - GLES20.GL_NO_ERROR -> { - Logger.d(TAG, "$cmd -> no error") - } - GLES20.GL_INVALID_ENUM -> Logger.e(TAG, "$cmd -> error in gl: GL_INVALID_ENUM") - GLES20.GL_INVALID_VALUE -> Logger.e( - TAG, - "$cmd -> error in gl: GL_INVALID_VALUE" - ) - GLES20.GL_INVALID_OPERATION -> Logger.e( - TAG, - "$cmd -> error in gl: GL_INVALID_OPERATION" - ) - GLES20.GL_INVALID_FRAMEBUFFER_OPERATION -> Logger.e( - TAG, - "$cmd -> error in gl: GL_INVALID_FRAMEBUFFER_OPERATION" - ) - GLES20.GL_OUT_OF_MEMORY -> Logger.e( - TAG, - "$cmd -> error in gl: GL_OUT_OF_MEMORY" - ) - else -> Logger.e(TAG, "$cmd -> error in gl: $error") - } - } - } - } -} - -private const val BYTES_PER_FLOAT = 4 - -private fun FloatArray.toFloatBuffer(): FloatBuffer { - return ByteBuffer.allocateDirect(size * BYTES_PER_FLOAT).run { - // use the device hardware's native byte order - order(ByteOrder.nativeOrder()) - - // create a floating point buffer from the ByteBuffer - asFloatBuffer().apply { - // add the coordinates to the FloatBuffer - put(this@toFloatBuffer) - // set the buffer to read the first coordinate - rewind() - } - } -} diff --git a/libnavui-androidauto/src/main/java/com/mapbox/androidauto/car/map/widgets/WidgetPosition.kt b/libnavui-androidauto/src/main/java/com/mapbox/androidauto/car/map/widgets/WidgetPosition.kt deleted file mode 100644 index b7084469a21..00000000000 --- a/libnavui-androidauto/src/main/java/com/mapbox/androidauto/car/map/widgets/WidgetPosition.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.mapbox.androidauto.car.map.widgets - -/** - * Gravity that the widget is aligned to the map. - */ -enum class WidgetPosition { - - /** - * The widget is aligned to the top-left of the map. - */ - TOP_LEFT, - - /** - * The widget is aligned to the top-right of the map. - */ - TOP_RIGHT, - - /** - * The widget is aligned to the bottom-left of the map. - */ - BOTTOM_LEFT, - - /** - * The widget is aligned to the bottom-right of the map. - */ - BOTTOM_RIGHT -} diff --git a/libnavui-androidauto/src/main/java/com/mapbox/androidauto/car/map/widgets/compass/CarCompassSurfaceRenderer.kt b/libnavui-androidauto/src/main/java/com/mapbox/androidauto/car/map/widgets/compass/CarCompassSurfaceRenderer.kt index 0c925aa1aed..b1eaf880783 100644 --- a/libnavui-androidauto/src/main/java/com/mapbox/androidauto/car/map/widgets/compass/CarCompassSurfaceRenderer.kt +++ b/libnavui-androidauto/src/main/java/com/mapbox/androidauto/car/map/widgets/compass/CarCompassSurfaceRenderer.kt @@ -1,46 +1,34 @@ package com.mapbox.androidauto.car.map.widgets.compass -import com.mapbox.androidauto.car.internal.extensions.getStyle -import com.mapbox.androidauto.car.internal.extensions.getStyleAsync -import com.mapbox.androidauto.car.map.widgets.logo.LogoWidget -import com.mapbox.maps.LayerPosition import com.mapbox.maps.MapboxExperimental import com.mapbox.maps.MapboxMap import com.mapbox.maps.extension.androidauto.MapboxCarMapObserver import com.mapbox.maps.extension.androidauto.MapboxCarMapSurface +import com.mapbox.maps.extension.androidauto.widgets.CompassWidget import com.mapbox.maps.plugin.delegates.listeners.OnCameraChangeListener @OptIn(MapboxExperimental::class) -class CarCompassSurfaceRenderer( - private val layerPosition: LayerPosition? = null -) : MapboxCarMapObserver { +class CarCompassSurfaceRenderer : MapboxCarMapObserver { private var mapboxMap: MapboxMap? = null private var compassWidget: CompassWidget? = null private val onCameraChangeListener = OnCameraChangeListener { _ -> - mapboxMap?.cameraState?.bearing?.toFloat()?.let { - compassWidget?.updateBearing(it) - } + mapboxMap?.cameraState?.bearing?.toFloat()?.let { compassWidget?.setRotation(-it) } } override fun onAttached(mapboxCarMapSurface: MapboxCarMapSurface) { - val compassWidget = CompassWidget(mapboxCarMapSurface.carContext) + val compassWidget = + CompassWidget(mapboxCarMapSurface.carContext, marginX = 26f, marginY = 120f) val mapboxMap = mapboxCarMapSurface.mapSurface.getMapboxMap().also { mapboxMap = it } this.compassWidget = compassWidget - mapboxCarMapSurface.getStyleAsync { - it.addPersistentStyleCustomLayer( - CompassWidget.COMPASS_WIDGET_LAYER_ID, - compassWidget.host, - layerPosition - ) - } + mapboxCarMapSurface.mapSurface.addWidget(compassWidget) mapboxMap.addOnCameraChangeListener(onCameraChangeListener) } override fun onDetached(mapboxCarMapSurface: MapboxCarMapSurface) { mapboxCarMapSurface.mapSurface.getMapboxMap() .removeOnCameraChangeListener(onCameraChangeListener) - mapboxCarMapSurface.getStyle()?.removeStyleLayer(LogoWidget.LOGO_WIDGET_LAYER_ID) + compassWidget?.let { mapboxCarMapSurface.mapSurface.removeWidget(it) } compassWidget = null mapboxMap = null } diff --git a/libnavui-androidauto/src/main/java/com/mapbox/androidauto/car/map/widgets/compass/CompassWidget.kt b/libnavui-androidauto/src/main/java/com/mapbox/androidauto/car/map/widgets/compass/CompassWidget.kt deleted file mode 100644 index 0cc7a0c45ee..00000000000 --- a/libnavui-androidauto/src/main/java/com/mapbox/androidauto/car/map/widgets/compass/CompassWidget.kt +++ /dev/null @@ -1,60 +0,0 @@ -package com.mapbox.androidauto.car.map.widgets.compass - -import android.content.Context -import android.graphics.BitmapFactory -import com.mapbox.androidauto.R -import com.mapbox.androidauto.car.map.widgets.ImageOverlayHost -import com.mapbox.androidauto.car.map.widgets.Margin -import com.mapbox.androidauto.car.map.widgets.WidgetPosition - -/** - * A widget to show the compass on the map. - */ -class CompassWidget( - /** - * Context of the app. - */ - context: Context, - /** - * The position of the widget. - */ - widgetPosition: WidgetPosition = WidgetPosition.TOP_RIGHT, - /** - * The left margin of the widget relative to the map. - */ - marginLeft: Float = 10f, - /** - * The top margin of the widget relative to the map. - */ - marginTop: Float = 130f, - /** - * The right margin of the widget relative to the map. - */ - marginRight: Float = 10f, - /** - * The bottom margin of the widget relative to the map. - */ - marginBottom: Float = 10f -) { - internal val host = ImageOverlayHost( - BitmapFactory.decodeResource( - context.resources, - R.drawable.mapbox_compass_icon - ), - widgetPosition, Margin(marginLeft, marginTop, marginRight, marginBottom), - ) - - /** - * Update the compass bearing. - */ - fun updateBearing(bearing: Float) { - host.rotate(-bearing) - } - - companion object { - /** - * The layer ID of the compass widget layer. - */ - const val COMPASS_WIDGET_LAYER_ID = "COMPASS_WIDGET_LAYER" - } -} diff --git a/libnavui-androidauto/src/main/java/com/mapbox/androidauto/car/map/widgets/logo/CarLogoSurfaceRenderer.kt b/libnavui-androidauto/src/main/java/com/mapbox/androidauto/car/map/widgets/logo/CarLogoSurfaceRenderer.kt index c06b7024e2f..1e13911295e 100644 --- a/libnavui-androidauto/src/main/java/com/mapbox/androidauto/car/map/widgets/logo/CarLogoSurfaceRenderer.kt +++ b/libnavui-androidauto/src/main/java/com/mapbox/androidauto/car/map/widgets/logo/CarLogoSurfaceRenderer.kt @@ -1,29 +1,33 @@ package com.mapbox.androidauto.car.map.widgets.logo -import com.mapbox.androidauto.car.internal.extensions.getStyle -import com.mapbox.androidauto.car.internal.extensions.getStyleAsync -import com.mapbox.maps.LayerPosition +import android.graphics.Rect +import com.mapbox.maps.EdgeInsets import com.mapbox.maps.MapboxExperimental import com.mapbox.maps.extension.androidauto.MapboxCarMapObserver import com.mapbox.maps.extension.androidauto.MapboxCarMapSurface +import com.mapbox.maps.extension.androidauto.widgets.LogoWidget +import com.mapbox.maps.renderer.widget.WidgetPosition @OptIn(MapboxExperimental::class) -class CarLogoSurfaceRenderer( - private val layerPosition: LayerPosition? = null -) : MapboxCarMapObserver { +class CarLogoSurfaceRenderer : MapboxCarMapObserver { + + private var logoWidget: LogoWidget? = null override fun onAttached(mapboxCarMapSurface: MapboxCarMapSurface) { - val logoWidget = LogoWidget(mapboxCarMapSurface.carContext) - mapboxCarMapSurface.getStyleAsync { - it.addPersistentStyleCustomLayer( - LogoWidget.LOGO_WIDGET_LAYER_ID, - logoWidget.host, - layerPosition - ) - } + val logoWidget = LogoWidget( + mapboxCarMapSurface.carContext, + WidgetPosition(WidgetPosition.Horizontal.RIGHT, WidgetPosition.Vertical.BOTTOM), + marginX = 26f, marginY = 10f, + ).also { logoWidget = it } + mapboxCarMapSurface.mapSurface.addWidget(logoWidget) } override fun onDetached(mapboxCarMapSurface: MapboxCarMapSurface) { - mapboxCarMapSurface.getStyle()?.removeStyleLayer(LogoWidget.LOGO_WIDGET_LAYER_ID) + logoWidget?.let { mapboxCarMapSurface.mapSurface.removeWidget(it) } + logoWidget = null + } + + override fun onVisibleAreaChanged(visibleArea: Rect, edgeInsets: EdgeInsets) { + logoWidget?.setTranslation(-edgeInsets.right.toFloat(), -edgeInsets.bottom.toFloat()) } } diff --git a/libnavui-androidauto/src/main/java/com/mapbox/androidauto/car/map/widgets/logo/LogoWidget.kt b/libnavui-androidauto/src/main/java/com/mapbox/androidauto/car/map/widgets/logo/LogoWidget.kt deleted file mode 100644 index 8226a2a8fce..00000000000 --- a/libnavui-androidauto/src/main/java/com/mapbox/androidauto/car/map/widgets/logo/LogoWidget.kt +++ /dev/null @@ -1,55 +0,0 @@ -package com.mapbox.androidauto.car.map.widgets.logo - -import android.content.Context -import android.graphics.BitmapFactory -import com.mapbox.androidauto.R -import com.mapbox.androidauto.car.map.widgets.ImageOverlayHost -import com.mapbox.androidauto.car.map.widgets.Margin -import com.mapbox.androidauto.car.map.widgets.WidgetPosition - -/** - * Logo widget displays the Mapbox logo on the map. - */ -class LogoWidget( - /** - * Context of the app. - */ - context: Context, - /** - * The position of the widget. - */ - widgetPosition: WidgetPosition = WidgetPosition.BOTTOM_RIGHT, - /** - * The left margin of the widget relative to the map. - */ - marginLeft: Float = 10f, - /** - * The top margin of the widget relative to the map. - */ - marginTop: Float = 10f, - /** - * The right margin of the widget relative to the map. - */ - marginRight: Float = 10f, - /** - * The bottom margin of the widget relative to the map. - */ - marginBottom: Float = 10f -) { - internal val host by lazy { - ImageOverlayHost( - BitmapFactory.decodeResource( - context.resources, - R.drawable.mapbox_logo_icon - ), - widgetPosition, Margin(marginLeft, marginTop, marginRight, marginBottom), - ) - } - - companion object { - /** - * The layer ID of the logo widget layer. - */ - const val LOGO_WIDGET_LAYER_ID = "LOGO_WIDGET_LAYER" - } -} diff --git a/libnavui-androidauto/src/main/java/com/mapbox/androidauto/car/navigation/speedlimit/CarSpeedLimitRenderer.kt b/libnavui-androidauto/src/main/java/com/mapbox/androidauto/car/navigation/speedlimit/CarSpeedLimitRenderer.kt index 65e748b61cb..ba14f91418b 100644 --- a/libnavui-androidauto/src/main/java/com/mapbox/androidauto/car/navigation/speedlimit/CarSpeedLimitRenderer.kt +++ b/libnavui-androidauto/src/main/java/com/mapbox/androidauto/car/navigation/speedlimit/CarSpeedLimitRenderer.kt @@ -3,8 +3,6 @@ package com.mapbox.androidauto.car.navigation.speedlimit import android.graphics.Rect import android.location.Location import com.mapbox.androidauto.car.MainCarContext -import com.mapbox.androidauto.car.internal.extensions.getStyle -import com.mapbox.androidauto.car.internal.extensions.getStyleAsync import com.mapbox.androidauto.logAndroidAuto import com.mapbox.maps.EdgeInsets import com.mapbox.maps.MapboxExperimental @@ -23,7 +21,7 @@ import kotlin.math.roundToInt class CarSpeedLimitRenderer( private val mainCarContext: MainCarContext, ) : MapboxCarMapObserver { - private val speedLimitWidget by lazy { SpeedLimitWidget() } + private var speedLimitWidget: SpeedLimitWidget? = null private val locationObserver = object : LocationObserver { override fun onNewLocationMatcherResult(locationMatcherResult: LocationMatcherResult) { @@ -45,10 +43,10 @@ class CarSpeedLimitRenderer( 5 * (speedLimitKmph / KILOMETERS_IN_MILE / 5).roundToInt() } val speed = speedKmph / KILOMETERS_IN_MILE - speedLimitWidget.update(speedLimit, speed.roundToInt()) + speedLimitWidget?.update(speedLimit, speed.roundToInt()) } UnitType.METRIC -> { - speedLimitWidget.update( + speedLimitWidget?.update( locationMatcherResult.speedLimit?.speedKmph, speedKmph.roundToInt() ) @@ -58,27 +56,19 @@ class CarSpeedLimitRenderer( override fun onAttached(mapboxCarMapSurface: MapboxCarMapSurface) { logAndroidAuto("CarSpeedLimitRenderer carMapSurface loaded") - mapboxCarMapSurface.getStyleAsync { - it.addPersistentStyleCustomLayer( - SpeedLimitWidget.SPEED_LIMIT_WIDGET_LAYER_ID, - speedLimitWidget.viewWidgetHost, - null - ) - } - speedLimitWidget.redraw() + val speedLimitWidget = SpeedLimitWidget().also { speedLimitWidget = it } + mapboxCarMapSurface.mapSurface.addWidget(speedLimitWidget) mainCarContext.mapboxNavigation.registerLocationObserver(locationObserver) } override fun onDetached(mapboxCarMapSurface: MapboxCarMapSurface) { logAndroidAuto("CarSpeedLimitRenderer carMapSurface detached") mainCarContext.mapboxNavigation.unregisterLocationObserver(locationObserver) - mapboxCarMapSurface.getStyle() - ?.removeStyleLayer(SpeedLimitWidget.SPEED_LIMIT_WIDGET_LAYER_ID) - speedLimitWidget.clear() + speedLimitWidget?.let { mapboxCarMapSurface.mapSurface.removeWidget(it) } } override fun onVisibleAreaChanged(visibleArea: Rect, edgeInsets: EdgeInsets) { - speedLimitWidget.setEdgeInsets(edgeInsets) + speedLimitWidget?.setTranslation(-edgeInsets.right.toFloat(), -edgeInsets.bottom.toFloat()) } private companion object { diff --git a/libnavui-androidauto/src/main/java/com/mapbox/androidauto/car/navigation/speedlimit/SpeedLimitWidget.kt b/libnavui-androidauto/src/main/java/com/mapbox/androidauto/car/navigation/speedlimit/SpeedLimitWidget.kt index 2b713aeb374..23827cb0acc 100644 --- a/libnavui-androidauto/src/main/java/com/mapbox/androidauto/car/navigation/speedlimit/SpeedLimitWidget.kt +++ b/libnavui-androidauto/src/main/java/com/mapbox/androidauto/car/navigation/speedlimit/SpeedLimitWidget.kt @@ -6,163 +6,45 @@ import android.graphics.Color import android.graphics.Paint import android.graphics.Rect import android.graphics.RectF -import com.mapbox.androidauto.car.map.widgets.ImageOverlayHost -import com.mapbox.androidauto.car.map.widgets.Margin -import com.mapbox.androidauto.car.map.widgets.WidgetPosition import com.mapbox.androidauto.logAndroidAuto -import com.mapbox.maps.EdgeInsets +import com.mapbox.maps.MapboxExperimental +import com.mapbox.maps.renderer.widget.BitmapWidget +import com.mapbox.maps.renderer.widget.WidgetPosition /** * Widget to display a speed limit sign on the map. */ +@OptIn(MapboxExperimental::class) class SpeedLimitWidget( /** * The position of the widget. */ - widgetPosition: WidgetPosition = WidgetPosition.BOTTOM_RIGHT, + position: WidgetPosition = WidgetPosition( + vertical = WidgetPosition.Vertical.BOTTOM, + horizontal = WidgetPosition.Horizontal.RIGHT, + ), /** - * The left margin of the widget relative to the map. + * The horizontal margin of the widget relative to the map. */ - private val marginLeft: Float = 10f, + marginX: Float = 26f, /** - * The top margin of the widget relative to the map. + * The vertical margin of the widget relative to the map. */ - private val marginTop: Float = 10f, - /** - * The right margin of the widget relative to the map. - */ - private val marginRight: Float = 10f, - /** - * The bottom margin of the widget relative to the map. - */ - private val marginBottom: Float = 50f, -) { + marginY: Float = 50f, +) : BitmapWidget(drawSpeedLimitSign(speedLimit = null, speed = 0), position, marginX, marginY) { + private var lastSpeedLimit: Int? = null private var lastSpeed = 0 - private val titlePaint = Paint().apply { - color = Color.BLACK - isAntiAlias = true - textSize = 12f - textAlign = Paint.Align.CENTER - } - private val speedLimitPaint = Paint().apply { - color = Color.BLACK - isAntiAlias = true - textSize = 27f - textAlign = Paint.Align.CENTER - } - private val speedPaint = Paint().apply { - color = Color.WHITE - isAntiAlias = true - textSize = 22.5f - textAlign = Paint.Align.CENTER - } - private val signPaint = Paint().apply { - color = Color.WHITE - isAntiAlias = true - } - private val borderPaint = Paint().apply { - color = Color.parseColor("#CDD0D0") - isAntiAlias = true - style = Paint.Style.STROKE - strokeWidth = STROKE - } - private val backgroundPaint = Paint().apply { - isAntiAlias = true - } - private val titleRect1 = Rect().apply { - titlePaint.getTextBounds(TITLE_1, 0, TITLE_1.length, this) - } - private val titleRect2 = Rect().apply { - titlePaint.getTextBounds(TITLE_2, 0, TITLE_2.length, this) - } - private val speedLimitRect = Rect() - private val speedRect = Rect() - private val signRect = RectF( - PADDING + STROKE / 2, - PADDING + STROKE / 2, - WIDTH - PADDING - STROKE / 2, - PADDING + STROKE * 1.5f + SIGN_HEIGHT, - ) - private val backgroundRect = RectF(0f, 0f, WIDTH.toFloat(), HEIGHT.toFloat()) - - internal val viewWidgetHost = ImageOverlayHost( - drawSpeedLimitSign(speedLimit = null, speed = 0), - widgetPosition, - Margin( - marginLeft, - marginTop, - marginRight, - marginBottom - ), - ) - fun update(speedLimit: Int?, speed: Int) { if (lastSpeedLimit == speedLimit && lastSpeed == speed) return lastSpeedLimit = speedLimit lastSpeed = speed - viewWidgetHost.updateBitmap(drawSpeedLimitSign(speedLimit, speed)) - } - - fun redraw() { - viewWidgetHost.updateBitmap(drawSpeedLimitSign(lastSpeedLimit, lastSpeed)) + updateBitmap(drawSpeedLimitSign(speedLimit, speed)) } - internal fun drawSpeedLimitSign(speedLimit: Int?, speed: Int): Bitmap { - logAndroidAuto("$TAG drawSpeedLimitSign: speedLimit = $speedLimit, speed = $speed") - - val speedLimitText = if (speedLimit == null) { - backgroundPaint.color = COLOR_NORMAL - SPEED_LIMIT_NO_DATA - } else { - backgroundPaint.color = if (speed > speedLimit) Color.RED else COLOR_NORMAL - speedLimit.toString() - } - speedLimitPaint.getTextBounds(speedLimitText, 0, speedLimitText.length, speedLimitRect) - val titlePadding = (SIGN_HEIGHT / 2f - titleRect1.height() - titleRect2.height()) / 3 - val speedText = speed.toString() - speedPaint.getTextBounds(speedText, 0, speedText.length, speedRect) - - val canvasBitmap = Bitmap.createBitmap(WIDTH, HEIGHT, Bitmap.Config.ARGB_8888) - val canvas = Canvas(canvasBitmap) - canvas.drawRoundRect(backgroundRect, RADIUS, RADIUS, backgroundPaint) - canvas.drawRoundRect(signRect, RADIUS - PADDING, RADIUS - PADDING, signPaint) - canvas.drawRoundRect(signRect, RADIUS - PADDING, RADIUS - PADDING, borderPaint) - - val titleY1 = PADDING + STROKE + titlePadding - canvas.drawText(TITLE_1, WIDTH / 2f, titleY1 - titleRect1.top, titlePaint) - - val titleY2 = titleY1 + titleRect1.height() + titlePadding - canvas.drawText(TITLE_2, WIDTH / 2f, titleY2 - titleRect2.top, titlePaint) - - val speedLimitY = PADDING + STROKE + SIGN_HEIGHT * 0.75f - speedLimitRect.centerY() - canvas.drawText(speedLimitText, WIDTH / 2f, speedLimitY, speedLimitPaint) - - val speedY = (PADDING + SIGN_HEIGHT + HEIGHT) / 2 + STROKE - speedRect.centerY() - canvas.drawText(speedText, WIDTH / 2f, speedY, speedPaint) - - return canvasBitmap - } - - /** - * Resetting this will force a new call to viewWidgetHost.updateBitmap - */ - fun clear() { - lastSpeedLimit = null - lastSpeed = 0 - } - - fun setEdgeInsets(edgeInsets: EdgeInsets) { - val marginLeft = marginLeft + edgeInsets.left.toFloat() - val marginTop = marginTop + edgeInsets.top.toFloat() - val marginRight = marginRight + edgeInsets.right.toFloat() - val marginBottom = marginBottom + edgeInsets.bottom.toFloat() - viewWidgetHost.updateMargins(Margin(marginLeft, marginTop, marginRight, marginBottom)) - } - - companion object { + internal companion object { private const val TAG = "SpeedLimitWidget" private const val WIDTH = 55 private const val HEIGHT = 98 @@ -174,6 +56,88 @@ class SpeedLimitWidget( private const val TITLE_2 = "LIMIT" private const val SPEED_LIMIT_NO_DATA = "--" private val COLOR_NORMAL = Color.parseColor("#4F4F4F") - const val SPEED_LIMIT_WIDGET_LAYER_ID = "SPEED_LIMIT_WIDGET_LAYER_ID" + + private val titlePaint = Paint().apply { + color = Color.BLACK + isAntiAlias = true + textSize = 12f + textAlign = Paint.Align.CENTER + } + private val speedLimitPaint = Paint().apply { + color = Color.BLACK + isAntiAlias = true + textSize = 27f + textAlign = Paint.Align.CENTER + } + private val speedPaint = Paint().apply { + color = Color.WHITE + isAntiAlias = true + textSize = 22.5f + textAlign = Paint.Align.CENTER + } + private val signPaint = Paint().apply { + color = Color.WHITE + isAntiAlias = true + } + private val borderPaint = Paint().apply { + color = Color.parseColor("#CDD0D0") + isAntiAlias = true + style = Paint.Style.STROKE + strokeWidth = STROKE + } + private val backgroundPaint = Paint().apply { + isAntiAlias = true + } + private val titleRect1 = Rect().apply { + titlePaint.getTextBounds(TITLE_1, 0, TITLE_1.length, this) + } + private val titleRect2 = Rect().apply { + titlePaint.getTextBounds(TITLE_2, 0, TITLE_2.length, this) + } + private val speedLimitRect = Rect() + private val speedRect = Rect() + private val signRect = RectF( + PADDING + STROKE / 2, + PADDING + STROKE / 2, + WIDTH - PADDING - STROKE / 2, + PADDING + STROKE * 1.5f + SIGN_HEIGHT, + ) + private val backgroundRect = RectF(0f, 0f, WIDTH.toFloat(), HEIGHT.toFloat()) + + internal fun drawSpeedLimitSign(speedLimit: Int?, speed: Int): Bitmap { + logAndroidAuto("$TAG drawSpeedLimitSign: speedLimit = $speedLimit, speed = $speed") + + val speedLimitText = if (speedLimit == null) { + backgroundPaint.color = COLOR_NORMAL + SPEED_LIMIT_NO_DATA + } else { + backgroundPaint.color = if (speed > speedLimit) Color.RED else COLOR_NORMAL + speedLimit.toString() + } + speedLimitPaint.getTextBounds(speedLimitText, 0, speedLimitText.length, speedLimitRect) + val titlePadding = (SIGN_HEIGHT / 2f - titleRect1.height() - titleRect2.height()) / 3 + val speedText = speed.toString() + speedPaint.getTextBounds(speedText, 0, speedText.length, speedRect) + + val canvasBitmap = Bitmap.createBitmap(WIDTH, HEIGHT, Bitmap.Config.ARGB_8888) + val canvas = Canvas(canvasBitmap) + canvas.drawRoundRect(backgroundRect, RADIUS, RADIUS, backgroundPaint) + canvas.drawRoundRect(signRect, RADIUS - PADDING, RADIUS - PADDING, signPaint) + canvas.drawRoundRect(signRect, RADIUS - PADDING, RADIUS - PADDING, borderPaint) + + val titleY1 = PADDING + STROKE + titlePadding + canvas.drawText(TITLE_1, WIDTH / 2f, titleY1 - titleRect1.top, titlePaint) + + val titleY2 = titleY1 + titleRect1.height() + titlePadding + canvas.drawText(TITLE_2, WIDTH / 2f, titleY2 - titleRect2.top, titlePaint) + + val speedLimitY = PADDING + STROKE + SIGN_HEIGHT * 0.75f - speedLimitRect.centerY() + canvas.drawText(speedLimitText, WIDTH / 2f, speedLimitY, speedLimitPaint) + + val speedY = (PADDING + SIGN_HEIGHT + HEIGHT) / 2 + STROKE - speedRect.centerY() + canvas.drawText(speedText, WIDTH / 2f, speedY, speedPaint) + + return canvasBitmap + } } }