diff --git a/README.md b/README.md index 77c85bd..fd29995 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ This is the first multiplatform drawing library! - Different image rations - Filling tool - Optimizing rendering (convert drawn PATHes) -- Migrate from Compose dependencies in DrawController +- Migrate from Compose dependencies in [controller folder](drawbox/src/commonMain/kotlin/io/github/markyav/drawbox/controller) ## Demo diff --git a/buildSrc/src/main/kotlin/Library.kt b/buildSrc/src/main/kotlin/Library.kt index 47c1c1a..3391b03 100644 --- a/buildSrc/src/main/kotlin/Library.kt +++ b/buildSrc/src/main/kotlin/Library.kt @@ -5,7 +5,7 @@ object Library { val group = "io.github.markyav.drawbox" val artifact = "drawbox" - val version = "1.1.0" + val version = "1.2.0" object License { val name = "Apache-2.0" diff --git a/drawbox/src/commonMain/kotlin/io/github/markyav/drawbox/box/DrawBox.kt b/drawbox/src/commonMain/kotlin/io/github/markyav/drawbox/box/DrawBox.kt index bdc5846..94afe7d 100644 --- a/drawbox/src/commonMain/kotlin/io/github/markyav/drawbox/box/DrawBox.kt +++ b/drawbox/src/commonMain/kotlin/io/github/markyav/drawbox/box/DrawBox.kt @@ -3,30 +3,34 @@ package io.github.markyav.drawbox.box import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier -import io.github.markyav.drawbox.controller.DrawBoxBackground +import io.github.markyav.drawbox.controller.DrawBoxSubscription import io.github.markyav.drawbox.controller.DrawController import io.github.markyav.drawbox.model.PathWrapper +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.StateFlow @Composable fun DrawBox( controller: DrawController, modifier: Modifier = Modifier.fillMaxSize(), ) { - val path: List? = controller.pathToDrawOnCanvas - val background: DrawBoxBackground = controller.background - val canvasAlpha: Float = controller.canvasOpacity + val path: StateFlow> = remember { + controller.getPathWrappersForDrawbox(DrawBoxSubscription.DynamicUpdate) + } Box(modifier = modifier) { DrawBoxBackground( - background = background, + background = controller.background.value, modifier = Modifier.fillMaxSize(), ) DrawBoxCanvas( - path = path ?: emptyList(), - alpha = canvasAlpha, + pathListWrapper = path, + alpha = controller.canvasOpacity.value, onSizeChanged = controller::connectToDrawBox, - onTap = controller::insertNewPath, + onTap = controller::onTap, onDragStart = controller::insertNewPath, onDrag = controller::updateLatestPath, onDragEnd = controller::finalizePath, diff --git a/drawbox/src/commonMain/kotlin/io/github/markyav/drawbox/box/DrawBoxCanvas.kt b/drawbox/src/commonMain/kotlin/io/github/markyav/drawbox/box/DrawBoxCanvas.kt index c50f408..2a5a575 100644 --- a/drawbox/src/commonMain/kotlin/io/github/markyav/drawbox/box/DrawBoxCanvas.kt +++ b/drawbox/src/commonMain/kotlin/io/github/markyav/drawbox/box/DrawBoxCanvas.kt @@ -4,6 +4,8 @@ import androidx.compose.foundation.Canvas import androidx.compose.foundation.gestures.detectDragGestures import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha @@ -18,10 +20,11 @@ import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.unit.IntSize import io.github.markyav.drawbox.model.PathWrapper import io.github.markyav.drawbox.util.createPath +import kotlinx.coroutines.flow.StateFlow @Composable fun DrawBoxCanvas( - path: List, + pathListWrapper: StateFlow>, alpha: Float, onSizeChanged: (IntSize) -> Unit, onTap: (Offset) -> Unit, @@ -33,6 +36,7 @@ fun DrawBoxCanvas( val onDragMapper: (change: PointerInputChange, dragAmount: Offset) -> Unit = remember { { change, _ -> onDrag(change.position) } } + val path by pathListWrapper.collectAsState() Canvas(modifier = modifier .onSizeChanged(onSizeChanged) diff --git a/drawbox/src/commonMain/kotlin/io/github/markyav/drawbox/controller/DrawController.kt b/drawbox/src/commonMain/kotlin/io/github/markyav/drawbox/controller/DrawController.kt index 0f08402..8dd1294 100644 --- a/drawbox/src/commonMain/kotlin/io/github/markyav/drawbox/controller/DrawController.kt +++ b/drawbox/src/commonMain/kotlin/io/github/markyav/drawbox/controller/DrawController.kt @@ -1,105 +1,129 @@ package io.github.markyav.drawbox.controller import androidx.compose.runtime.* -import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.* import androidx.compose.ui.unit.IntSize import io.github.markyav.drawbox.model.PathWrapper +import io.github.markyav.drawbox.util.addNotNull +import io.github.markyav.drawbox.util.combineStates import io.github.markyav.drawbox.util.createPath -import io.github.markyav.drawbox.util.pop +import io.github.markyav.drawbox.util.mapState import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.* +import kotlinx.coroutines.launch +import kotlin.reflect.KProperty /** * DrawController interacts with [DrawBox] and it allows you to control the canvas and all the components with it. */ class DrawController { - private var state: DrawBoxConnectionState by mutableStateOf(DrawBoxConnectionState.Disconnected) + private var state: MutableStateFlow = MutableStateFlow(DrawBoxConnectionState.Disconnected) /** A stateful list of [Path] that is drawn on the [Canvas]. */ - private val drawnPaths: SnapshotStateList = mutableStateListOf() + private val drawnPaths: MutableStateFlow> = MutableStateFlow(emptyList()) - private val completelyDrawnPaths: SnapshotStateList = mutableStateListOf() - - /** A stateful list of [Path] that is drawn on the [Canvas] and is scaled for the connected bitmap. */ - internal val pathToDrawOnCanvas: List? by derivedStateOf { - (state as? DrawBoxConnectionState.Connected)?.let { - drawnPaths.scale(it.size.toFloat()) - } - } + private val activeDrawingPath: MutableStateFlow?> = MutableStateFlow(null) /** A stateful list of [Path] that was drawn on the [Canvas] but user retracted his action. */ - private val canceledPaths: SnapshotStateList = mutableStateListOf() + private val canceledPaths: MutableStateFlow> = MutableStateFlow(emptyList()) /** An [canvasOpacity] of the [Canvas] in the [DrawBox] */ - var canvasOpacity: Float by mutableStateOf(1f) + var canvasOpacity: MutableStateFlow = MutableStateFlow(1f) /** An [opacity] of the stroke */ - var opacity: Float by mutableStateOf(1f) + var opacity: MutableStateFlow = MutableStateFlow(1f) /** A [strokeWidth] of the stroke */ - var strokeWidth: Float by mutableStateOf(10f) + var strokeWidth: MutableStateFlow = MutableStateFlow(10f) /** A [color] of the stroke */ - var color: Color by mutableStateOf(Color.Red) + var color: MutableStateFlow = MutableStateFlow(Color.Red) /** A [background] of the background of DrawBox */ - var background: DrawBoxBackground by mutableStateOf(DrawBoxBackground.NoBackground) + var background: MutableStateFlow = MutableStateFlow(DrawBoxBackground.NoBackground) /** Indicate how many redos it is possible to do. */ - val undoCount: Int by derivedStateOf { drawnPaths.size } + val undoCount = drawnPaths.mapState { it.size } /** Indicate how many undos it is possible to do. */ - val redoCount: Int by derivedStateOf { canceledPaths.size } + val redoCount = canceledPaths.mapState { it.size } /** Executes undo the drawn path if possible. */ fun undo() { - if (drawnPaths.isNotEmpty()) { - canceledPaths.add(drawnPaths.pop()) - finalizePath() + if (drawnPaths.value.isNotEmpty()) { + val _drawnPaths = drawnPaths.value.toMutableList() + val _canceledPaths = canceledPaths.value.toMutableList() + + _canceledPaths.add(_drawnPaths.removeLast()) + + drawnPaths.value = _drawnPaths + canceledPaths.value = _canceledPaths } } /** Executes redo the drawn path if possible. */ fun redo() { - if (canceledPaths.isNotEmpty()) { - drawnPaths.add(canceledPaths.pop()) - finalizePath() + if (canceledPaths.value.isNotEmpty()) { + val _drawnPaths = drawnPaths.value.toMutableList() + val _canceledPaths = canceledPaths.value.toMutableList() + + _drawnPaths.add(_canceledPaths.removeLast()) + + drawnPaths.value = _drawnPaths + canceledPaths.value = _canceledPaths } } /** Clear drawn paths and the bitmap image. */ fun reset() { - drawnPaths.clear() - canceledPaths.clear() - completelyDrawnPaths.clear() + drawnPaths.value = emptyList() + canceledPaths.value = emptyList() } /** Call this function when user starts drawing a path. */ internal fun updateLatestPath(newPoint: Offset) { - (state as? DrawBoxConnectionState.Connected)?.let { - drawnPaths.last().points.add(newPoint.div(it.size.toFloat())) + (state.value as? DrawBoxConnectionState.Connected)?.let { + require(activeDrawingPath.value != null) + val list = activeDrawingPath.value!!.toMutableList() + list.add(newPoint.div(it.size.toFloat())) + activeDrawingPath.value = list } } /** When dragging call this function to update the last path. */ internal fun insertNewPath(newPoint: Offset) { - (state as? DrawBoxConnectionState.Connected)?.let { - val pathWrapper = PathWrapper( + (state.value as? DrawBoxConnectionState.Connected)?.let { + require(activeDrawingPath.value == null) + /*val pathWrapper = PathWrapper( points = mutableStateListOf(newPoint.div(it.size.toFloat())), - strokeColor = color, - alpha = opacity, - strokeWidth = strokeWidth.div(it.size.toFloat()), - ) - drawnPaths.add(pathWrapper) - canceledPaths.clear() + strokeColor = color.value, + alpha = opacity.value, + strokeWidth = strokeWidth.value.div(it.size.toFloat()), + )*/ + activeDrawingPath.value = listOf(newPoint.div(it.size.toFloat())) + canceledPaths.value = emptyList() } } internal fun finalizePath() { - completelyDrawnPaths.clear() - completelyDrawnPaths.addAll(drawnPaths) + (state.value as? DrawBoxConnectionState.Connected)?.let { + require(activeDrawingPath.value != null) + val _drawnPaths = drawnPaths.value.toMutableList() + + val pathWrapper = PathWrapper( + points = activeDrawingPath.value!!, + strokeColor = color.value, + alpha = opacity.value, + strokeWidth = strokeWidth.value.div(it.size.toFloat()), + ) + _drawnPaths.add(pathWrapper) + + drawnPaths.value = _drawnPaths + activeDrawingPath.value = null + } } /** Call this function to connect to the [DrawBox]. */ @@ -107,33 +131,64 @@ class DrawController { if ( size.width > 0 && size.height > 0 && - size.width == size.height //&& - //state is DrawBoxConnectionState.Disconnected + size.width == size.height ) { - state = DrawBoxConnectionState.Connected(size = size.width) + state.value = DrawBoxConnectionState.Connected(size = size.width) } } + internal fun onTap(newPoint: Offset) { + insertNewPath(newPoint) + finalizePath() + } + private fun List.scale(size: Float): List { return this.map { pw -> val t = pw.points.map { it.times(size) } pw.copy( - points = SnapshotStateList().also { it.addAll(t) }, + points = mutableListOf().also { it.addAll(t) }, strokeWidth = pw.strokeWidth * size ) } } - fun getBitmap(size: Int, coroutineScope: CoroutineScope, subscription: DrawBoxSubscription): StateFlow { + fun getDrawPath(subscription: DrawBoxSubscription): StateFlow> { + return when (subscription) { + is DrawBoxSubscription.DynamicUpdate -> getDynamicUpdateDrawnPath() + is DrawBoxSubscription.FinishDrawingUpdate -> drawnPaths + } + } + + private fun getDynamicUpdateDrawnPath(): StateFlow> { + return combineStates(drawnPaths, activeDrawingPath) { a, b -> + val _a = a.toMutableList() + (state.value as? DrawBoxConnectionState.Connected)?.let { + val pathWrapper = PathWrapper( + points = activeDrawingPath.value ?: emptyList(), + strokeColor = color.value, + alpha = opacity.value, + strokeWidth = strokeWidth.value.div(it.size.toFloat()), + ) + _a.addNotNull(pathWrapper) + } + _a + } + } + + internal fun getPathWrappersForDrawbox(subscription: DrawBoxSubscription): StateFlow> { + return combineStates(getDrawPath(subscription), state) { paths, st -> + val size = (st as? DrawBoxConnectionState.Connected)?.size ?: 1 + paths.scale(size.toFloat()) + } + } + + fun getBitmap(size: Int, subscription: DrawBoxSubscription): StateFlow { val initialBitmap = ImageBitmap(size, size, ImageBitmapConfig.Argb8888) - return flow { + val path = getDrawPath(subscription) + return path.mapState { val bitmap = ImageBitmap(size, size, ImageBitmapConfig.Argb8888) val canvas = Canvas(bitmap) - val path = when (subscription) { - is DrawBoxSubscription.DynamicUpdate -> drawnPaths - is DrawBoxSubscription.FinishDrawingUpdate -> completelyDrawnPaths - } - path.scale(size.toFloat()).forEach { pw -> + it.scale(size.toFloat()).forEach { pw -> canvas.drawPath( createPath(pw.points), paint = Paint().apply { @@ -146,7 +201,7 @@ class DrawController { } ) } - emit(bitmap) - }.stateIn(coroutineScope, started = SharingStarted.Eagerly, initialValue = initialBitmap) + bitmap + } } } \ No newline at end of file diff --git a/drawbox/src/commonMain/kotlin/io/github/markyav/drawbox/model/PathWrapper.kt b/drawbox/src/commonMain/kotlin/io/github/markyav/drawbox/model/PathWrapper.kt index d47163b..63c601b 100644 --- a/drawbox/src/commonMain/kotlin/io/github/markyav/drawbox/model/PathWrapper.kt +++ b/drawbox/src/commonMain/kotlin/io/github/markyav/drawbox/model/PathWrapper.kt @@ -5,7 +5,7 @@ import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color data class PathWrapper( - var points: SnapshotStateList, + var points: List, val strokeWidth: Float = 5f, val strokeColor: Color, val alpha: Float = 1f diff --git a/drawbox/src/commonMain/kotlin/io/github/markyav/drawbox/util/SnapshotStateListUtil.kt b/drawbox/src/commonMain/kotlin/io/github/markyav/drawbox/util/SnapshotStateListUtil.kt deleted file mode 100644 index 432518f..0000000 --- a/drawbox/src/commonMain/kotlin/io/github/markyav/drawbox/util/SnapshotStateListUtil.kt +++ /dev/null @@ -1,10 +0,0 @@ -package io.github.markyav.drawbox.util - -import androidx.compose.runtime.snapshots.SnapshotStateList - -fun SnapshotStateList.pop(): T { - val len = this.size - val obj = this.last() - removeAt(len - 1) - return obj -} \ No newline at end of file diff --git a/drawbox/src/commonMain/kotlin/io/github/markyav/drawbox/util/StateFlowUtil.kt b/drawbox/src/commonMain/kotlin/io/github/markyav/drawbox/util/StateFlowUtil.kt new file mode 100644 index 0000000..c0da537 --- /dev/null +++ b/drawbox/src/commonMain/kotlin/io/github/markyav/drawbox/util/StateFlowUtil.kt @@ -0,0 +1,41 @@ +package io.github.markyav.drawbox.util + +import kotlinx.coroutines.InternalCoroutinesApi +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.* + +/** + * This implementation will be used until official implementation comes. + * Source: https://github.com/Kotlin/kotlinx.coroutines/issues/2631#issuecomment-870565860 + */ + +class DerivedStateFlow( + private val getValue: () -> T, + private val flow: Flow +) : StateFlow { + + override val replayCache: List + get () = listOf(value) + + override val value: T + get () = getValue() + + @InternalCoroutinesApi + override suspend fun collect(collector: FlowCollector): Nothing { + coroutineScope { flow.distinctUntilChanged().stateIn(this).collect(collector) } + } +} + +fun StateFlow.mapState(transform: (a: T1) -> R): StateFlow { + return DerivedStateFlow( + getValue = { transform(this.value) }, + flow = this.map { a -> transform(a) } + ) +} + +fun combineStates(flow: StateFlow, flow2: StateFlow, transform: (a: T1, b: T2) -> R): StateFlow { + return DerivedStateFlow( + getValue = { transform(flow.value, flow2.value) }, + flow = combine(flow, flow2) { a, b -> transform(a, b) } + ) +} \ No newline at end of file diff --git a/drawbox/src/commonMain/kotlin/io/github/markyav/drawbox/util/Util.kt b/drawbox/src/commonMain/kotlin/io/github/markyav/drawbox/util/Util.kt index e190cd6..692f00a 100644 --- a/drawbox/src/commonMain/kotlin/io/github/markyav/drawbox/util/Util.kt +++ b/drawbox/src/commonMain/kotlin/io/github/markyav/drawbox/util/Util.kt @@ -26,4 +26,11 @@ fun createPath(points: List): Path { } private fun calculateMidpoint(start: Offset, end: Offset) = - Offset((start.x + end.x) / 2, (start.y + end.y) / 2) \ No newline at end of file + Offset((start.x + end.x) / 2, (start.y + end.y) / 2) + +fun MutableList.addNotNull(element: E?): Boolean { + return if (element == null) + false + else + this.add(element) +} \ No newline at end of file diff --git a/sample/android/src/main/java/io/github/markyav/drawbox/android/MainActivity.kt b/sample/android/src/main/java/io/github/markyav/drawbox/android/MainActivity.kt index 122a67e..8965d9f 100644 --- a/sample/android/src/main/java/io/github/markyav/drawbox/android/MainActivity.kt +++ b/sample/android/src/main/java/io/github/markyav/drawbox/android/MainActivity.kt @@ -3,65 +3,15 @@ package io.github.markyav.drawbox.android import android.os.Bundle import androidx.activity.compose.setContent import androidx.appcompat.app.AppCompatActivity -import androidx.compose.foundation.Image -import androidx.compose.foundation.border -import androidx.compose.foundation.layout.* import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.unit.dp -import io.github.markyav.drawbox.box.DrawBox -import io.github.markyav.drawbox.controller.DrawBoxBackground -import io.github.markyav.drawbox.controller.DrawBoxSubscription -import io.github.markyav.drawbox.controller.DrawController +import io.github.markyav.drawbox.android.app.MainScreen class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MaterialTheme { - val controller = remember { DrawController() } - val coroutineSubscription = rememberCoroutineScope() - val bitmap by controller.getBitmap(250, coroutineSubscription, DrawBoxSubscription.DynamicUpdate).collectAsState() - val bitmapFinishDrawingUpdate by controller.getBitmap(250, coroutineSubscription, DrawBoxSubscription.FinishDrawingUpdate).collectAsState() - - controller.background = DrawBoxBackground.ColourBackground(color = Color.Blue, alpha = 0.15f) - controller.canvasOpacity = 0.5f - - Row { - DrawBox( - controller = controller, - modifier = Modifier - .fillMaxHeight() - .aspectRatio(1f) - .padding(100.dp) - .border(width = 1.dp, color = Color.Blue), - ) - Column { - Text("DynamicUpdate:") - Spacer(modifier = Modifier.height(10.dp)) - Image( - bitmap, - contentDescription = "drawn bitmap", - modifier = Modifier.size(200.dp).border(width = 1.dp, color = Color.Red), - ) - - Spacer(modifier = Modifier.height(50.dp)) - - Text("FinishDrawingUpdate:") - Spacer(modifier = Modifier.height(10.dp)) - Image( - bitmapFinishDrawingUpdate, - contentDescription = "drawn bitmap", - modifier = Modifier.size(200.dp).border(width = 1.dp, color = Color.Red), - ) - } - } + MainScreen() } } } diff --git a/sample/android/src/main/java/io/github/markyav/drawbox/android/app/MainScreen.kt b/sample/android/src/main/java/io/github/markyav/drawbox/android/app/MainScreen.kt new file mode 100644 index 0000000..f7bb417 --- /dev/null +++ b/sample/android/src/main/java/io/github/markyav/drawbox/android/app/MainScreen.kt @@ -0,0 +1,38 @@ +package io.github.markyav.drawbox.android.app + +import androidx.compose.foundation.Image +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.unit.dp +import io.github.markyav.drawbox.android.drawing.DrawingScreen + +@Composable +fun MainScreen() { + val bitmap = remember { mutableStateOf(ImageBitmap(1, 1)) } + + val bitmapCallback: (ImageBitmap) -> Unit = { + bitmap.value = it + } + + Row { + Column(modifier = Modifier.weight(2f, true)) { + DrawingScreen(bitmapCallback = bitmapCallback) + } + Column(modifier = Modifier.weight(1f, true)) { + Image( + bitmap = bitmap.value, + contentDescription = null, + modifier = Modifier.fillMaxSize().padding(8.dp).border(1.dp, Color.Blue), + ) + } + } +} \ No newline at end of file diff --git a/sample/android/src/main/java/io/github/markyav/drawbox/android/drawing/DrawingScreen.kt b/sample/android/src/main/java/io/github/markyav/drawbox/android/drawing/DrawingScreen.kt new file mode 100644 index 0000000..08836f3 --- /dev/null +++ b/sample/android/src/main/java/io/github/markyav/drawbox/android/drawing/DrawingScreen.kt @@ -0,0 +1,31 @@ +package io.github.markyav.drawbox.android.drawing + +import android.annotation.SuppressLint +import android.util.Log +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ImageBitmap +import io.github.markyav.drawbox.controller.DrawBoxSubscription +import io.github.markyav.drawbox.controller.DrawController +import kotlinx.coroutines.flow.collectLatest + +@SuppressLint("CoroutineCreationDuringComposition") +@Composable +fun DrawingScreen( + bitmapCallback: (ImageBitmap) -> Unit, +) { + val drawController = remember { DrawController() } + + LaunchedEffect(Unit) { + drawController.color.value = Color.Blue + drawController.getBitmap(500, DrawBoxSubscription.FinishDrawingUpdate) + .collectLatest { + Log.i("TAG_aaa", "DrawingScreen: emitted!") + bitmapCallback(it) + } + } + + ExpandedDrawingScreen(drawController) +} \ No newline at end of file diff --git a/sample/android/src/main/java/io/github/markyav/drawbox/android/drawing/ExpandedDrawingScreen.kt b/sample/android/src/main/java/io/github/markyav/drawbox/android/drawing/ExpandedDrawingScreen.kt new file mode 100644 index 0000000..b7b0edf --- /dev/null +++ b/sample/android/src/main/java/io/github/markyav/drawbox/android/drawing/ExpandedDrawingScreen.kt @@ -0,0 +1,65 @@ +package io.github.markyav.drawbox.android.drawing + +import android.util.Log +import androidx.compose.foundation.Image +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.* +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.filled.ArrowForward +import androidx.compose.material.icons.filled.Clear +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import io.github.markyav.drawbox.box.DrawBox +import io.github.markyav.drawbox.controller.DrawBoxSubscription +import io.github.markyav.drawbox.controller.DrawController + +@Composable +internal fun ExpandedDrawingScreen( + drawController: DrawController, +) { + val bitmap by remember { drawController.getBitmap(500, DrawBoxSubscription.FinishDrawingUpdate) }.collectAsState() + + Row { + Image(bitmap = bitmap, modifier = Modifier + .size(50.dp) + .border(1.dp, Color.Red), contentDescription = null) + + Column(modifier = Modifier.weight(4.5f, false)) { + Log.i("TAG_aaa", "ExpandedDrawingScreen: $bitmap") + DrawBox( + controller = drawController, + modifier = Modifier + .aspectRatio(1f) + .padding(8.dp) + .border(width = 1.dp, color = Color.Blue) + .weight(1f, fill = false), + ) + Row { + val enableUndo by remember { derivedStateOf { drawController.undoCount.value > 0 } } + val enableRedo by remember { derivedStateOf { drawController.redoCount.value > 0 } } + IconButton(onClick = drawController::undo, enabled = enableUndo) { + Icon(imageVector = Icons.Default.ArrowBack, contentDescription = "undo") + } + IconButton(onClick = drawController::redo, enabled = enableRedo) { + Icon(imageVector = Icons.Default.ArrowForward, contentDescription = "redo") + } + IconButton(onClick = drawController::reset, enabled = enableUndo || enableRedo) { + Icon(imageVector = Icons.Default.Clear, contentDescription = "reset") + } + } + } + } +} + +/*@AndroidPreviewDevices +@Composable +fun ExpandedDrawingScreenPreview() { + MobiSketchTheme { + ExpandedDrawingScreen() + } +}*/ \ No newline at end of file diff --git a/sample/desktop/src/jvmMain/kotlin/Main.kt b/sample/desktop/src/jvmMain/kotlin/Main.kt index 7cb2e0e..2090d85 100644 --- a/sample/desktop/src/jvmMain/kotlin/Main.kt +++ b/sample/desktop/src/jvmMain/kotlin/Main.kt @@ -21,20 +21,26 @@ import io.github.markyav.drawbox.controller.DrawController fun main() = application { Window(onCloseRequest = ::exitApplication) { val controller = remember { DrawController() } - val coroutineSubscription = rememberCoroutineScope() - val bitmap by controller.getBitmap(250, coroutineSubscription, DrawBoxSubscription.DynamicUpdate).collectAsState() - val bitmapFinishDrawingUpdate by controller.getBitmap(250, coroutineSubscription, DrawBoxSubscription.FinishDrawingUpdate).collectAsState() + val bitmap by remember { controller.getBitmap(250, DrawBoxSubscription.DynamicUpdate) }.collectAsState() + val bitmapFinishDrawingUpdate by remember { controller.getBitmap(250, DrawBoxSubscription.FinishDrawingUpdate) }.collectAsState() + + val undoCount by controller.undoCount.collectAsState() + val redoCount by controller.redoCount.collectAsState() + val enableUndo by remember { derivedStateOf { undoCount > 0 } } + val enableRedo by remember { derivedStateOf { redoCount > 0 } } + + val strokeWith by controller.strokeWidth.collectAsState() + val canvasOpacity by controller.canvasOpacity.collectAsState() + val background by controller.background.collectAsState() LaunchedEffect(Unit) { - controller.background = DrawBoxBackground.ColourBackground(color = Color.Blue, alpha = 0.15f) - controller.canvasOpacity = 0.5f + controller.background.value = DrawBoxBackground.ColourBackground(color = Color.Blue, alpha = 0.15f) + controller.canvasOpacity.value = 0.5f } Row { Column(modifier = Modifier.weight(2f, false)) { Row { - val enableUndo by remember { derivedStateOf { controller.undoCount > 0 } } - val enableRedo by remember { derivedStateOf { controller.redoCount > 0 } } IconButton(onClick = controller::undo, enabled = enableUndo) { Icon(imageVector = Icons.Default.ArrowBack, contentDescription = "undo") } @@ -49,16 +55,16 @@ fun main() = application { Column(modifier = Modifier.weight(2f, false)) { Text("Stroke width") Slider( - value = controller.strokeWidth, - onValueChange = { controller.strokeWidth = it }, + value = strokeWith, + onValueChange = { controller.strokeWidth.value = it }, valueRange = 1f..100f ) } Column(modifier = Modifier.weight(2f, false)) { Text("Canvas opacity") Slider( - value = controller.canvasOpacity, - onValueChange = { controller.canvasOpacity = it }, + value = canvasOpacity, + onValueChange = { controller.canvasOpacity.value = it }, valueRange = 0f..1f ) } @@ -68,13 +74,13 @@ fun main() = application { Column(modifier = Modifier.weight(2f, true)) { Text("Color") Row { - TextButton(onClick = { controller.color = Color.Red }) { + TextButton(onClick = { controller.color.value = Color.Red }) { Text("Red") } - TextButton(onClick = { controller.color = Color.Green }) { + TextButton(onClick = { controller.color.value = Color.Green }) { Text("Green") } - TextButton(onClick = { controller.color = Color.Yellow }) { + TextButton(onClick = { controller.color.value = Color.Yellow }) { Text("Yellow") } } @@ -82,8 +88,8 @@ fun main() = application { Column(modifier = Modifier.weight(2f, false)) { Text("Background opacity") Slider( - value = (controller.background as? DrawBoxBackground.ColourBackground)?.alpha ?: 0f, - onValueChange = { controller.background = DrawBoxBackground.ColourBackground(color = Color.Blue, alpha = it) }, + value = (background as? DrawBoxBackground.ColourBackground)?.alpha ?: 0f, + onValueChange = { controller.background.value = DrawBoxBackground.ColourBackground(color = Color.Blue, alpha = it) }, valueRange = 0f..1f ) }