Jetpack Compose offset image vector in Canvas - kotlin

I have a problem with vector image in Canvas. As shown below I can just call vector image but I can’t make any offset in Canvas. So I only can have it the way it is.
I don't know the reason why there is no Offset option like in drawCircle or drawRect, if someone has some ideas it would be great.
val vector = ImageVector.vectorResource(id = R.drawable.ic_test)
val painter = rememberVectorPainter(image = vector)
Box(contentAlignment = Alignment.Center) {
Canvas(
modifier = Modifier
.fillMaxWidth()
.padding(10.dp)
) {
with(painter) {
draw(
painter.intrinsicSize
)
}
}
}
I tried something like adding Offset into with(painter) but nothing changes:
with(painter) {
draw(
painter.intrinsicSize
)
Offset(x = 10f, y = 10f)
}

You can use DrawScope.translate:
translate(left = 10f, top = 10f) {
with(painter) {
draw(
painter.intrinsicSize
)
}
}

Related

Compose onValueChange behaviour isn't consistent

I'm making a sudoku game and solver. For my user interface I used LazyVerticalGrid to create a 9x9 grid. I successfully made it so when you click on a cell it will only accept digits [1-9] via an OutLinedTextField. I then added a conditional that only empty cells would have the text field applied. That worked and only those cells could be altered but when I do that the logic that only accepts digits doesn't work and the program crashes. If I comment out the conditional statement and the OutLinedTextField is applied to all cells it works again. I get the following error.
Also if I add conditionals for backgroundColor or Content Color the same thing happens and the program crashes if a non digit is pressed. I'm not sure why the conditionals affect the onValueChange logic. Why is this and how do I fix it?
fun displayPuzzle(answer: Array<Array<IntArray>>) {
var list: SnapshotStateList<String> = mutableStateListOf()
for (x in answer[0]) list.addAll(x.map { it.toString() })
var columnHeighty by remember { mutableStateOf(0F) }
var columnWidthx by remember { mutableStateOf(0f) }
var pad = 20.dp
LazyVerticalGrid(
columns = GridCells.Fixed(9),
contentPadding = PaddingValues(
start = pad,
top = pad,
end = pad,
bottom = pad
)
) {
items(list.size) { index ->
Card(
shape = RectangleShape,
backgroundColor = Color.Red,
modifier = Modifier
.requiredWidth(83.dp)
.fillMaxWidth()
.fillMaxHeight()
.onGloballyPositioned { coordinates ->
columnWidthx = coordinates.size.width.toFloat()
columnHeighty = coordinates.size.height.toFloat()
},
//backgroundColor = if (list[index].toInt() == 0) Color.Yellow else Color.White ,
//contentColor = if (list[index].toInt() == 0) Color.Blue else Color.Black ,
border = BorderStroke(width = 1.dp, color = Color.Black)
) {
Text(
text = list[index],
fontWeight = FontWeight.Bold,
fontSize = 30.sp,
color = Color(0xFF000000),
textAlign = TextAlign.Center,
modifier = Modifier
.padding(23.dp)
.clickable { }
)
}
// When the if statement is included the program crashes on a non digit entry
//if (list[index].toInt() == 0) {
val pattern = remember { Regex("[1-9]") }
var value by remember { mutableStateOf("") }
OutlinedTextField(
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
colors = TextFieldDefaults.outlinedTextFieldColors(cursorColor = Color.Transparent),
textStyle = TextStyle(color = Color.Red),
modifier = Modifier
.fillMaxHeight()
.padding(vertical = 10.dp, horizontal = 10.dp),
value = value,
onValueChange = { if (it.isEmpty() || (it.matches(pattern) && (it.length == 1)))
value = it
list[index] = value}
)
//}
}
}
Your game crashed because you trying to convert for example 'a' to Int value and runtime throws NumberFormatException.
You need to use:
if (list[index].toIntOrNull() == null)
This condition will be triggered if a non-decimical number is obtained from your SnapshotStateList
Explanation: toIntOrNull() returns Int from String (example: "4".toIntOrNull() - returns 4) otherwise it returns null

How can i make this animation stop after completion in Jetpack Compose or not be infinite

#Composable
fun Gojo(
maxWidth:Dp,
maxHeight:Dp,)
{
val resource: Painter
val modifier: Modifier
val gojoSize = 200.dp
val infiniteTransition = rememberInfiniteTransition()
val posistionState = infiniteTransition.animateFloat(
initialValue = 0f,
targetValue = 1f,
animationSpec = infiniteRepeatable(
animation = tween(
durationMillis = 1000,
easing = LinearEasing
),
repeatMode=RepeatMode.Reverse
)
)
if(posistionState.value<=0.1f) {
modifier = Modifier.offset(
x = -140.dp,
y = maxHeight * 0.75f,
).rotate(50.0F)
}
else if(posistionState.value<=0.2f){
modifier = Modifier.offset(
x = -100.dp,
y = maxHeight * 0.75f,
).rotate(50.0F)
}
else if(posistionState.value<=0.25f){
modifier = Modifier.offset(
x = -100.dp,
y = maxHeight * 0.75f,
).rotate(0.0F)
}
else if(posistionState.value<=0.3f){
modifier = Modifier.offset(
x = -100.dp+(maxWidth*posistionState.value),
y = maxHeight * 0.75f,
).rotate(50.0F)
}
else{
modifier = Modifier.offset(
x = maxWidth * 0.25f,
y = maxHeight * 0.75f,
)
}
resource=painterResource(R.drawable.gojo)
Image(modifier=modifier.width(gojoSize).height(gojoSize),painter = resource, contentDescription ="gojo sama")
}
you are asking for not to be infinite and using infinite transition? just use animateDpAsState and change size of the image by using the dp values. If you want to start the transition when the item has composed just call a LaunchedEffect (true) {startTransition = true} by remembering the startTransition value as false in the beginning:
var startTransition by remember {
mutableStateOf(false)
}
LaunchedEffect(true){
startTransition = true
}
val size = animateDpAsState(
targetValue = if(startTransition == false)
0.dp
else
/*Your Desired Value*/
)
// And use size in Image. You can create different animateDpAsState values for width and height.

How to monitor mouse point move entered a path in Jetpack Compose?

I got a problem on how to detect mouse pointer entered a path or reach in Jetpack Compose canvas, what I want is something like isPointInPath()in JavaScript: when my mouse moved into the react area in canvas, I want to change the react color into green.
My current code is blow, I wanted to make the rectangle change color like the white dot on the canvas in the code, I searched on google android doc, but I got nothing, any thing helpful will be appriciated:
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.forEachGesture
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.*
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.*
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.input.pointer.PointerEventType
import androidx.compose.ui.input.pointer.onPointerEvent
import androidx.compose.ui.input.pointer.pointerInput
data class PathProperties(val Angle: Float, val length: Float, val startPoint: Pair<Float, Float>)
#OptIn(ExperimentalComposeUiApi::class)
#Composable
fun customCanvas(){
var currentPosition by remember { mutableStateOf(Offset.Unspecified) }
var previousPosition by remember { mutableStateOf(Offset.Unspecified) }
val randomAngle = listOf(45f, -45f)
var paths = remember { mutableStateListOf<Pair<Path, PathProperties>>() }
var currentPath by remember { mutableStateOf(Path()) }
var show by remember { mutableStateOf(false) }
Canvas(
modifier = Modifier
.fillMaxSize()
.background(color = Color.Gray)
.pointerInput(Unit) {
forEachGesture {
awaitPointerEventScope {
awaitFirstDown().also {
currentPosition = it.position
previousPosition = currentPosition
currentPath.moveTo(currentPosition.x, currentPosition.y)
val angle = randomAngle.random()
paths.add(Pair(currentPath, PathProperties(angle, 30f, Pair(currentPosition.x, currentPosition.y))))
}
}
}
}
.onPointerEvent(PointerEventType.Move) {
val position = it.changes.first().position
show = (position.x in 90f..110f) && position.y in 90f..110f
}
){
with(drawContext.canvas.nativeCanvas) {
val checkPoint = saveLayer(null, null)
paths.forEach { it: Pair<Path, PathProperties> ->
rotate(it.second.Angle, it.second.startPoint.first, it.second.startPoint.second )
drawLine(
color = Color.Black,
start = Offset(it.second.startPoint.first, it.second.startPoint.second ),
end = Offset(it.second.startPoint.first + it.second.length, it.second.startPoint.second),
cap = StrokeCap.Round
)
// draw a widder line on canvas using drawLine
drawLine(
color = Color.White,
start = Offset(it.second.startPoint.first, it.second.startPoint.second ),
end = Offset(it.second.startPoint.first + it.second.length, it.second.startPoint.second),
strokeWidth = 10f,
cap = StrokeCap.Square
)
rotate(-it.second.Angle, it.second.startPoint.first, it.second.startPoint.second)
}
// draw a white dot example
drawCircle(
color = if (show) Color.Green else Color.White,
center= Offset(100f, 100f),
radius = 10f,
)
}
}
}

max string length in compose before clipping

I want to create a string with the max number of characters allowable in the box.
setContent {
ViewincomposetestTheme {
var size by remember { mutableStateOf(IntSize.Zero) }
var widthdp by remember { mutableStateOf(0.dp) }
BoxWithConstraints(Modifier.fillMaxSize().background(Color.Yellow)) {
val widthOfChar = 13 // how do I find this
var string by remember {
mutableStateOf(
StringBuffer()
.apply {
repeat(maxWidth.value.toInt() / widthOfChar) { append("H") }
}
.toString()
)
}
Text(string)
}
}
}
You can create a separate Text to calculate the size of a character, which will report you its size with onTextLayout. Using drawWithContent you can prevent it from being drawn.
Also in your example you get the width using maxWidth.value.toInt(): here you get the value of dp, not pixels. You could convert it using LocalDensity, but you also get the pixel value directly from BoxWithConstraints using constraints.maxWidth.
BoxWithConstraints(
Modifier
.fillMaxSize()
.background(Color.Yellow)
) {
var charWidth by remember { mutableStateOf<Int?>(null) }
val string = remember(maxWidth, charWidth) {
charWidth?.let { charWidth ->
StringBuffer()
.apply {
repeat(constraints.maxWidth / charWidth) { append("H") }
}
.toString()
}
}
Text(
"H",
onTextLayout = {
charWidth = it.size.width
},
modifier = Modifier.drawWithContent { }
)
string?.let {
Text(string)
}
}
You can just use the maxWidth parameter of the BoxWithConstraints, then convert the obtained dp value toPx(). Decide a textsize for the TextField in sp and then do whatever calculations you want to do after converting that to Px as well. Max characters will be maxWidth / textSize, roughly.

Fragment BarChart in Modal is not updating - TornadoFX

I've created a Fragment to hold a BarChart and a ScrollPane - the end result will be a scrollable histogram.
I'm creating these new fragments in a seperate modal using the openModal method.
The problem that i'm having is that the BarChart doesn't seem to be updating when I call my loadData method, as shown below:
class Histogram : Fragment() {
override val root = vbox{
hgrow = Priority.ALWAYS
vgrow = Priority.ALWAYS
style{
minWidth = 1280.px
minHeight = 180.px
}
hbox{
hgrow = Priority.ALWAYS
}
}
private val bar = ScrollBar()
private var barChart = barchart("bar", CategoryAxis(), NumberAxis()){
barGap = 0.0
categoryGap = -1.0
hgrow = Priority.ALWAYS
vgrow = Priority.ALWAYS
style{
minWidth = 640.px
minHeight = 240.px
maxHeight = 480.px
}
isLegendVisible = false
}
private val s = XYChart.Series<String, Number>()
init{
root.add(barChart)
root.add(bar)
}
fun loadData(h: AlignmentHistogram){
s.data.add(XYChart.Data<String, Number>("asd", 2))
barChart.title = h.rname
/* for(i in 0..MAX_DATASET_SIZE){
if(i > h.histogram.size){
break
}
val data = XYChart.Data<String, Int>((h.firstPosition + i + 1).toString(), (h.histogram[i]))
println(data)
s.data.add(data)
}*/
s.data.add(XYChart.Data<String, Number>("asasdd", 5))
s.name = h.rname
barChart.data.add(s)
}
}
AlignmentHistogram is just a POJO with the data for the histogram and a few other details.
I'm calling the fragment with the following code:
val stage = Stage()
stage.title = "${pointer.rname} - ${selectedFile.file.name}"
val view = Histogram()
view.loadData(h)
val parent = StackPane()
stage.scene = Scene(parent, 1280.0, 360.0)
view.openModal(StageStyle.UTILITY, Modality.NONE, false, stage, false, true)
The result of this is just an empty histogram in a new modal, despite calling barChart.data.add(s)
Any ideas? Thanks in advance!
Nevermind, I solved it by passing the POJO in as the scope and putting the set-up code into the init block.
This article helped with the scope part:
Tornadofx - How to pass parameter to Fragment on every instance