Files
PinPoint/app/src/main/java/com/example/jnicpp/bowling/FeedbackUI.kt
T
qychan de45eab0f5 feedbackui
can show in recording
2026-09-06 22:31:36 +08:00

140 lines
4.6 KiB
Kotlin

package com.example.jnicpp.bowling
import android.text.Layout
import android.text.TextPaint
import android.text.StaticLayout
import android.graphics.Canvas
import android.graphics.RectF
import android.graphics.Color
import android.graphics.drawable.GradientDrawable
import android.content.Context
import android.graphics.Paint
import android.view.View
import android.widget.TextView
import com.example.jnicpp.R
import android.graphics.BlurMaskFilter
import android.graphics.Matrix
import kotlin.math.min
import androidx.constraintlayout.widget.ConstraintLayout
class FeedbackUI(private val context: Context, private val rootView: View) {
private var landmarks: Map<Int, SmoothedLandmark>? = null
private var CIRCLE_RADIUS = 128f
private val uiTextSize = 32f
private val uiStrokeWidth = 12f
private val bannerPaddingY = 24
private val bannerPaddingX = 24
private val camScale = 0.5f
private var liveUiWidth: Int = 0
private var liveUiHeight: Int = 0
private val glowPaint = Paint().apply {
color = Color.RED
style = Paint.Style.STROKE
isAntiAlias = true
strokeWidth = uiStrokeWidth // thicker stroke so glow is visible
maskFilter = BlurMaskFilter(25f, BlurMaskFilter.Blur.OUTER)
}
private val circlePaint = Paint().apply {
color = Color.WHITE
style = Paint.Style.STROKE
isAntiAlias = true
strokeWidth = uiStrokeWidth
}
private val textPaint = TextPaint().apply {
color = Color.WHITE
textSize = uiTextSize
isAntiAlias = true
}
private val bgPaint = Paint().apply {
color = Color.argb(64, 255, 255, 255)
isAntiAlias = true
}
init {
rootView.viewTreeObserver.addOnGlobalLayoutListener {
setLiveUiSize(rootView)
}
}
fun showBanner(canvas: Canvas, message: String, forRecord: Boolean = false) {
val scale = if (forRecord) computeCamScale(canvas) else 1f
val paddingX = bannerPaddingX * scale
val paddingY = bannerPaddingY * scale
val maxWidth = (canvas.width * 0.8f).toInt()
// Build StaticLayout for multi-line text
textPaint.textSize = uiTextSize * scale
val staticLayout = StaticLayout.Builder
.obtain(message, 0, message.length, textPaint, maxWidth)
.setAlignment(Layout.Alignment.ALIGN_CENTER) // center text horizontally
.setLineSpacing(0f, 1f)
.setIncludePad(false)
.build()
// Use StaticLayout dimensions
val textWidth = staticLayout.width.toFloat()
val textHeight = staticLayout.height.toFloat()
val bannerWidth = textWidth + paddingX * 2
val bannerHeight = textHeight + paddingY * 2
// Center horizontally
val left = (canvas.width - bannerWidth) / 2f
val top = 172f * scale
val right = left + bannerWidth
val bottom = top + bannerHeight
// Draw background
val rect = RectF(left, top, right, bottom)
// Scale corner radius too
val cornerRadius = 24f * scale
canvas.drawRoundRect(rect, cornerRadius, cornerRadius, bgPaint)
// Draw text layout inside background
canvas.save()
canvas.translate(left + paddingX, top + paddingY)
staticLayout.draw(canvas)
canvas.restore()
}
// --- Shape overlay (circle) ---
fun drawCircles(canvas: Canvas, landmarks: Map<Int, SmoothedLandmark>, transform: Matrix, forRecord: Boolean = false) {
for (landmark in landmarks.values) {
val point = floatArrayOf(landmark.x, landmark.y)
transform.mapPoints(point)
drawCircle(canvas, point[0], point[1], 0f, forRecord)
}
}
fun drawCircle(canvas: Canvas, x: Float, y: Float, radius: Float = 0f, forRecord: Boolean = false) {
val scale = if (forRecord) camScale else 1f
var rad = (if (radius == 0f) CIRCLE_RADIUS else radius) * scale
circlePaint.strokeWidth = uiStrokeWidth * scale
glowPaint.strokeWidth = uiStrokeWidth * scale
canvas.drawCircle(x, y, rad, circlePaint)
canvas.drawCircle(x, y, rad, glowPaint)
}
// Store live UI size once
fun setLiveUiSize(rootView: View) {
liveUiWidth = rootView.width
liveUiHeight = rootView.height
}
// Compute scale when drawing to recording canvas
private fun computeCamScale(recordCanvas: Canvas): Float {
if (liveUiWidth == 0 || liveUiHeight == 0) return 1f
val scaleX = recordCanvas.width.toFloat() / liveUiWidth.toFloat()
val scaleY = recordCanvas.height.toFloat() / liveUiHeight.toFloat()
return min(scaleX, scaleY)
}
}