Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for saving and loading mesh gradient configuration #4

Merged
merged 30 commits into from
Mar 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
1a030d7
Use correct handler for Enter on TextFields
c5inco Mar 13, 2025
6bd07bc
First attempt at a database
c5inco Mar 14, 2025
55e9628
Refactor: Move AppState and AppDatabase to data package
c5inco Mar 14, 2025
efc714f
Try Room instead
c5inco Mar 14, 2025
0f06e0b
Refactor: Improve color management and database location
c5inco Mar 15, 2025
ddc0627
Refactor: Display saved colors in SidePanel
c5inco Mar 15, 2025
08b8d80
Add inlineAdd icons
c5inco Mar 15, 2025
59ec9a7
Refactor: Introduce preset colors and custom colors
c5inco Mar 15, 2025
5998b7e
Refactor: Use saved colors in color dropdown
c5inco Mar 15, 2025
ca1f3bc
Refactor: Introduce AppConfiguration
c5inco Mar 15, 2025
07b7185
Introduce MeshPoint and related database operations
c5inco Mar 15, 2025
5006d14
Add unique ID to `SavedColor` and support finding color by ID
c5inco Mar 15, 2025
067c6ec
Refactor: Improve mesh point management and UI
c5inco Mar 15, 2025
2541fec
Refactor: Use AppConfiguration state for mesh points and canvas color
c5inco Mar 15, 2025
9bede7f
Manage removing colors better with AppConfiguration
c5inco Mar 15, 2025
6a76099
Refactor code export
c5inco Mar 15, 2025
af8df98
Fix toggling constraining edge points
c5inco Mar 15, 2025
341231c
Fix distributing mesh points evenly
c5inco Mar 15, 2025
cfc75a7
Fix updating rows and cols
c5inco Mar 15, 2025
13ef28c
Fix blur level
c5inco Mar 15, 2025
f85a493
Refactor notification system and image export
c5inco Mar 15, 2025
83f3189
Refactor: Move DimensionMode to AppConfiguration
c5inco Mar 15, 2025
6288176
Refactor: Move color management to AppConfiguration
c5inco Mar 15, 2025
2090d82
Fix canvas height and width setting
c5inco Mar 16, 2025
00f4b2b
Remove AppState class, responsibilities now being managed by AppConfi…
c5inco Mar 16, 2025
604d666
Use room query directly
c5inco Mar 16, 2025
dff1b72
Feat: Save and load mesh points
c5inco Mar 16, 2025
d386756
Feat: Add mesh state management
c5inco Mar 17, 2025
913264e
Fix saving state on shutdown
c5inco Mar 17, 2025
33be014
Add interface for DataRepository
c5inco Mar 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ plugins {
alias(libs.plugins.composeMultiplatform) apply false
alias(libs.plugins.composeCompiler) apply false
alias(libs.plugins.kotlinMultiplatform) apply false
alias(libs.plugins.kotlinSerialization) apply false
}
13 changes: 13 additions & 0 deletions composeApp/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.composeMultiplatform)
alias(libs.plugins.composeCompiler)
alias(libs.plugins.kotlinSerialization)
alias(libs.plugins.ksp)
alias(libs.plugins.room)
}

repositories {
Expand Down Expand Up @@ -38,6 +41,9 @@ kotlin {
implementation(compose.components.uiToolingPreview)
implementation(libs.androidx.lifecycle.viewmodel)
implementation(libs.androidx.lifecycle.runtime.compose)
implementation(libs.kotlinx.serialization.json)
implementation(libs.room.runtime)
implementation(libs.sqlite.bundled)
}
desktopMain.dependencies {
// See https://github.com/JetBrains/Jewel/releases for the release notes
Expand All @@ -52,6 +58,13 @@ kotlin {
}
}

dependencies {
add("kspDesktop", libs.room.compiler)
}

room {
schemaDirectory("$projectDir/schemas")
}

compose.desktop {
application {
Expand Down
111 changes: 111 additions & 0 deletions composeApp/schemas/data.AppRoomDatabase/1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
{
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "8ff8768a392e73b92cf53b11d5f8362d",
"entities": [
{
"tableName": "SavedColor",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `red` INTEGER NOT NULL, `green` INTEGER NOT NULL, `blue` INTEGER NOT NULL, `alpha` REAL NOT NULL DEFAULT 1, `preset` INTEGER NOT NULL DEFAULT false)",
"fields": [
{
"fieldPath": "uid",
"columnName": "uid",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "red",
"columnName": "red",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "green",
"columnName": "green",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "blue",
"columnName": "blue",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "alpha",
"columnName": "alpha",
"affinity": "REAL",
"notNull": true,
"defaultValue": "1"
},
{
"fieldPath": "preset",
"columnName": "preset",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"uid"
]
}
},
{
"tableName": "MeshPoint",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `row` INTEGER NOT NULL, `col` INTEGER NOT NULL, `x` REAL NOT NULL, `y` REAL NOT NULL, `savedColorId` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "uid",
"columnName": "uid",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "row",
"columnName": "row",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "col",
"columnName": "col",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "x",
"columnName": "x",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "y",
"columnName": "y",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "savedColorId",
"columnName": "savedColorId",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"uid"
]
}
}
],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '8ff8768a392e73b92cf53b11d5f8362d')"
]
}
}
32 changes: 32 additions & 0 deletions composeApp/src/commonMain/kotlin/data/AppRoomDatabase.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package data

import androidx.room.ConstructedBy
import androidx.room.Database
import androidx.room.RoomDatabase
import androidx.room.RoomDatabaseConstructor
import androidx.sqlite.driver.bundled.BundledSQLiteDriver
import kotlinx.coroutines.Dispatchers
import model.MeshPoint
import model.SavedColor

@Database(entities = [SavedColor::class, MeshPoint::class], version = 1)
@ConstructedBy(AppDatabaseConstructor::class)
abstract class AppRoomDatabase : RoomDatabase() {
abstract fun savedColorDao(): SavedColorDao
abstract fun meshPointDao(): MeshPointDao
}

// The Room compiler generates the `actual` implementations.
@Suppress("NO_ACTUAL_FOR_EXPECT")
expect object AppDatabaseConstructor : RoomDatabaseConstructor<AppRoomDatabase> {
override fun initialize(): AppRoomDatabase
}

fun getRoomDatabase(
builder: RoomDatabase.Builder<AppRoomDatabase>
): AppRoomDatabase {
return builder
.setDriver(BundledSQLiteDriver())
.setQueryCoroutineContext(Dispatchers.IO)
.build()
}
28 changes: 28 additions & 0 deletions composeApp/src/commonMain/kotlin/data/DataRepository.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package data

import androidx.compose.ui.graphics.Color
import kotlinx.coroutines.flow.Flow
import model.MeshPoint
import model.SavedColor

val defaultPresetColors = listOf(
Color(0xff7766EE),
Color(0xff8899ff),
Color(0xff429BED),
Color(0xff4FC1A6),
Color(0xffF0C03E),
Color(0xffff5599),
Color(0xFFFF00FF),
)

interface DataRepository {
fun getAllColors(): Flow<List<SavedColor>>
fun getPresetColors(): Flow<List<SavedColor>>
fun getCustomColors(): Flow<List<SavedColor>>
fun addColor(color: SavedColor)
fun deleteColor(color: SavedColor)
fun loadMeshState(): MeshState
fun saveMeshState(state: MeshState)
fun getMeshPoints(): Flow<List<MeshPoint>>
suspend fun saveMeshPoints(points: List<MeshPoint>)
}
19 changes: 19 additions & 0 deletions composeApp/src/commonMain/kotlin/data/MeshPointDao.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package data

import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import kotlinx.coroutines.flow.Flow
import model.MeshPoint

@Dao
interface MeshPointDao {
@Query("SELECT * FROM meshPoint")
fun getAll(): Flow<List<MeshPoint>>

@Insert
suspend fun insertAll(vararg points: MeshPoint)

@Query("DELETE FROM meshPoint")
suspend fun deleteAll()
}
20 changes: 20 additions & 0 deletions composeApp/src/commonMain/kotlin/data/MeshState.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package data

import kotlinx.serialization.Serializable

enum class DimensionMode {
Fixed,
Fill
}

@Serializable
data class MeshState(
val canvasWidthMode: DimensionMode = DimensionMode.Fill,
val canvasWidth: Int = 0,
val canvasHeightMode: DimensionMode = DimensionMode.Fill,
val canvasHeight: Int = 0,
val resolution: Int = 10,
val blurLevel: Float = 0f,
val rows: Int = 3,
val cols: Int = 4,
)
26 changes: 26 additions & 0 deletions composeApp/src/commonMain/kotlin/data/SavedColorDao.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package data

import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import kotlinx.coroutines.flow.Flow
import model.SavedColor

@Dao
interface SavedColorDao {
@Query("SELECT * FROM savedColor")
fun getAll(): Flow<List<SavedColor>>

@Query("SELECT * FROM savedColor WHERE preset = true")
fun getAllPresets(): Flow<List<SavedColor>>

@Query("SELECT * FROM savedColor WHERE preset = false")
fun getAllCustom(): Flow<List<SavedColor>>

@Insert
suspend fun insertAll(vararg colors: SavedColor)

@Delete
suspend fun delete(color: SavedColor)
}
64 changes: 64 additions & 0 deletions composeApp/src/commonMain/kotlin/model/MeshPoint.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package model

import androidx.compose.ui.geometry.Offset
import androidx.room.Entity
import androidx.room.PrimaryKey
import kotlinx.serialization.Serializable

@Serializable
@Entity
data class MeshPoint(
@PrimaryKey(true) val uid: Long = 0,
val row: Int,
val col: Int,
val x: Float,
val y: Float,
val savedColorId: Long,
)

fun Pair<Offset, Long>.toMeshPoint(row: Int, col: Int): MeshPoint {
return MeshPoint(
row = row,
col = col,
x = first.x,
y = first.y,
savedColorId = second
)
}

fun List<MeshPoint>.toOffsetGrid(): List<List<Pair<Offset, Long>>> {
if (isEmpty()) return emptyList()

// Determine the dimensions of the grid.
val maxRow = maxOf { it.row }
val maxCol = maxOf { it.col }

// Initialize an empty grid.
val grid = MutableList(maxRow + 1) { MutableList(maxCol + 1) { Pair(Offset.Zero, 0L) } }

// Populate the grid with the MeshPoint data.
this.forEach { meshPoint ->
grid[meshPoint.row][meshPoint.col] = Pair(Offset(meshPoint.x, meshPoint.y), meshPoint.savedColorId)
}

// Convert the mutable lists to immutable lists.
return grid.map { it.toList() }.toList()
}

fun List<List<Pair<Offset, Long>>>.toSavedMeshPoints(): List<MeshPoint> {
val meshPoints = mutableListOf<MeshPoint>()
this.forEachIndexed { rowIndex, row ->
row.forEachIndexed { colIndex, pair ->
meshPoints.add(
MeshPoint(
row = rowIndex,
col = colIndex,
x = pair.first.x,
y = pair.first.y,
savedColorId = pair.second
)
)
}
}
return meshPoints
}
44 changes: 44 additions & 0 deletions composeApp/src/commonMain/kotlin/model/SavedColor.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package model

import androidx.compose.ui.graphics.Color
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import kotlinx.serialization.Serializable

@Serializable
@Entity
data class SavedColor(
@PrimaryKey(true) val uid: Long = 0,
val red: Int,
val green: Int,
val blue: Int,
@ColumnInfo(defaultValue = "1")
val alpha: Float = 1f,
@ColumnInfo(defaultValue = "false")
val preset: Boolean = false
)

fun SavedColor.toColor(): Color {
return Color(
red = red / 255f,
green = green / 255f,
blue = blue / 255f,
alpha = alpha
)
}

fun Color.toSavedColor(uid: Long = 0, preset: Boolean = false): SavedColor {
return SavedColor(
uid = uid,
red = (red * 255).toInt(),
green = (green * 255).toInt(),
blue = (blue * 255).toInt(),
alpha = alpha,
preset = preset
)
}

fun List<SavedColor>.findColor(uid: Long): Color {
return find { it.uid == uid }?.toColor() ?: Color.Transparent
}
Loading