Simple-Launcher/app/src/main/kotlin/com/simplemobiletools/launcher/views/MyAppWidgetResizeFrame.kt

361 lines
16 KiB
Kotlin

package com.simplemobiletools.launcher.views
import android.annotation.SuppressLint
import android.appwidget.AppWidgetManager
import android.content.Context
import android.graphics.*
import android.graphics.drawable.ColorDrawable
import android.util.AttributeSet
import android.view.MotionEvent
import android.widget.RelativeLayout
import com.simplemobiletools.launcher.R
import com.simplemobiletools.launcher.extensions.config
import com.simplemobiletools.launcher.extensions.getCellCount
import com.simplemobiletools.launcher.helpers.MAX_CLICK_DURATION
import com.simplemobiletools.launcher.models.HomeScreenGridItem
import com.simplemobiletools.commons.R as CommonsR
@SuppressLint("ViewConstructor")
class MyAppWidgetResizeFrame(context: Context, attrs: AttributeSet, defStyle: Int) : RelativeLayout(context, attrs, defStyle) {
constructor(context: Context, attrs: AttributeSet) : this(context, attrs, 0)
private var resizeWidgetLinePaint: Paint
private var resizeWidgetLineDotPaint: Paint
private var actionDownCoords = PointF()
private var actionDownMS = 0L
private var frameRect = Rect(0, 0, 0, 0) // coords in pixels
private var cellsRect = Rect(0, 0, 0, 0) // cell IDs like 0, 1, 2..
private var cellWidth = 0
private var cellHeight = 0
private var minResizeWidthCells = 1
private var minResizeHeightCells = 1
private val occupiedCells = ArrayList<Pair<Int, Int>>()
private var resizedItem: HomeScreenGridItem? = null
private var sideMargins = Rect()
private val lineDotRadius = context.resources.getDimension(R.dimen.resize_frame_dot_radius)
private val MAX_TOUCH_LINE_DISTANCE = lineDotRadius * 5 // how close we have to be to the widgets side to drag it
var onClickListener: (() -> Unit)? = null
var onResizeListener: ((cellsRect: Rect) -> Unit)? = null
private val DRAGGING_NONE = 0
private val DRAGGING_LEFT = 1
private val DRAGGING_TOP = 2
private val DRAGGING_RIGHT = 3
private val DRAGGING_BOTTOM = 4
private var dragDirection = DRAGGING_NONE
init {
background = ColorDrawable(Color.TRANSPARENT)
resizeWidgetLinePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = Color.WHITE
strokeWidth = context.resources.getDimension(CommonsR.dimen.tiny_margin)
style = Paint.Style.STROKE
}
resizeWidgetLineDotPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = Color.WHITE
}
}
fun updateFrameCoords(
coords: Rect,
cellWidth: Int,
cellHeight: Int,
sideMargins: Rect,
gridItem: HomeScreenGridItem,
allGridItems: ArrayList<HomeScreenGridItem>
) {
frameRect = coords
cellsRect = Rect(gridItem.left, gridItem.top, gridItem.right, gridItem.bottom)
this.cellWidth = cellWidth
this.cellHeight = cellHeight
this.sideMargins = sideMargins
this.resizedItem = gridItem
val providerInfo = gridItem.providerInfo ?: AppWidgetManager.getInstance(context)!!.installedProviders.firstOrNull {
it.provider.className == gridItem.className
} ?: return
minResizeWidthCells = Math.min(context.config.homeColumnCount, context.getCellCount(providerInfo.minResizeWidth))
minResizeHeightCells = Math.min(context.config.homeRowCount, context.getCellCount(providerInfo.minResizeHeight))
redrawFrame()
occupiedCells.clear()
allGridItems.forEach { item ->
for (xCell in item.left..item.right) {
for (yCell in item.top..item.bottom) {
occupiedCells.add(Pair(xCell, yCell))
}
}
}
}
private fun redrawFrame() {
layoutParams.width = frameRect.right - frameRect.left
layoutParams.height = frameRect.bottom - frameRect.top
x = frameRect.left.toFloat()
y = frameRect.top.toFloat()
requestLayout()
}
private fun cellChanged() {
onResizeListener?.invoke(cellsRect)
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
if (event == null) {
return false
}
when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> {
actionDownCoords.x = event.rawX
actionDownCoords.y = event.rawY
actionDownMS = System.currentTimeMillis()
dragDirection = DRAGGING_NONE
if (event.x < MAX_TOUCH_LINE_DISTANCE) {
dragDirection = DRAGGING_LEFT
} else if (event.y < MAX_TOUCH_LINE_DISTANCE) {
dragDirection = DRAGGING_TOP
} else if (event.x > width - MAX_TOUCH_LINE_DISTANCE) {
dragDirection = DRAGGING_RIGHT
} else if (event.y > height - MAX_TOUCH_LINE_DISTANCE) {
dragDirection = DRAGGING_BOTTOM
}
}
MotionEvent.ACTION_MOVE -> {
val minWidth = minResizeWidthCells * cellWidth
val minHeight = minResizeHeightCells * cellHeight
when (dragDirection) {
DRAGGING_LEFT -> {
val newWidth = frameRect.right - event.rawX.toInt()
val wantedLeft = if (newWidth >= minWidth) {
event.rawX.toInt()
} else {
frameRect.right - minWidth
}
val wantedLeftCellX = roundToClosestMultiplyOfNumber(wantedLeft - sideMargins.left, cellWidth) / cellWidth
var areAllCellsFree = true
for (xCell in wantedLeftCellX..cellsRect.right) {
for (yCell in cellsRect.top..cellsRect.bottom) {
if (occupiedCells.contains(Pair(xCell, yCell))) {
areAllCellsFree = false
break
}
}
}
if (areAllCellsFree && cellsRect.left != wantedLeftCellX) {
cellsRect.left = wantedLeftCellX
cellChanged()
}
frameRect.left = wantedLeft
}
DRAGGING_TOP -> {
val newHeight = frameRect.bottom - event.rawY.toInt()
val wantedTop = if (newHeight >= minHeight) {
event.rawY.toInt()
} else {
frameRect.bottom - minHeight
}
val wantedTopCellY = roundToClosestMultiplyOfNumber(wantedTop - sideMargins.top, cellHeight) / cellHeight
var areAllCellsFree = true
for (xCell in cellsRect.left..cellsRect.right) {
for (yCell in wantedTopCellY..cellsRect.bottom) {
if (occupiedCells.contains(Pair(xCell, yCell))) {
areAllCellsFree = false
break
}
}
}
if (areAllCellsFree && cellsRect.top != wantedTopCellY) {
cellsRect.top = wantedTopCellY
cellChanged()
}
frameRect.top = wantedTop
}
DRAGGING_RIGHT -> {
val newWidth = event.rawX.toInt() - frameRect.left
val wantedRight = if (newWidth >= minWidth) {
event.rawX.toInt()
} else {
frameRect.left + minWidth
}
val wantedRightCellX = roundToClosestMultiplyOfNumber(wantedRight - sideMargins.left, cellWidth) / cellWidth - 1
var areAllCellsFree = true
for (xCell in cellsRect.left..wantedRightCellX) {
for (yCell in cellsRect.top..cellsRect.bottom) {
if (occupiedCells.contains(Pair(xCell, yCell))) {
areAllCellsFree = false
break
}
}
}
if (areAllCellsFree && cellsRect.right != wantedRightCellX) {
cellsRect.right = wantedRightCellX
cellChanged()
}
frameRect.right = wantedRight
}
DRAGGING_BOTTOM -> {
val newHeight = event.rawY.toInt() - frameRect.top
val wantedBottom = if (newHeight >= minHeight) {
event.rawY.toInt()
} else {
frameRect.top + minHeight
}
val wantedBottomCellY = roundToClosestMultiplyOfNumber(wantedBottom - sideMargins.top, cellHeight) / cellHeight - 1
var areAllCellsFree = true
for (xCell in cellsRect.left..cellsRect.right) {
for (yCell in cellsRect.top..wantedBottomCellY) {
if (occupiedCells.contains(Pair(xCell, yCell))) {
areAllCellsFree = false
break
}
}
}
if (wantedBottomCellY == context.config.homeRowCount - 1) {
areAllCellsFree = false
}
if (areAllCellsFree && cellsRect.bottom != wantedBottomCellY) {
cellsRect.bottom = wantedBottomCellY
cellChanged()
}
frameRect.bottom = wantedBottom
}
}
if (dragDirection != DRAGGING_NONE) {
redrawFrame()
}
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
if (dragDirection == DRAGGING_NONE) {
onClickListener?.invoke()
} else if (System.currentTimeMillis() - actionDownMS < MAX_CLICK_DURATION) {
onClickListener?.invoke()
dragDirection = DRAGGING_NONE
} else {
when (dragDirection) {
DRAGGING_LEFT -> {
val wantedLeft = roundToClosestMultiplyOfNumber(frameRect.left - sideMargins.left, cellWidth)
val wantedLeftCellX = wantedLeft / cellWidth
var areAllCellsFree = true
for (xCell in wantedLeftCellX..cellsRect.right) {
for (yCell in cellsRect.top..cellsRect.bottom) {
if (occupiedCells.contains(Pair(xCell, yCell))) {
areAllCellsFree = false
break
}
}
}
if (areAllCellsFree) {
frameRect.left = wantedLeft + sideMargins.left
cellsRect.left = wantedLeftCellX
} else {
frameRect.left = cellsRect.left * cellWidth + sideMargins.left
}
}
DRAGGING_TOP -> {
val wantedTop = roundToClosestMultiplyOfNumber(frameRect.top - sideMargins.top, cellHeight)
val wantedTopCellY = wantedTop / cellHeight
var areAllCellsFree = true
for (xCell in cellsRect.left..cellsRect.right) {
for (yCell in wantedTopCellY..cellsRect.bottom) {
if (occupiedCells.contains(Pair(xCell, yCell))) {
areAllCellsFree = false
break
}
}
}
if (areAllCellsFree) {
frameRect.top = wantedTop + sideMargins.top
cellsRect.top = wantedTopCellY
} else {
frameRect.top = cellsRect.top * cellHeight + sideMargins.top
}
}
DRAGGING_RIGHT -> {
val wantedRight = roundToClosestMultiplyOfNumber(frameRect.right - sideMargins.left, cellWidth)
val wantedRightCellX = wantedRight / cellWidth - 1
var areAllCellsFree = true
for (xCell in cellsRect.left..wantedRightCellX + 1) {
for (yCell in cellsRect.top..cellsRect.bottom) {
if (occupiedCells.contains(Pair(xCell, yCell))) {
areAllCellsFree = false
break
}
}
}
if (areAllCellsFree) {
frameRect.right = wantedRight + sideMargins.left
cellsRect.right = wantedRightCellX
} else {
frameRect.right = (cellsRect.right + 1) * cellWidth + sideMargins.left
}
}
DRAGGING_BOTTOM -> {
val wantedBottom = roundToClosestMultiplyOfNumber(frameRect.bottom - sideMargins.top, cellHeight)
val wantedBottomCellY = wantedBottom / cellHeight - 1
var areAllCellsFree = true
for (xCell in cellsRect.left..cellsRect.right) {
for (yCell in cellsRect.top..wantedBottomCellY + 1) {
if (occupiedCells.contains(Pair(xCell, yCell))) {
areAllCellsFree = false
break
}
}
}
if (wantedBottomCellY == context.config.homeRowCount - 1) {
areAllCellsFree = false
}
if (areAllCellsFree) {
frameRect.bottom = wantedBottom + sideMargins.top
cellsRect.bottom = wantedBottomCellY
} else {
frameRect.bottom = (cellsRect.bottom + 1) * cellHeight + sideMargins.top
}
}
}
redrawFrame()
}
}
}
return true
}
private fun roundToClosestMultiplyOfNumber(value: Int, number: Int): Int {
return number * (Math.round(Math.abs(value / number.toDouble()))).toInt()
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
if (x != 0f || y != 0f) {
canvas.drawRect(lineDotRadius, lineDotRadius, width.toFloat() - lineDotRadius, height.toFloat() - lineDotRadius, resizeWidgetLinePaint)
canvas.drawCircle(lineDotRadius, height / 2f, lineDotRadius, resizeWidgetLineDotPaint)
canvas.drawCircle(width / 2f, lineDotRadius, lineDotRadius, resizeWidgetLineDotPaint)
canvas.drawCircle(width - lineDotRadius, height / 2f, lineDotRadius, resizeWidgetLineDotPaint)
canvas.drawCircle(width / 2f, height - lineDotRadius, lineDotRadius, resizeWidgetLineDotPaint)
}
}
}