Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
64 changes: 56 additions & 8 deletions app/src/main/java/io/github/naharaoss/canvaslite/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,13 @@ import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.staggeredgrid.items
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MaterialTheme
Expand All @@ -39,6 +43,7 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
Expand All @@ -59,6 +64,8 @@ import io.github.naharaoss.canvaslite.compose.page.LibraryPageScaffold
import io.github.naharaoss.canvaslite.compose.page.LibraryRoute
import io.github.naharaoss.canvaslite.compose.Overlay
import io.github.naharaoss.canvaslite.compose.OverlayState
import io.github.naharaoss.canvaslite.compose.TimeDisplayText
import io.github.naharaoss.canvaslite.compose.page.LibraryItem
import io.github.naharaoss.canvaslite.compose.page.PreferencesPage
import io.github.naharaoss.canvaslite.compose.page.PreferencesRoute
import io.github.naharaoss.canvaslite.compose.panel.LayerBackgroundItem
Expand Down Expand Up @@ -139,16 +146,57 @@ class MainActivity : ComponentActivity() {
LaunchedEffect(Unit) { folderViewModel.refresh() }

LibraryPageContent(
items = items,
innerPadding = innerPadding,
onOpen = {
when (it.type) {
Library.ItemType.Folder -> libraryNavController.navigate(LibraryRoute(it.libraryId, it.metadata.name))
Library.ItemType.Canvas -> navController.navigate(CanvasPage(it.libraryId))
else -> {}
empty = items?.isEmpty() ?: false,
loading = items == null,
innerPadding = innerPadding
) {
val items = items
if (items != null) items(items, { it.libraryId }) { item ->
LibraryItem(
onClick = {
when (item.type) {
Library.ItemType.Canvas -> navController.navigate(CanvasPage(item.libraryId))
Library.ItemType.Folder -> libraryNavController.navigate(LibraryRoute(item.libraryId, item.metadata.name))
}
},
name = { Text(item.metadata.name) },
supportingContent = {
when {
item.type == Library.ItemType.Folder -> Text("Folder")
item.type == Library.ItemType.Canvas && item.metadata.canvasSize == null -> Text("Infinite canvas")
item.type == Library.ItemType.Canvas && item.metadata.canvasSize != null -> Text(item.metadata.canvasSize.toString())
else -> item.type.toString()
}

TimeDisplayText(item.metadata.lastModified)
}
) {
if (item.type == Library.ItemType.Canvas) {
var thumbnail: ImageBitmap? by remember { mutableStateOf(null) }

LaunchedEffect(item.libraryId) {
thumbnail = folderViewModel.library.loadThumbnail(item.libraryId)
Log.d("MainActivity", "Thumbnail is $thumbnail")
}

Box(Modifier
.aspectRatio(item.metadata.canvasSize?.let { it.width / it.height.toFloat() } ?: 1f)
.background(Color.White, MaterialTheme.shapes.medium)) {
val thumbnail = thumbnail

if (thumbnail != null) {
Log.d("MainActivity", "Recompose with $thumbnail")
Image(
modifier = Modifier.fillMaxSize(),
bitmap = thumbnail,
contentDescription = null
)
}
}
}
}
}
)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import androidx.compose.animation.SizeTransform
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
Expand All @@ -19,6 +20,7 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridScope
import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridItemSpan
Expand Down Expand Up @@ -51,6 +53,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
Expand Down Expand Up @@ -191,9 +194,10 @@ fun LibraryPageScaffold(
@Composable
fun LibraryPageContent(
modifier: Modifier = Modifier,
items: List<Library.Item>?,
empty: Boolean,
loading: Boolean,
innerPadding: PaddingValues,
onOpen: (Library.Item) -> Unit
content: LazyStaggeredGridScope.() -> Unit
) {
Box(modifier.padding(innerPadding)) {
LazyVerticalStaggeredGrid(
Expand All @@ -203,55 +207,30 @@ fun LibraryPageContent(
verticalItemSpacing = 16.dp,
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
if (items != null) {
when (items.isEmpty()) {
true -> item(span = StaggeredGridItemSpan.FullLine) {
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onSurfaceVariant) {
Column(
modifier = Modifier.padding(0.dp, 96.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Icon(
modifier = Modifier.size(96.dp),
painter = painterResource(R.drawable.folder_24px),
contentDescription = null
)
Text("Umm... nothing here!")
}
}
}
false -> items(items, { it.libraryId }) { item ->
LibraryItem(
modifier = Modifier.fillMaxWidth(),
onClick = { onOpen(item) },
name = { Text(item.metadata.name) },
supportingContent = {
Text(when {
item.type == Library.ItemType.Folder -> "Folder"
item.type == Library.ItemType.Canvas && item.metadata.canvasSize == null -> "Infinite canvas"
item.type == Library.ItemType.Canvas && item.metadata.canvasSize != null -> item.metadata.canvasSize.toString()
else -> "Unknown"
})
TimeDisplayText(item.metadata.lastModified)
}
if (empty) {
item(span = StaggeredGridItemSpan.FullLine) {
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onSurfaceVariant) {
Column(
modifier = Modifier.padding(0.dp, 96.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
when (item.type) {
Library.ItemType.Canvas -> Box(Modifier
.aspectRatio(item.metadata.canvasSize
?.let { it.width / it.height.toFloat() }
?: 1f)
.background(Color.White, MaterialTheme.shapes.medium))
else -> {}
}
Icon(
modifier = Modifier.size(96.dp),
painter = painterResource(R.drawable.folder_24px),
contentDescription = null
)
Text("Umm... nothing here!")
}
}
}
} else {
content()
}
}

AnimatedContent(
modifier = Modifier.align(Alignment.Center),
targetState = items == null,
targetState = loading,
content = { loading -> if (loading) LoadingIndicator(Modifier.align(Alignment.Center)) }
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package io.github.naharaoss.canvaslite.engine.project

import android.annotation.SuppressLint
import android.graphics.Bitmap
import androidx.compose.runtime.Composable
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.platform.LocalResources
import io.github.naharaoss.canvaslite.engine.Blending
import io.github.naharaoss.canvaslite.ext.ColorSerializer
Expand Down Expand Up @@ -38,6 +40,8 @@ interface Canvas {

fun removeLayer(layer: Layer)

fun putThumbnail(bitmap: Bitmap) {}

@Serializable
data class CanvasSize(val width: Int, val height: Int) {
override fun toString() = "${width}x${height}"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.github.naharaoss.canvaslite.engine.project

import androidx.compose.ui.graphics.ImageBitmap
import io.github.naharaoss.canvaslite.ext.ZonedDateTimeSerializer
import kotlinx.serialization.Serializable
import java.time.ZonedDateTime
Expand All @@ -14,6 +15,8 @@ interface Library {
suspend fun loadCanvas(canvasId: String): Canvas
suspend fun delete(libraryId: String)

suspend fun loadThumbnail(canvasId: String): ImageBitmap?

data class Item(
val libraryId: String,
val type: ItemType,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
package io.github.naharaoss.canvaslite.engine.project

import android.graphics.ImageDecoder
import android.util.Log
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asImageBitmap
import androidx.core.graphics.createBitmap
import androidx.core.graphics.set
import io.github.naharaoss.canvaslite.ext.readAsJson
import io.github.naharaoss.canvaslite.ext.writeAsJson
import kotlinx.coroutines.Dispatchers
Expand Down Expand Up @@ -96,6 +102,11 @@ class LibraryImpl(val root: File) : Library {
TODO("Not yet implemented")
}

override suspend fun loadThumbnail(canvasId: String): ImageBitmap? {
val file = File(contentRoot, "$canvasId/thumbnail.png")
return if (file.exists()) ImageDecoder.decodeBitmap(ImageDecoder.createSource(file)).asImageBitmap() else null
}

@Serializable
data class FolderIndex(
val parentId: String?,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.github.naharaoss.canvaslite.engine.project

import android.graphics.Bitmap
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import io.github.naharaoss.canvaslite.engine.Blending
Expand All @@ -9,6 +10,7 @@ import io.github.naharaoss.canvaslite.ext.readAsJson
import io.github.naharaoss.canvaslite.ext.writeAsJson
import kotlinx.serialization.Serializable
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.io.RandomAccessFile
import java.io.Serial
Expand Down Expand Up @@ -83,6 +85,12 @@ class ScuffedFileCanvas(val root: File) : Canvas {
if (currentlySelected) currentLayer = if (layers.isNotEmpty()) layers.lastIndex else null
}

override fun putThumbnail(bitmap: Bitmap) {
FileOutputStream(File(root, "thumbnail.png")).use { stream ->
bitmap.compress(Bitmap.CompressFormat.PNG, 0, stream)
}
}

private class FileLayer(
val canvas: ScuffedFileCanvas,
val layerId: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,28 @@ class CanvasRenderer(
}
}

/**
* Render to temporary render target then transfer the pixels in RGBA format to provided byte
* buffer.
*/
fun captureAsRgba(worldToViewport: Matrix, width: Int, height: Int, dst: ByteBuffer) {
GPUTexture(GPUTexture.Type.Texture2D).use { color ->
color.bind { initTexture(width, height) }

GPURenderTarget().use { target ->
target.bind {
attach(GPURenderTarget.Attachment.Color(0), color)
ensureCompleted()
GLES20.glViewport(0, 0, width, height)
renderBackground(worldToViewport)
renderContent(worldToViewport)
readPixels(0, 0, width, height, dst)
dst.position(dst.position() + width * height * 4)
}
}
}
}

override fun close() {
trackDrawingTiles = false
drawingTiles.clear()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.github.naharaoss.canvaslite.model

import androidx.compose.ui.graphics.ImageBitmap
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
import androidx.lifecycle.viewModelScope
Expand Down Expand Up @@ -33,4 +34,8 @@ class LibraryViewModel(val library: Library) : ViewModel() {
onFinished(item)
}
}

fun loadThumbnail(canvasId: String, onFinished: (ImageBitmap?) -> Unit) {
viewModelScope.launch { onFinished(library.loadThumbnail(canvasId)) }
}
}
Loading