|
@@ -16,56 +16,48 @@ import androidx.compose.ui.geometry.Offset
|
|
|
import androidx.compose.ui.geometry.Size
|
|
import androidx.compose.ui.geometry.Size
|
|
|
import androidx.compose.ui.graphics.Color
|
|
import androidx.compose.ui.graphics.Color
|
|
|
import androidx.compose.ui.graphics.Path
|
|
import androidx.compose.ui.graphics.Path
|
|
|
|
|
+import androidx.compose.ui.graphics.StrokeCap
|
|
|
|
|
+import androidx.compose.ui.graphics.StrokeJoin
|
|
|
import androidx.compose.ui.graphics.drawscope.Stroke
|
|
import androidx.compose.ui.graphics.drawscope.Stroke
|
|
|
import androidx.compose.ui.layout.onGloballyPositioned
|
|
import androidx.compose.ui.layout.onGloballyPositioned
|
|
|
import androidx.compose.ui.layout.positionInParent
|
|
import androidx.compose.ui.layout.positionInParent
|
|
|
import androidx.compose.ui.unit.dp
|
|
import androidx.compose.ui.unit.dp
|
|
|
import androidx.compose.ui.unit.toSize
|
|
import androidx.compose.ui.unit.toSize
|
|
|
import androidx.compose.ui.zIndex
|
|
import androidx.compose.ui.zIndex
|
|
|
|
|
+import com.iscs.bozzys.ui.theme.Main
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* 布局之间的连线操作(贝塞尔曲线)
|
|
* 布局之间的连线操作(贝塞尔曲线)
|
|
|
*/
|
|
*/
|
|
|
@Composable
|
|
@Composable
|
|
|
-fun ConnectionLayer(
|
|
|
|
|
- nodes: Map<String, Node>,
|
|
|
|
|
- connections: List<Pair<String, String>>
|
|
|
|
|
-) {
|
|
|
|
|
|
|
+fun ConnectionLayer(nodes: Map<String, Node>, connections: List<Connection>, scale: Float) {
|
|
|
Canvas(
|
|
Canvas(
|
|
|
modifier = Modifier
|
|
modifier = Modifier
|
|
|
.fillMaxSize()
|
|
.fillMaxSize()
|
|
|
.zIndex(0f)
|
|
.zIndex(0f)
|
|
|
) {
|
|
) {
|
|
|
- connections.forEach { (fromId, toId) ->
|
|
|
|
|
- val from = nodes[fromId]
|
|
|
|
|
- val to = nodes[toId]
|
|
|
|
|
- if (from != null && to != null) {
|
|
|
|
|
|
|
+ connections.forEach { conn ->
|
|
|
|
|
+ val from = nodes[conn.fromId]
|
|
|
|
|
+ val to = nodes[conn.toId]
|
|
|
|
|
|
|
|
- val start = from.rightCenter()
|
|
|
|
|
- val end = to.leftCenter()
|
|
|
|
|
|
|
+ if (from != null && to != null) {
|
|
|
|
|
|
|
|
- val control1 = Offset(
|
|
|
|
|
- start.x + 100f,
|
|
|
|
|
- start.y
|
|
|
|
|
- )
|
|
|
|
|
- val control2 = Offset(
|
|
|
|
|
- end.x - 100f,
|
|
|
|
|
- end.y
|
|
|
|
|
|
|
+ val pathPoints = orthogonalPath(
|
|
|
|
|
+ fromNode = from,
|
|
|
|
|
+ fromAnchor = conn.fromAnchor,
|
|
|
|
|
+ toNode = to,
|
|
|
|
|
+ toAnchor = conn.toAnchor
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
val path = Path().apply {
|
|
val path = Path().apply {
|
|
|
- moveTo(start.x, start.y)
|
|
|
|
|
- cubicTo(
|
|
|
|
|
- control1.x, control1.y,
|
|
|
|
|
- control2.x, control2.y,
|
|
|
|
|
- end.x, end.y
|
|
|
|
|
- )
|
|
|
|
|
|
|
+ moveTo(pathPoints.first().x, pathPoints.first().y)
|
|
|
|
|
+ for (i in 1 until pathPoints.size) {
|
|
|
|
|
+ lineTo(pathPoints[i].x, pathPoints[i].y)
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
drawPath(
|
|
drawPath(
|
|
|
- path = path,
|
|
|
|
|
- color = Color.Gray,
|
|
|
|
|
- style = Stroke(width = 3f)
|
|
|
|
|
|
|
+ path = path, color = Main, style = Stroke(width = 6f / scale, cap = StrokeCap.Round, join = StrokeJoin.Round)
|
|
|
)
|
|
)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -95,17 +87,87 @@ fun NodeItem(
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-/**
|
|
|
|
|
- * 右边连线
|
|
|
|
|
- */
|
|
|
|
|
-fun Node.rightCenter(): Offset =
|
|
|
|
|
- Offset(offset.x + size.width, offset.y + size.height / 2)
|
|
|
|
|
|
|
+fun Node.anchor(anchor: Anchor): Offset = when (anchor) {
|
|
|
|
|
+ Anchor.LEFT -> Offset(offset.x, offset.y + size.height / 2)
|
|
|
|
|
+ Anchor.RIGHT -> Offset(offset.x + size.width, offset.y + size.height / 2)
|
|
|
|
|
+ Anchor.TOP -> Offset(offset.x + size.width / 2, offset.y)
|
|
|
|
|
+ Anchor.BOTTOM -> Offset(offset.x + size.width / 2, offset.y + size.height)
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 左边连线
|
|
|
|
|
|
|
+ * 绘制正交连接线
|
|
|
*/
|
|
*/
|
|
|
-fun Node.leftCenter(): Offset =
|
|
|
|
|
- Offset(offset.x, offset.y + size.height / 2)
|
|
|
|
|
|
|
+private fun orthogonalPath(fromNode: Node, fromAnchor: Anchor, toNode: Node, toAnchor: Anchor, padding: Float = 40f): List<Offset> {
|
|
|
|
|
+ val from = fromNode.anchor(fromAnchor)
|
|
|
|
|
+ val to = toNode.anchor(toAnchor)
|
|
|
|
|
+ // 处理连接的点位
|
|
|
|
|
+ val points = mutableListOf<Offset>()
|
|
|
|
|
+ // 起点出线
|
|
|
|
|
+ var fromOut = when (fromAnchor) {
|
|
|
|
|
+ Anchor.LEFT -> Offset(from.x - padding, from.y)
|
|
|
|
|
+ Anchor.RIGHT -> Offset(from.x + padding, from.y)
|
|
|
|
|
+ Anchor.TOP -> Offset(from.x, from.y - padding)
|
|
|
|
|
+ Anchor.BOTTOM -> Offset(from.x, from.y + padding)
|
|
|
|
|
+ }
|
|
|
|
|
+ // 终点入线
|
|
|
|
|
+ var toIn = when (toAnchor) {
|
|
|
|
|
+ Anchor.LEFT -> Offset(to.x - padding, to.y)
|
|
|
|
|
+ Anchor.RIGHT -> Offset(to.x + padding, to.y)
|
|
|
|
|
+ Anchor.TOP -> Offset(to.x, to.y - padding)
|
|
|
|
|
+ Anchor.BOTTOM -> Offset(to.x, to.y + padding)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ val midPoints = mutableListOf<Offset>()
|
|
|
|
|
+ // 绘制中间连线
|
|
|
|
|
+ if (fromAnchor == Anchor.BOTTOM) {
|
|
|
|
|
+ when (toAnchor) {
|
|
|
|
|
+ // 只需要绘制一个点
|
|
|
|
|
+ Anchor.LEFT -> midPoints += Offset(fromOut.x, toIn.y)
|
|
|
|
|
+ // 顶部需要绘制两个点才能变成直线
|
|
|
|
|
+ Anchor.TOP -> {
|
|
|
|
|
+ val y = if (toIn.y - fromOut.y < 0) {
|
|
|
|
|
+ fromOut = fromOut.copy(y = from.y)
|
|
|
|
|
+ toIn = toIn.copy(y = to.y)
|
|
|
|
|
+ from.y + (to.y - from.y) / 2
|
|
|
|
|
+ } else {
|
|
|
|
|
+ fromOut.y + (toIn.y - fromOut.y) / 2
|
|
|
|
|
+ }
|
|
|
|
|
+ midPoints += Offset(fromOut.x, y)
|
|
|
|
|
+ midPoints += Offset(toIn.x, y)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ else -> {}
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if (fromAnchor == Anchor.RIGHT) {
|
|
|
|
|
+ when (toAnchor) {
|
|
|
|
|
+ Anchor.LEFT -> {
|
|
|
|
|
+ val x = if (toIn.x - fromOut.x < 0) {
|
|
|
|
|
+ fromOut = fromOut.copy(x = from.x)
|
|
|
|
|
+ toIn = toIn.copy(x = to.x)
|
|
|
|
|
+ from.x + (to.x - from.x) / 2
|
|
|
|
|
+ } else {
|
|
|
|
|
+ fromOut.x + (toIn.x - fromOut.x) / 2
|
|
|
|
|
+ }
|
|
|
|
|
+ midPoints += Offset(x, fromOut.y)
|
|
|
|
|
+ midPoints += Offset(x, toIn.y)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ else -> {}
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 将起点和起点出线绘制好
|
|
|
|
|
+ points += from
|
|
|
|
|
+ points += fromOut
|
|
|
|
|
+ // 绘制中间点
|
|
|
|
|
+ points += midPoints
|
|
|
|
|
+ // 绘制结束连线
|
|
|
|
|
+ points += toIn
|
|
|
|
|
+ points += to
|
|
|
|
|
+
|
|
|
|
|
+ return points
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* 节点定义
|
|
* 节点定义
|
|
@@ -115,3 +177,20 @@ data class Node(
|
|
|
val offset: Offset,
|
|
val offset: Offset,
|
|
|
val size: Size
|
|
val size: Size
|
|
|
)
|
|
)
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 定义连线位置
|
|
|
|
|
+ */
|
|
|
|
|
+enum class Anchor {
|
|
|
|
|
+ LEFT, RIGHT, TOP, BOTTOM
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 定义连线信息
|
|
|
|
|
+ */
|
|
|
|
|
+data class Connection(
|
|
|
|
|
+ val fromId: String,
|
|
|
|
|
+ val toId: String,
|
|
|
|
|
+ val fromAnchor: Anchor = Anchor.RIGHT,
|
|
|
|
|
+ val toAnchor: Anchor = Anchor.LEFT
|
|
|
|
|
+)
|