Skip to content

Commit

Permalink
feat: reordering working
Browse files Browse the repository at this point in the history
  • Loading branch information
agronick committed Dec 15, 2023
2 parents bb6c60e + c85beff commit bef456e
Show file tree
Hide file tree
Showing 21 changed files with 729 additions and 212 deletions.
2 changes: 1 addition & 1 deletion .idea/compiler.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .idea/gradle.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions .idea/kotlinc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 0 additions & 12 deletions .idea/runConfigurations.xml

This file was deleted.

10 changes: 5 additions & 5 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'

android {
compileSdkVersion 34
buildToolsVersion "30.0.3"
compileSdk 34
namespace = "com.agronick.launcher"

defaultConfig {
Expand All @@ -23,12 +22,12 @@ android {
}

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_19
targetCompatibility JavaVersion.VERSION_19
}

kotlinOptions{
jvmTarget = JavaVersion.VERSION_1_8
jvmTarget = JavaVersion.VERSION_19
}
}

Expand All @@ -42,6 +41,7 @@ dependencies {
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.recyclerview:recyclerview:1.3.2'
implementation 'com.jakewharton.timber:timber:5.0.1'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'

implementation 'androidx.wear:wear:1.3.0'
compileOnly 'com.google.android.wearable:wearable:2.9.0'
Expand Down
115 changes: 65 additions & 50 deletions app/src/main/java/com/agronick/launcher/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,95 +2,110 @@ package com.agronick.launcher

import android.graphics.Canvas
import android.graphics.Rect
import androidx.core.graphics.drawable.toBitmap
import util.geometry.Circle
import util.geometry.CircleCircleIntersection
import util.geometry.Vector2


class App(val pkgInfo: PInfo, var size: Int) {
var left = 0.0f
var top = 0.0f
var bitmap = pkgInfo.icon.toBitmap(size * 2, size * 2)
var hidden = false
public var assignedPos: Pair<Int, Int>? = null
private var lastCircle: Circle? = null

fun asOpenAnimator(endSize: Int): App {
fun copy(): App {
val other = App(pkgInfo, size)
other.left = left
other.top = top
other.bitmap = pkgInfo.icon.toBitmap(endSize * 2, endSize * 2)
return other
}

fun drawNormal(canvas: Canvas) {
if (lastCircle != null) {
val radius = lastCircle!!.r
draw(canvas, radius, left, top)
if (!hidden && lastCircle != null) {
val radius = lastCircle!!.r.coerceAtLeast(10f)
draw(canvas, radius, lastCircle!!.c.x, lastCircle!!.c.y)
}
}

fun prepare(faceCircle: Circle) {
lastCircle = getCircle(faceCircle) ?: return
fun prepare(faceCircle: Circle, checkCollisions: Boolean = true) {
lastCircle = getCircle(faceCircle, checkCollisions)
}

fun draw(canvas: Canvas, radius: Float, x: Float, y: Float) {
canvas.drawBitmap(
bitmap,
null,
Rect(
(x - radius).toInt(),
(y - radius).toInt(),
(x + radius).toInt(),
(y + radius).toInt()
),
null
private fun draw(canvas: Canvas, radius: Float, x: Float, y: Float) {
if (pkgInfo.icon == null) return
pkgInfo.icon!!.bounds = Rect(
(x - radius).toInt(),
(y - radius).toInt(),
(x + radius).toInt(),
(y + radius).toInt()
)
pkgInfo.icon!!.draw(canvas)
}

fun getCircle(faceCircle: Circle): Circle? {
private fun getCircle(faceCircle: Circle, checkCollisions: Boolean): Circle? {
val startSize = size
val appCircle = Circle(
Vector2(
left,
top
), startSize.toFloat()
)
if (!checkCollisions) {
return appCircle
}
val intersects =
CircleCircleIntersection(faceCircle, appCircle)
if (intersects.type == CircleCircleIntersection.Type.SEPARATE) {
return null
}
if (!intersects.type.isContained) {
if (intersects.type.intersectionPointCount == 0) return null
for (i in startSize.toLong() downTo 1) {
val testCircle = Circle(
Vector2(
left,
top
), i.toFloat()
)
val intersector = CircleCircleIntersection(
faceCircle,
testCircle
val arcMidpoint = getArcMidpoint(intersects) ?: return null
val opposite = getPointClosestToCenter(faceCircle, appCircle)
return Circle(
arcMidpoint.midpoint(opposite),
arcMidpoint.distance(opposite) * 0.5f
)
}
return appCircle
}

private fun getArcMidpoint(intersection: CircleCircleIntersection): Vector2? {
return when (intersection.type.intersectionPointCount) {
1 -> intersection.intersectionPoint1
2 -> {
val AB = intersection.intersectionPoint2.sub(intersection.intersectionPoint1)
val lAB = kotlin.math.sqrt(AB.x * AB.x + AB.y * AB.y)
val uAB = Vector2(AB.x / lAB, AB.y / lAB)
val mAB = intersection.intersectionPoint1.add(intersection.intersectionPoint2).div(
2f
)
if (intersector.type.intersectionPointCount == 0) {
return testCircle
}
val R = intersection.c1.r
val F = R - kotlin.math.sqrt(R * R - lAB * lAB * 0.25f)
Vector2(mAB.x - uAB.y * F, mAB.y + uAB.x * F)
}
return null
else -> null
}
return appCircle
}

fun intersects(x: Float, y: Float): Boolean {
if (null == lastCircle) return false
val circle = getCircle(lastCircle!!)
val point = Circle(
Vector2(
x,
y
), 3.0f
)
return CircleCircleIntersection(
circle,
point
).type == CircleCircleIntersection.Type.ECCENTRIC_CONTAINED
private fun getPointClosestToCenter(faceCircle: Circle, appCircle: Circle): Vector2 {
val vX = faceCircle.c.x - appCircle.c.x
val vY = faceCircle.c.y - appCircle.c.y
val magV = kotlin.math.sqrt((vX * vX + vY * vY).toDouble()).toFloat()
val aX = appCircle.c.x + vX / magV * appCircle.r
val aY = appCircle.c.y + vY / magV * appCircle.r
return Vector2(aX, aY)
}

fun intersects(point: Vector2): Boolean {
val circle = lastCircle
if (circle != null) {
return CircleCircleIntersection(
circle,
Circle(point, 3.0f)
).type == CircleCircleIntersection.Type.ECCENTRIC_CONTAINED
}
return false
}

}
108 changes: 108 additions & 0 deletions app/src/main/java/com/agronick/launcher/AppListProvider.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package com.agronick.launcher

import android.content.Context
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.json.JSONObject
import timber.log.Timber
import java.io.File
import java.io.FileReader
import java.io.FileWriter


class AppListProvider(val appList: List<PInfo>, context: Context) {

val positions = HashMap<Int, HashMap<Int, PInfo?>>().withDefault { HashMap() }
val filePath = "${context.dataDir}/appPositions.json"
val mapping = appList.map {
it.asKey() to it
}.toMap().toMutableMap()
val unregisted = mapping.keys.toMutableList()
val totalItems = appList.size

fun load() {
positions.clear()
if (!File(filePath).exists()) {
Timber.i("No file found. Will create first file shortly.")
return
}
val file = FileReader(filePath)
file.readText().let {
val json = JSONObject(it)
json.keys().forEach { rowKey ->
val rowInt = rowKey.toInt()
val row = json.getJSONObject(rowKey)
val rowFinal = positions.getOrPut(
rowInt,
) {
HashMap()
}
row.keys().forEach { colId ->
val col = row.getString(colId)
unregisted.remove(col)
rowFinal[colId.toInt()] = mapping.getOrDefault(col, PInfo.getBlank())
}
}
}
}

fun getPkgIterator(): PkgIterator {
return PkgIterator(mapping, unregisted, positions)
}

class PkgIterator(
val mapping: MutableMap<String, PInfo>,
val unregistered: MutableList<String>,
val positions: MutableMap<Int, HashMap<Int, PInfo?>>,
) {
val touchedItems = mapping.keys.toMutableSet()

fun get(x: Int, y: Int): PInfo? {
val found = positions.getOrPut(x) {
HashMap()
}.getOrPut(y) {
unregistered.removeLastOrNull()?.let {
mapping[it]
}
}
Timber.d("Requesting app for $x $y got ${found?.appname}")
return found?.also {
touchedItems.remove(it.asKey())
}
}

fun hasMore(): Boolean {
return touchedItems.isNotEmpty()
}
}

fun save() {
GlobalScope.launch(Dispatchers.IO) {
val jsonObject = JSONObject()
positions.forEach {
jsonObject.put(it.key.toString(), JSONObject().apply {
it.value.forEach {
put(it.key.toString(), it.value?.asKey())
}
})
}
val file = FileWriter(filePath)
file.write(jsonObject.toString())
file.flush()
file.close()
}
}

fun swap(app1: App, app2: App) {
(app1.assignedPos to app2.assignedPos).apply {
app2.assignedPos = first
app1.assignedPos = second
}
arrayOf(app1, app2).forEach {
val toPlace = if (it.pkgInfo.isBlank()) null else it.pkgInfo
positions[it.assignedPos!!.first]!![it.assignedPos!!.second] = toPlace
}
}
}

Loading

0 comments on commit bef456e

Please sign in to comment.