Skip to content

Commit 83f5968

Browse files
committed
Improve canvas sizing mode, fix preview A/R
This commit changes the canvas sizing mode; it is now going to either fill available space, or have a fixed size, on both directions. This makes it behave more intuitively, and allows us to properly respect the canvas aspect ratio in the preview. This is reflected in the UI. That change also includes a bunch of cleanup (including addressing Lint complaints) of the project code. It also includes a change to how points are drawn in the gradient, to avoid the points being squashed for non-square canvases. Lastly, it adds an explicit output image size in the export scale selector, allowing a better understanding of what each option means.
1 parent 4b2cf72 commit 83f5968

File tree

7 files changed

+185
-163
lines changed

7 files changed

+185
-163
lines changed

composeApp/src/commonMain/kotlin/des/c5inco/mesh/common/MeshGradient.kt

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import androidx.compose.ui.graphics.lerp
2424
@Composable
2525
fun Modifier.meshGradient(
2626
points: List<List<Pair<Offset, Color>>>,
27-
blendMode: BlendMode = BlendMode.DstIn,
27+
gradientBlendMode: BlendMode = BlendMode.DstIn,
2828
resolutionX: Int = 1,
2929
resolutionY: Int = 1,
3030
showPoints: Boolean = false,
@@ -36,6 +36,15 @@ fun Modifier.meshGradient(
3636
}
3737
}
3838

39+
val pointsPaint = remember {
40+
Paint().apply {
41+
color = Color.White.copy(alpha = .9f)
42+
strokeWidth = 4f
43+
strokeCap = StrokeCap.Round
44+
blendMode = BlendMode.SrcOver
45+
}
46+
}
47+
3948
return drawWithCache {
4049
onDrawBehind {
4150
drawIntoCanvas { canvas ->
@@ -52,29 +61,20 @@ fun Modifier.meshGradient(
5261
colors = pointData.colors,
5362
indices = indicesModifier(pointData.indices)
5463
),
55-
blendMode = blendMode,
64+
blendMode = gradientBlendMode,
5665
paint = paint,
5766
)
5867
}
5968

6069
if (showPoints) {
61-
val flattenedPaint = Paint()
62-
flattenedPaint.color = Color.White.copy(alpha = .9f)
63-
flattenedPaint.strokeWidth = 4f * .001f
64-
flattenedPaint.strokeCap = StrokeCap.Round
65-
flattenedPaint.blendMode = BlendMode.SrcOver
66-
67-
scale(
68-
scaleX = size.width,
69-
scaleY = size.height,
70-
pivot = Offset.Zero
71-
) {
72-
canvas.drawPoints(
73-
pointMode = PointMode.Points,
74-
points = pointData.offsets,
75-
paint = flattenedPaint
76-
)
77-
}
70+
val intermediatePoints = pointData.offsets
71+
.map { Offset(it.x * size.width, it.y * size.height) }
72+
73+
canvas.drawPoints(
74+
pointMode = PointMode.Points,
75+
points = intermediatePoints,
76+
paint = pointsPaint
77+
)
7878
}
7979
}
8080
}
@@ -285,4 +285,4 @@ private fun cubicPathY(point1: Offset, point2: Offset, position: Int): Path {
285285
return path
286286
}
287287

288-
private val paint = Paint()
288+
private val paint = Paint()

composeApp/src/desktopMain/kotlin/des/c5inco/mesh/App.kt

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@ package des.c5inco.mesh
33
import androidx.compose.foundation.layout.Row
44
import androidx.compose.foundation.layout.fillMaxSize
55
import androidx.compose.foundation.layout.width
6-
import androidx.compose.runtime.*
6+
import androidx.compose.runtime.Composable
7+
import androidx.compose.runtime.getValue
8+
import androidx.compose.runtime.mutableStateOf
9+
import androidx.compose.runtime.remember
10+
import androidx.compose.runtime.rememberCoroutineScope
11+
import androidx.compose.runtime.setValue
712
import androidx.compose.ui.Modifier
813
import androidx.compose.ui.graphics.rememberGraphicsLayer
914
import androidx.compose.ui.graphics.toAwtImage
@@ -26,12 +31,11 @@ fun App() {
2631
val coroutineScope = rememberCoroutineScope()
2732

2833
GradientCanvas(
29-
exportGraphicsLayer = exportGraphicsLayer,
30-
exportScale = exportScale,
31-
onPointDrag = { selectedColorPoint = it },
32-
modifier = Modifier.weight(1f)
33-
)
34-
SidePanel(
34+
exportGraphicsLayer = exportGraphicsLayer,
35+
exportScale = exportScale,
36+
modifier = Modifier.weight(1f)
37+
) { selectedColorPoint = it }
38+
SidePanel(
3539
exportScale = exportScale,
3640
onExportScaleChange = { exportScale = it },
3741
onExport = {
@@ -47,4 +51,4 @@ fun App() {
4751
modifier = Modifier.width(280.dp)
4852
)
4953
}
50-
}
54+
}

composeApp/src/desktopMain/kotlin/des/c5inco/mesh/ui/GradientCanvas.kt

Lines changed: 30 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,15 @@ import androidx.compose.foundation.gestures.detectTapGestures
66
import androidx.compose.foundation.layout.Box
77
import androidx.compose.foundation.layout.BoxWithConstraints
88
import androidx.compose.foundation.layout.Spacer
9-
import androidx.compose.foundation.layout.fillMaxHeight
9+
import androidx.compose.foundation.layout.aspectRatio
1010
import androidx.compose.foundation.layout.fillMaxSize
11-
import androidx.compose.foundation.layout.fillMaxWidth
12-
import androidx.compose.foundation.layout.height
1311
import androidx.compose.foundation.layout.padding
14-
import androidx.compose.foundation.layout.width
1512
import androidx.compose.foundation.shape.RoundedCornerShape
1613
import androidx.compose.runtime.Composable
1714
import androidx.compose.runtime.LaunchedEffect
18-
import androidx.compose.runtime.collectAsState
1915
import androidx.compose.runtime.derivedStateOf
2016
import androidx.compose.runtime.getValue
2117
import androidx.compose.runtime.mutableStateListOf
22-
import androidx.compose.runtime.mutableStateOf
2318
import androidx.compose.runtime.remember
2419
import androidx.compose.ui.Alignment
2520
import androidx.compose.ui.Modifier
@@ -54,24 +49,19 @@ import org.jetbrains.jewel.ui.util.thenIf
5449
fun GradientCanvas(
5550
exportGraphicsLayer: GraphicsLayer,
5651
exportScale: Int,
52+
modifier: Modifier = Modifier,
5753
onPointDrag: (Pair<Int, Int>?) -> Unit = { _ -> },
58-
modifier: Modifier = Modifier
5954
) {
6055
val showPoints by remember { AppState::showPoints }
6156
val resolution by remember { AppState::resolution }
6257
val colors = remember { AppState.colorPoints }
6358

64-
val canvasWidthMode by AppState.canvasWidthMode.collectAsState()
65-
val canvasHeightMode by AppState.canvasHeightMode.collectAsState()
59+
val canvasSizeMode = AppState.canvasSizeMode
6660
var canvasWidth by remember { AppState::canvasWidth }
6761
var canvasHeight by remember { AppState::canvasHeight }
6862

6963
val notifications = remember { mutableStateListOf<String>() }
7064

71-
val exportSize by derivedStateOf {
72-
mutableStateOf(IntSize(canvasWidth, canvasHeight))
73-
}
74-
7565
val density = LocalDensity.current
7666

7767
LaunchedEffect(Unit) {
@@ -83,7 +73,7 @@ fun GradientCanvas(
8373
}
8474

8575
fun handlePositioned(coordinates: LayoutCoordinates) {
86-
with (density) {
76+
with(density) {
8777
val dpWidth = coordinates.size.width.toDp()
8878
val dpHeight = coordinates.size.height.toDp()
8979

@@ -95,13 +85,17 @@ fun GradientCanvas(
9585
Box(
9686
contentAlignment = Alignment.Center,
9787
modifier = modifier
98-
.background(if (AppState.canvasBackgroundColor > -1) {
99-
AppState.getColor(AppState.canvasBackgroundColor)
100-
} else {
101-
JewelTheme.colorPalette.gray(1)
102-
})
88+
.background(
89+
if (AppState.canvasBackgroundColor > -1) {
90+
AppState.getColor(AppState.canvasBackgroundColor)
91+
} else {
92+
JewelTheme.colorPalette.gray(1)
93+
}
94+
)
10395
.fillMaxSize()
10496
) {
97+
val canvasAspectRatio by remember { derivedStateOf { canvasWidth.toFloat() / canvasHeight } }
98+
10599
BoxWithConstraints(
106100
modifier = Modifier
107101
.pointerInput(Unit) {
@@ -112,20 +106,12 @@ fun GradientCanvas(
112106
)
113107
}
114108
.padding(32.dp)
115-
.then(
116-
if (canvasWidthMode == DimensionMode.Fill) {
117-
Modifier.fillMaxWidth()
118-
} else {
119-
Modifier.width(canvasWidth.dp)
120-
}
121-
)
122-
.then(
123-
if (canvasHeightMode == DimensionMode.Fill) {
124-
Modifier.fillMaxHeight()
125-
} else {
126-
Modifier.height(canvasHeight.dp)
127-
}
128-
)
109+
.thenIf(canvasSizeMode == DimensionMode.Fixed) {
110+
aspectRatio(canvasAspectRatio)
111+
}
112+
.thenIf(canvasSizeMode == DimensionMode.Fill) {
113+
fillMaxSize()
114+
}
129115
) {
130116
val maxWidth = constraints.maxWidth
131117
val maxHeight = constraints.maxHeight
@@ -149,32 +135,32 @@ fun GradientCanvas(
149135

150136
Box(
151137
Modifier
152-
.thenIf(canvasWidthMode == DimensionMode.Fill || canvasHeightMode == DimensionMode.Fill) {
153-
Modifier.onGloballyPositioned { handlePositioned(it) }
138+
.thenIf(canvasSizeMode == DimensionMode.Fill) {
139+
onGloballyPositioned { handlePositioned(it) }
154140
}
155141
.clip(RoundedCornerShape(16.dp))
156142
.drawWithContent {
157-
// Record content on visible graphics layer
143+
// Record content on a visible graphics layer
158144
graphicsLayer.record {
159145
this@drawWithContent.drawContent()
160146
}
161147

162-
val (width, height) = exportSize.value
163-
164148
// Scale and translate the export graphics layer accordingly
165149
exportGraphicsLayer.apply {
166150
scaleX = exportScale.toFloat()
167151
scaleY = exportScale.toFloat()
168152

169153
when (exportScale) {
170154
3 -> {
171-
translationX = width.toFloat() * 3
172-
translationY = height.toFloat() * 3
155+
translationX = canvasWidth.toFloat() * 3
156+
translationY = canvasHeight.toFloat() * 3
173157
}
158+
174159
2 -> {
175-
translationX = width.toFloat()
176-
translationY = height.toFloat()
160+
translationX = canvasWidth.toFloat()
161+
translationY = canvasHeight.toFloat()
177162
}
163+
178164
else -> {
179165
translationX = 0f
180166
translationY = 0f
@@ -184,7 +170,7 @@ fun GradientCanvas(
184170

185171
// Record content on the export graphics layer
186172
exportGraphicsLayer.record(
187-
size = IntSize(width * exportScale, height * exportScale),
173+
size = IntSize(canvasWidth * exportScale, canvasHeight * exportScale),
188174
) {
189175
scale(
190176
scale = 1f / density.density,
@@ -287,4 +273,4 @@ fun GradientCanvas(
287273
}
288274
}
289275
}
290-
}
276+
}

0 commit comments

Comments
 (0)