Skip to content

Improve canvas sizing mode, fix preview A/R #2

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
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
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import androidx.compose.ui.graphics.lerp
@Composable
fun Modifier.meshGradient(
points: List<List<Pair<Offset, Color>>>,
blendMode: BlendMode = BlendMode.DstIn,
gradientBlendMode: BlendMode = BlendMode.DstIn,
resolutionX: Int = 1,
resolutionY: Int = 1,
showPoints: Boolean = false,
Expand All @@ -36,6 +36,15 @@ fun Modifier.meshGradient(
}
}

val pointsPaint = remember {
Paint().apply {
color = Color.White.copy(alpha = .9f)
strokeWidth = 3f
strokeCap = StrokeCap.Round
blendMode = BlendMode.SrcOver
}
}

return drawWithCache {
onDrawBehind {
drawIntoCanvas { canvas ->
Expand All @@ -52,29 +61,20 @@ fun Modifier.meshGradient(
colors = pointData.colors,
indices = indicesModifier(pointData.indices)
),
blendMode = blendMode,
blendMode = gradientBlendMode,
paint = paint,
)
}

if (showPoints) {
val flattenedPaint = Paint()
flattenedPaint.color = Color.White.copy(alpha = .9f)
flattenedPaint.strokeWidth = 4f * .001f
flattenedPaint.strokeCap = StrokeCap.Round
flattenedPaint.blendMode = BlendMode.SrcOver

scale(
scaleX = size.width,
scaleY = size.height,
pivot = Offset.Zero
) {
canvas.drawPoints(
pointMode = PointMode.Points,
points = pointData.offsets,
paint = flattenedPaint
)
}
val intermediatePoints = pointData.offsets
.map { Offset(it.x * size.width, it.y * size.height) }

canvas.drawPoints(
pointMode = PointMode.Points,
points = intermediatePoints,
paint = pointsPaint
)
}
}
}
Expand Down Expand Up @@ -285,4 +285,4 @@ private fun cubicPathY(point1: Offset, point2: Offset, position: Int): Path {
return path
}

private val paint = Paint()
private val paint = Paint()
13 changes: 6 additions & 7 deletions composeApp/src/desktopMain/kotlin/des/c5inco/mesh/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,11 @@ fun App() {
val coroutineScope = rememberCoroutineScope()

GradientCanvas(
exportGraphicsLayer = exportGraphicsLayer,
exportScale = exportScale,
onPointDrag = { selectedColorPoint = it },
modifier = Modifier.weight(1f)
)
SidePanel(
exportGraphicsLayer = exportGraphicsLayer,
exportScale = exportScale,
modifier = Modifier.weight(1f)
) { selectedColorPoint = it }
SidePanel(
exportScale = exportScale,
onExportScaleChange = { exportScale = it },
onExport = {
Expand All @@ -51,4 +50,4 @@ fun App() {
modifier = Modifier.width(280.dp)
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,19 @@ package des.c5inco.mesh.ui
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.detectDragGestures
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
Expand Down Expand Up @@ -39,24 +49,19 @@ import org.jetbrains.jewel.ui.util.thenIf
fun GradientCanvas(
exportGraphicsLayer: GraphicsLayer,
exportScale: Int,
modifier: Modifier = Modifier,
onPointDrag: (Pair<Int, Int>?) -> Unit = { _ -> },
modifier: Modifier = Modifier
) {
val showPoints by remember { AppState::showPoints }
val resolution by remember { AppState::resolution }
val colors = remember { AppState.colorPoints }

val canvasWidthMode by AppState.canvasWidthMode.collectAsState()
val canvasHeightMode by AppState.canvasHeightMode.collectAsState()
val canvasSizeMode = AppState.canvasSizeMode
var canvasWidth by remember { AppState::canvasWidth }
var canvasHeight by remember { AppState::canvasHeight }

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

val exportSize by derivedStateOf {
mutableStateOf(IntSize(canvasWidth, canvasHeight))
}

val density = LocalDensity.current

LaunchedEffect(Unit) {
Expand All @@ -68,7 +73,7 @@ fun GradientCanvas(
}

fun handlePositioned(coordinates: LayoutCoordinates) {
with (density) {
with(density) {
val dpWidth = coordinates.size.width.toDp()
val dpHeight = coordinates.size.height.toDp()

Expand All @@ -80,13 +85,17 @@ fun GradientCanvas(
Box(
contentAlignment = Alignment.Center,
modifier = modifier
.background(if (AppState.canvasBackgroundColor > -1) {
AppState.getColor(AppState.canvasBackgroundColor)
} else {
JewelTheme.colorPalette.gray(1)
})
.background(
if (AppState.canvasBackgroundColor > -1) {
AppState.getColor(AppState.canvasBackgroundColor)
} else {
JewelTheme.colorPalette.gray(1)
}
)
.fillMaxSize()
) {
val canvasAspectRatio by remember { derivedStateOf { canvasWidth.toFloat() / canvasHeight } }

BoxWithConstraints(
modifier = Modifier
.pointerInput(Unit) {
Expand All @@ -97,20 +106,12 @@ fun GradientCanvas(
)
}
.padding(32.dp)
.then(
if (canvasWidthMode == DimensionMode.Fill) {
Modifier.fillMaxWidth()
} else {
Modifier.width(canvasWidth.dp)
}
)
.then(
if (canvasHeightMode == DimensionMode.Fill) {
Modifier.fillMaxHeight()
} else {
Modifier.height(canvasHeight.dp)
}
)
.thenIf(canvasSizeMode == DimensionMode.Fixed) {
aspectRatio(canvasAspectRatio)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While this maintains aspect ratio, doesn't behave as expected when setting the size to be smaller than the available space. It also seems to break the double click action to hide/show the points.

It works as expected if setting the size Modifier directly (with the canvasWidth and canvasHeight respectively).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doesn't behave as expected when setting the size to be smaller than the available space

It works as I expected it to :D That is, take up as much space as possible for the preview. This solves issues with having to implement zoom levels, too, for cases where the canvas is too small to be worked on. If you set the size directly, then you need to implement panning and zooming for when the canvas is too small or large, I reckon, and I didn't want to have to do it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Forgot:

It also seems to break the double click action to hide/show the points

Works for me — at least when double clicking on the preview canvas. Not elsewhere (but that was already the case before, if you made the canvas non-fill, and small enough to fit in the viewport)

Screen.Recording.2025-03-07.at.16.37.59.mov

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This solves issues with having to implement zoom levels, too, for cases where the canvas is too small to be worked on. If you set the size directly, then you need to implement panning and zooming for when the canvas is too small or large, I reckon, and I didn't want to have to do it

Gotcha. I would prefer to reflect the size at 100% even if it's too small to work with in some situations. Currently I find it confusing when I set it to a smaller size than the available space and visually feels to be roughly the same size. Maybe if we had a label that expressed what zoom level it is at, it would be clearer.

Screen.Recording.2025-03-07.at.21.02.56.mov

To that end, I think we will need zoom levels at some point so I would prefer to fix this sizing issue then.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Forgot:

It also seems to break the double click action to hide/show the points

Works for me — at least when double clicking on the preview canvas. Not elsewhere (but that was already the case before, if you made the canvas non-fill, and small enough to fit in the viewport)

Screen.Recording.2025-03-07.at.16.37.59.mov

Yeah it seems to be fine now. Not sure what was happening when I saw it not working.

}
.thenIf(canvasSizeMode == DimensionMode.Fill) {
fillMaxSize()
}
) {
val maxWidth = constraints.maxWidth
val maxHeight = constraints.maxHeight
Expand All @@ -134,32 +135,32 @@ fun GradientCanvas(

Box(
Modifier
.thenIf(canvasWidthMode == DimensionMode.Fill || canvasHeightMode == DimensionMode.Fill) {
Modifier.onGloballyPositioned { handlePositioned(it) }
.thenIf(canvasSizeMode == DimensionMode.Fill) {
onGloballyPositioned { handlePositioned(it) }
}
.clip(RoundedCornerShape(16.dp))
.drawWithContent {
// Record content on visible graphics layer
// Record content on a visible graphics layer
graphicsLayer.record {
[email protected]()
}

val (width, height) = exportSize.value

// Scale and translate the export graphics layer accordingly
exportGraphicsLayer.apply {
scaleX = exportScale.toFloat()
scaleY = exportScale.toFloat()

when (exportScale) {
3 -> {
translationX = width.toFloat() * 3
translationY = height.toFloat() * 3
translationX = canvasWidth.toFloat() * 3
translationY = canvasHeight.toFloat() * 3
}

2 -> {
translationX = width.toFloat()
translationY = height.toFloat()
translationX = canvasWidth.toFloat()
translationY = canvasHeight.toFloat()
}

else -> {
translationX = 0f
translationY = 0f
Expand All @@ -169,7 +170,7 @@ fun GradientCanvas(

// Record content on the export graphics layer
exportGraphicsLayer.record(
size = IntSize(width * exportScale, height * exportScale),
size = IntSize(canvasWidth * exportScale, canvasHeight * exportScale),
) {
scale(
scale = 1f / density.density,
Expand Down Expand Up @@ -272,4 +273,4 @@ fun GradientCanvas(
}
}
}
}
}
Loading