fix content shift when the tree is toggled

visualizer
maxime.batista 1 year ago committed by maxime
parent fc9a2181e9
commit 3bd501af3a

@ -1,5 +1,3 @@
import java.net.URI
plugins { plugins {
alias(libs.plugins.androidApplication) alias(libs.plugins.androidApplication)
alias(libs.plugins.jetbrainsKotlinAndroid) alias(libs.plugins.jetbrainsKotlinAndroid)

@ -74,7 +74,7 @@ class MainActivity : ComponentActivity() {
.addConverterFactory(EitherBodyConverter.create()) .addConverterFactory(EitherBodyConverter.create())
.addConverterFactory(MoshiConverterFactory.create(moshi)) .addConverterFactory(MoshiConverterFactory.create(moshi))
.addCallAdapterFactory(EitherCallAdapterFactory.create()) .addCallAdapterFactory(EitherCallAdapterFactory.create())
.baseUrl("http://192.168.127.83:5254/") .baseUrl("http://grospc:5254/")
.client( .client(
OkHttpClient.Builder() OkHttpClient.Builder()
.addInterceptor { it.proceed(it.request()) } .addInterceptor { it.proceed(it.request()) }
@ -85,7 +85,7 @@ class MainActivity : ComponentActivity() {
val service = retrofit.create<IQBallService>() val service = retrofit.create<IQBallService>()
setContent { setContent {
IQBallTheme { IQBallTheme(darkTheme = false) {
// A surface container using the 'background' color from the theme // A surface container using the 'background' color from the theme
Surface( Surface(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),

@ -195,8 +195,7 @@ private fun computeSegmentsToPath(
path.moveTo(segmentStart.x, segmentStart.y) path.moveTo(segmentStart.x, segmentStart.y)
for (i in segments.indices) { for ((i, segment) in segments.withIndex()) {
val segment = segments[i]
var nextPos = extractPos(segment.next, offsets, area) var nextPos = extractPos(segment.next, offsets, area)
if (i == segments.size - 1) { if (i == segments.size - 1) {

@ -1,12 +1,24 @@
package com.iqball.app.component package com.iqball.app.component
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.layout.requiredWidth
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@ -14,11 +26,13 @@ import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.boundsInParent
import androidx.compose.ui.layout.boundsInRoot import androidx.compose.ui.layout.boundsInRoot
import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import arrow.core.getOrNone
import com.iqball.app.R import com.iqball.app.R
import com.iqball.app.domains.getPlayerInfo import com.iqball.app.domains.getPlayerInfo
import com.iqball.app.geo.toVector import com.iqball.app.geo.toVector
@ -50,34 +64,42 @@ fun BasketCourt(
CourtType.Half -> R.drawable.half_court CourtType.Half -> R.drawable.half_court
} }
var courtArea by state.courtArea
val zoomState = state.zoomState val zoomState = state.zoomState
Box( Box(
modifier = modifier, modifier = modifier
.background(Color.LightGray)
.fillMaxSize(),
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
Box( Box(
modifier = Modifier modifier = Modifier
.width(IntrinsicSize.Min)
.zoomable(zoomState), .zoomable(zoomState),
contentAlignment = Alignment.Center, contentAlignment = Alignment.Center,
) { ) {
Image( Image(
painter = painterResource(id = courtImg), painter = painterResource(id = courtImg),
contentDescription = "court", contentDescription = "court",
modifier = Modifier modifier = Modifier
.background(Color.White) .background(Color.White)
.onGloballyPositioned {
if (courtArea == Rect.Zero)
courtArea = it.boundsInRoot()
}
) )
CourtContent( CourtContent(
courtAreaState = state.courtArea, courtArea = courtArea,
content = content, content = content,
offsets = state.stepComponentsOffsets, offsets = state.stepComponentsOffsets,
isFromParent = false isFromParent = false
) )
if (parentContent != null) { if (parentContent != null) {
CourtContent( CourtContent(
courtAreaState = state.courtArea, courtArea = courtArea,
content = parentContent, content = parentContent,
offsets = state.parentComponentsOffsets, offsets = state.parentComponentsOffsets,
isFromParent = true isFromParent = true
@ -89,26 +111,31 @@ fun BasketCourt(
@Composable @Composable
private fun CourtContent( private fun CourtContent(
courtAreaState: MutableState<Rect>, courtArea: Rect,
content: StepContent, content: StepContent,
offsets: MutableMap<ComponentId, Offset>, offsets: MutableMap<ComponentId, Offset>,
isFromParent: Boolean isFromParent: Boolean
) { ) {
var courtArea by courtAreaState
val playersPixelsRadius = LocalDensity.current.run { PlayerPieceDiameterDp.dp.toPx() / 2 } val playersPixelsRadius = LocalDensity.current.run { PlayerPieceDiameterDp.dp.toPx() / 2 }
val width = LocalDensity.current.run { courtArea.width.toDp() }
val height = LocalDensity.current.run { courtArea.height.toDp() }
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxSize() .requiredWidth(width)
.onGloballyPositioned { .requiredHeight(height)
if (courtArea == Rect.Zero)
courtArea = it.boundsInRoot()
}
.drawWithContent { .drawWithContent {
val relativeOffsets = val relativeOffsets =
offsets.mapValues { (it.value - courtArea.topLeft).toVector() } offsets.mapValues { (it.value).toVector() }
drawActions(this, content, relativeOffsets, courtArea, playersPixelsRadius, if (isFromParent) Color.Gray else Color.Black) drawActions(
this,
content,
relativeOffsets,
courtArea,
playersPixelsRadius,
if (isFromParent) Color.Gray else Color.Black
)
drawContent() drawContent()
} }
) { ) {
@ -117,8 +144,8 @@ private fun CourtContent(
for (component in content.components) { for (component in content.components) {
val componentModifier = Modifier val componentModifier = Modifier
.onGloballyPositioned { .onGloballyPositioned {
if (!offsets.containsKey(component.id)) if (!offsets.getOrNone(component.id).isSome { it != Offset.Zero })
offsets[component.id] = it.boundsInRoot().center offsets[component.id] = it.boundsInParent().center
} }
when (component) { when (component) {
is PlayerLike -> { is PlayerLike -> {

@ -14,7 +14,7 @@ import com.iqball.app.model.tactic.StepComponent
import com.iqball.app.model.tactic.StepContent import com.iqball.app.model.tactic.StepContent
/** /**
* Converts the phantom's [PhantomPositioning] to a XY Position * Converts the phantom's [Positioning] to a XY Position
* if the phantom is a [RelativePositioning], the XY coords are determined * if the phantom is a [RelativePositioning], the XY coords are determined
* using the attached component, and by expecting that there is an action on the attached component that * using the attached component, and by expecting that there is an action on the attached component that
* targets the given phantom. * targets the given phantom.

@ -105,28 +105,37 @@ fun VisualizerPage(
onNodeSelected = { selectedStepId = it.id }) onNodeSelected = { selectedStepId = it.id })
Configuration.ORIENTATION_LANDSCAPE -> { Configuration.ORIENTATION_LANDSCAPE -> {
val courtArea = remember { mutableStateOf(Rect.Zero) }
val stepOffsets = val stepOffsets =
remember(showTree.value, content) { mutableStateMapOf<ComponentId, Offset>() } remember(selectedStepId) { mutableStateMapOf<ComponentId, Offset>() }
val parentOffsets = val parentOffsets =
remember(showTree.value, parentContent) { mutableStateMapOf<ComponentId, Offset>() } remember(selectedStepId) { mutableStateMapOf<ComponentId, Offset>() }
val courtArea = remember(showTree.value) { mutableStateOf(Rect.Zero) }
val courtZoomState = remember(showTree.value, selectedStepId) { ZoomState() }
val courtModifier = val courtModifier =
if (showTree.value) Modifier.width(IntrinsicSize.Min) else Modifier.fillMaxWidth() if (showTree.value) Modifier.width(IntrinsicSize.Min) else Modifier.fillMaxWidth()
val courtZoomState = remember { ZoomState() }
Row(modifier = Modifier.background(Color.LightGray)) { Row(modifier = Modifier.background(Color.LightGray)) {
BasketCourt( BasketCourt(
content = content, content = content,
parentContent, parentContent,
type = info.type, type = info.type,
modifier = courtModifier, modifier = courtModifier,
state = BasketCourtStates(stepOffsets, parentOffsets, courtArea, courtZoomState) state = BasketCourtStates(
stepOffsets,
parentOffsets,
courtArea,
courtZoomState
)
) )
if (showTree.value) { if (showTree.value) {
StepsTree(root = stepsTree, StepsTree(
root = stepsTree,
selectedNodeId = selectedStepId, selectedNodeId = selectedStepId,
onNodeSelected = { selectedStepId = it.id }) onNodeSelected = { selectedStepId = it.id }
)
} }
} }
} }

@ -28,7 +28,10 @@ private class EitherTypeAdapter(
JsonReader.Token.STRING -> JsonPrimitiveType.String JsonReader.Token.STRING -> JsonPrimitiveType.String
JsonReader.Token.NUMBER -> JsonPrimitiveType.Number JsonReader.Token.NUMBER -> JsonPrimitiveType.Number
JsonReader.Token.BOOLEAN -> JsonPrimitiveType.Boolean JsonReader.Token.BOOLEAN -> JsonPrimitiveType.Boolean
JsonReader.Token.NULL -> return null JsonReader.Token.NULL -> {
reader.nextNull<Any>()
return null
}
else -> throw JsonDataException("unexpected token : $token") else -> throw JsonDataException("unexpected token : $token")
} }

Loading…
Cancel
Save