11package io.github.markyav.drawbox.controller
22
33import androidx.compose.runtime.*
4- import androidx.compose.runtime.snapshots.SnapshotStateList
54import androidx.compose.ui.geometry.Offset
65import androidx.compose.ui.graphics.*
76import androidx.compose.ui.unit.IntSize
87import io.github.markyav.drawbox.model.PathWrapper
8+ import io.github.markyav.drawbox.util.addNotNull
9+ import io.github.markyav.drawbox.util.combineStates
910import io.github.markyav.drawbox.util.createPath
10- import io.github.markyav.drawbox.util.pop
11+ import io.github.markyav.drawbox.util.mapState
1112import kotlinx.coroutines.CoroutineScope
13+ import kotlinx.coroutines.Dispatchers
14+ import kotlinx.coroutines.coroutineScope
1215import kotlinx.coroutines.flow.*
16+ import kotlinx.coroutines.launch
17+ import kotlin.reflect.KProperty
1318
1419/* *
1520 * DrawController interacts with [DrawBox] and it allows you to control the canvas and all the components with it.
1621 */
1722class DrawController {
18- private var state: DrawBoxConnectionState by mutableStateOf (DrawBoxConnectionState .Disconnected )
23+ private var state: MutableStateFlow < DrawBoxConnectionState > = MutableStateFlow (DrawBoxConnectionState .Disconnected )
1924
2025 /* * A stateful list of [Path] that is drawn on the [Canvas]. */
21- private val drawnPaths: SnapshotStateList < PathWrapper > = mutableStateListOf( )
26+ private val drawnPaths: MutableStateFlow < List < PathWrapper >> = MutableStateFlow (emptyList() )
2227
23- private val completelyDrawnPaths: SnapshotStateList <PathWrapper > = mutableStateListOf()
24-
25- /* * A stateful list of [Path] that is drawn on the [Canvas] and is scaled for the connected bitmap. */
26- internal val pathToDrawOnCanvas: List <PathWrapper >? by derivedStateOf {
27- (state as ? DrawBoxConnectionState .Connected )?.let {
28- drawnPaths.scale(it.size.toFloat())
29- }
30- }
28+ private val activeDrawingPath: MutableStateFlow <List <Offset >? > = MutableStateFlow (null )
3129
3230 /* * A stateful list of [Path] that was drawn on the [Canvas] but user retracted his action. */
33- private val canceledPaths: SnapshotStateList < PathWrapper > = mutableStateListOf( )
31+ private val canceledPaths: MutableStateFlow < List < PathWrapper >> = MutableStateFlow (emptyList() )
3432
3533 /* * An [canvasOpacity] of the [Canvas] in the [DrawBox] */
36- var canvasOpacity: Float by mutableStateOf (1f )
34+ var canvasOpacity: MutableStateFlow < Float > = MutableStateFlow (1f )
3735
3836 /* * An [opacity] of the stroke */
39- var opacity: Float by mutableStateOf (1f )
37+ var opacity: MutableStateFlow < Float > = MutableStateFlow (1f )
4038
4139 /* * A [strokeWidth] of the stroke */
42- var strokeWidth: Float by mutableStateOf (10f )
40+ var strokeWidth: MutableStateFlow < Float > = MutableStateFlow (10f )
4341
4442 /* * A [color] of the stroke */
45- var color: Color by mutableStateOf (Color .Red )
43+ var color: MutableStateFlow < Color > = MutableStateFlow (Color .Red )
4644
4745 /* * A [background] of the background of DrawBox */
48- var background: DrawBoxBackground by mutableStateOf (DrawBoxBackground .NoBackground )
46+ var background: MutableStateFlow < DrawBoxBackground > = MutableStateFlow (DrawBoxBackground .NoBackground )
4947
5048 /* * Indicate how many redos it is possible to do. */
51- val undoCount: Int by derivedStateOf { drawnPaths .size }
49+ val undoCount = drawnPaths.mapState { it .size }
5250
5351 /* * Indicate how many undos it is possible to do. */
54- val redoCount: Int by derivedStateOf { canceledPaths .size }
52+ val redoCount = canceledPaths.mapState { it .size }
5553
5654 /* * Executes undo the drawn path if possible. */
5755 fun undo () {
58- if (drawnPaths.isNotEmpty()) {
59- canceledPaths.add(drawnPaths.pop())
60- finalizePath()
56+ if (drawnPaths.value.isNotEmpty()) {
57+ val _drawnPaths = drawnPaths.value.toMutableList()
58+ val _canceledPaths = canceledPaths.value.toMutableList()
59+
60+ _canceledPaths .add(_drawnPaths .removeLast())
61+
62+ drawnPaths.value = _drawnPaths
63+ canceledPaths.value = _canceledPaths
6164 }
6265 }
6366
6467 /* * Executes redo the drawn path if possible. */
6568 fun redo () {
66- if (canceledPaths.isNotEmpty()) {
67- drawnPaths.add(canceledPaths.pop())
68- finalizePath()
69+ if (canceledPaths.value.isNotEmpty()) {
70+ val _drawnPaths = drawnPaths.value.toMutableList()
71+ val _canceledPaths = canceledPaths.value.toMutableList()
72+
73+ _drawnPaths .add(_canceledPaths .removeLast())
74+
75+ drawnPaths.value = _drawnPaths
76+ canceledPaths.value = _canceledPaths
6977 }
7078 }
7179
7280 /* * Clear drawn paths and the bitmap image. */
7381 fun reset () {
74- drawnPaths.clear()
75- canceledPaths.clear()
76- completelyDrawnPaths.clear()
82+ drawnPaths.value = emptyList()
83+ canceledPaths.value = emptyList()
7784 }
7885
7986 /* * Call this function when user starts drawing a path. */
8087 internal fun updateLatestPath (newPoint : Offset ) {
81- (state as ? DrawBoxConnectionState .Connected )?.let {
82- drawnPaths.last().points.add(newPoint.div(it.size.toFloat()))
88+ (state.value as ? DrawBoxConnectionState .Connected )?.let {
89+ require(activeDrawingPath.value != null )
90+ val list = activeDrawingPath.value!! .toMutableList()
91+ list.add(newPoint.div(it.size.toFloat()))
92+ activeDrawingPath.value = list
8393 }
8494 }
8595
8696 /* * When dragging call this function to update the last path. */
8797 internal fun insertNewPath (newPoint : Offset ) {
88- (state as ? DrawBoxConnectionState .Connected )?.let {
89- val pathWrapper = PathWrapper (
98+ (state.value as ? DrawBoxConnectionState .Connected )?.let {
99+ require(activeDrawingPath.value == null )
100+ /* val pathWrapper = PathWrapper(
90101 points = mutableStateListOf(newPoint.div(it.size.toFloat())),
91- strokeColor = color,
92- alpha = opacity,
93- strokeWidth = strokeWidth.div(it.size.toFloat()),
94- )
95- drawnPaths.add(pathWrapper )
96- canceledPaths.clear ()
102+ strokeColor = color.value ,
103+ alpha = opacity.value ,
104+ strokeWidth = strokeWidth.value. div(it.size.toFloat()),
105+ )*/
106+ activeDrawingPath.value = listOf (newPoint.div(it.size.toFloat()) )
107+ canceledPaths.value = emptyList ()
97108 }
98109 }
99110
100111 internal fun finalizePath () {
101- completelyDrawnPaths.clear()
102- completelyDrawnPaths.addAll(drawnPaths)
112+ (state.value as ? DrawBoxConnectionState .Connected )?.let {
113+ require(activeDrawingPath.value != null )
114+ val _drawnPaths = drawnPaths.value.toMutableList()
115+
116+ val pathWrapper = PathWrapper (
117+ points = activeDrawingPath.value!! ,
118+ strokeColor = color.value,
119+ alpha = opacity.value,
120+ strokeWidth = strokeWidth.value.div(it.size.toFloat()),
121+ )
122+ _drawnPaths .add(pathWrapper)
123+
124+ drawnPaths.value = _drawnPaths
125+ activeDrawingPath.value = null
126+ }
103127 }
104128
105129 /* * Call this function to connect to the [DrawBox]. */
106130 internal fun connectToDrawBox (size : IntSize ) {
107131 if (
108132 size.width > 0 &&
109133 size.height > 0 &&
110- size.width == size.height // &&
111- // state is DrawBoxConnectionState.Disconnected
134+ size.width == size.height
112135 ) {
113- state = DrawBoxConnectionState .Connected (size = size.width)
136+ state.value = DrawBoxConnectionState .Connected (size = size.width)
114137 }
115138 }
116139
140+ internal fun onTap (newPoint : Offset ) {
141+ insertNewPath(newPoint)
142+ finalizePath()
143+ }
144+
117145 private fun List<PathWrapper>.scale (size : Float ): List <PathWrapper > {
118146 return this .map { pw ->
119147 val t = pw.points.map { it.times(size) }
120148 pw.copy(
121- points = SnapshotStateList <Offset >().also { it.addAll(t) },
149+ points = mutableListOf <Offset >().also { it.addAll(t) },
122150 strokeWidth = pw.strokeWidth * size
123151 )
124152 }
125153 }
126154
127- fun getBitmap (size : Int , coroutineScope : CoroutineScope , subscription : DrawBoxSubscription ): StateFlow <ImageBitmap > {
155+ fun getDrawPath (subscription : DrawBoxSubscription ): StateFlow <List <PathWrapper >> {
156+ return when (subscription) {
157+ is DrawBoxSubscription .DynamicUpdate -> getDynamicUpdateDrawnPath()
158+ is DrawBoxSubscription .FinishDrawingUpdate -> drawnPaths
159+ }
160+ }
161+
162+ private fun getDynamicUpdateDrawnPath (): StateFlow <List <PathWrapper >> {
163+ return combineStates(drawnPaths, activeDrawingPath) { a, b ->
164+ val _a = a.toMutableList()
165+ (state.value as ? DrawBoxConnectionState .Connected )?.let {
166+ val pathWrapper = PathWrapper (
167+ points = activeDrawingPath.value ? : emptyList(),
168+ strokeColor = color.value,
169+ alpha = opacity.value,
170+ strokeWidth = strokeWidth.value.div(it.size.toFloat()),
171+ )
172+ _a .addNotNull(pathWrapper)
173+ }
174+ _a
175+ }
176+ }
177+
178+ internal fun getPathWrappersForDrawbox (subscription : DrawBoxSubscription ): StateFlow <List <PathWrapper >> {
179+ return combineStates(getDrawPath(subscription), state) { paths, st ->
180+ val size = (st as ? DrawBoxConnectionState .Connected )?.size ? : 1
181+ paths.scale(size.toFloat())
182+ }
183+ }
184+
185+ fun getBitmap (size : Int , subscription : DrawBoxSubscription ): StateFlow <ImageBitmap > {
128186 val initialBitmap = ImageBitmap (size, size, ImageBitmapConfig .Argb8888 )
129- return flow {
187+ val path = getDrawPath(subscription)
188+ return path.mapState {
130189 val bitmap = ImageBitmap (size, size, ImageBitmapConfig .Argb8888 )
131190 val canvas = Canvas (bitmap)
132- val path = when (subscription) {
133- is DrawBoxSubscription .DynamicUpdate -> drawnPaths
134- is DrawBoxSubscription .FinishDrawingUpdate -> completelyDrawnPaths
135- }
136- path.scale(size.toFloat()).forEach { pw ->
191+ it.scale(size.toFloat()).forEach { pw ->
137192 canvas.drawPath(
138193 createPath(pw.points),
139194 paint = Paint ().apply {
@@ -146,7 +201,7 @@ class DrawController {
146201 }
147202 )
148203 }
149- emit( bitmap)
150- }.stateIn(coroutineScope, started = SharingStarted . Eagerly , initialValue = initialBitmap)
204+ bitmap
205+ }
151206 }
152207}
0 commit comments