mirror of
https://gitlab.com/cc-ru/ocelot/ocelot-desktop.git
synced 2025-12-20 02:59:19 +01:00
Add relays
This commit is contained in:
parent
d3642d0800
commit
c8decda297
@ -1 +1 @@
|
||||
Subproject commit 69c4076ae6dd199c403bbfb564c5e54d5065fbcc
|
||||
Subproject commit 2ea81581884e6f766dc8477af54eb645ab63a5a7
|
||||
BIN
sprites/nodes/Relay.png
Normal file
BIN
sprites/nodes/Relay.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 595 B |
Binary file not shown.
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 55 KiB |
@ -1,6 +1,6 @@
|
||||
BackgroundPattern 0 0 304 304
|
||||
Empty 337 197 1 1
|
||||
ShadowBorder 305 197 1 24
|
||||
Empty 337 204 1 1
|
||||
ShadowBorder 305 204 1 24
|
||||
ShadowCorner 424 0 24 24
|
||||
buttons/PowerOff 441 25 18 18
|
||||
buttons/PowerOn 460 25 18 18
|
||||
@ -78,18 +78,19 @@ nodes/ComputerActivityOverlay 356 180 16 16
|
||||
nodes/ComputerErrorOverlay 373 180 16 16
|
||||
nodes/ComputerOnOverlay 390 180 16 16
|
||||
nodes/NewNode 407 180 16 16
|
||||
nodes/Screen 424 180 16 16
|
||||
nodes/ScreenOnOverlay 441 180 16 16
|
||||
screen/BorderB 310 197 2 8
|
||||
screen/BorderT 307 197 2 10
|
||||
screen/CornerBL 476 180 8 8
|
||||
screen/CornerBR 485 180 8 8
|
||||
screen/CornerTL 458 180 8 10
|
||||
screen/CornerTR 467 180 8 10
|
||||
window/BorderDark 333 197 1 4
|
||||
window/BorderLight 335 197 1 4
|
||||
window/CloseButton 494 180 7 6
|
||||
window/CornerBL 313 197 4 4
|
||||
window/CornerBR 318 197 4 4
|
||||
window/CornerTL 323 197 4 4
|
||||
window/CornerTR 328 197 4 4
|
||||
nodes/Relay 424 180 16 16
|
||||
nodes/Screen 441 180 16 16
|
||||
nodes/ScreenOnOverlay 458 180 16 16
|
||||
screen/BorderB 310 204 2 8
|
||||
screen/BorderT 307 204 2 10
|
||||
screen/CornerBL 493 180 8 8
|
||||
screen/CornerBR 502 180 8 8
|
||||
screen/CornerTL 475 180 8 10
|
||||
screen/CornerTR 484 180 8 10
|
||||
window/BorderDark 333 204 1 4
|
||||
window/BorderLight 335 204 1 4
|
||||
window/CloseButton 305 197 7 6
|
||||
window/CornerBL 313 204 4 4
|
||||
window/CornerBR 318 204 4 4
|
||||
window/CornerTL 323 204 4 4
|
||||
window/CornerTR 328 204 4 4
|
||||
|
||||
@ -88,4 +88,8 @@ case class Rect2D(x: Float, y: Float, w: Float, h: Float) {
|
||||
def manhattanDistanceTo(that: Rect2D): Float = {
|
||||
((center - that.center).abs - (extent + that.extent)).max(Vector2D(0, 0)).manhattanLength
|
||||
}
|
||||
|
||||
def mapX(f: Float => Float): Rect2D = copy(x = f(x))
|
||||
|
||||
def mapY(f: Float => Float): Rect2D = copy(y = f(y))
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
package ocelot.desktop.graphics
|
||||
|
||||
import ocelot.desktop.color.{Color, RGBAColorNorm}
|
||||
import ocelot.desktop.geometry.{Size2D, Transform2D, Vector2D}
|
||||
import ocelot.desktop.geometry.{Rect2D, Size2D, Transform2D, Vector2D}
|
||||
import ocelot.desktop.graphics.mesh.{Mesh, MeshInstance}
|
||||
import ocelot.desktop.graphics.render.InstanceRenderer
|
||||
import ocelot.desktop.util.{Font, Logging, Spritesheet}
|
||||
@ -227,6 +227,10 @@ class Graphics extends Logging {
|
||||
_rect(x, y, width, height)
|
||||
}
|
||||
|
||||
def rect(r: Rect2D, color: Color): Unit = {
|
||||
rect(r.x, r.y, r.w, r.h, color)
|
||||
}
|
||||
|
||||
def rect(x: Float, y: Float, width: Float, height: Float, color: Color = RGBAColorNorm(1f, 1f, 1f)): Unit = {
|
||||
sprite("Empty", x, y, width, height, color)
|
||||
}
|
||||
|
||||
@ -1,18 +1,21 @@
|
||||
package ocelot.desktop.node
|
||||
|
||||
import ocelot.desktop.color.{Color, RGBAColor}
|
||||
import ocelot.desktop.geometry.{Size2D, Vector2D}
|
||||
import ocelot.desktop.geometry.{Rect2D, Size2D, Vector2D}
|
||||
import ocelot.desktop.graphics.Graphics
|
||||
import ocelot.desktop.ui.event.{ClickEvent, DragEvent, HoverEvent, MouseEvent}
|
||||
import ocelot.desktop.ui.event.handlers.{ClickHandler, DragHandler, HoverHandler}
|
||||
import ocelot.desktop.ui.event.sources.KeyEvents
|
||||
import ocelot.desktop.ui.widget.{Widget, WorkspaceView}
|
||||
import ocelot.desktop.ui.event.{ClickEvent, DragEvent, HoverEvent, MouseEvent}
|
||||
import ocelot.desktop.ui.widget.window.Window
|
||||
import ocelot.desktop.ui.widget.{Widget, WorkspaceView}
|
||||
import ocelot.desktop.util.DrawUtils
|
||||
import ocelot.desktop.util.animation.ColorAnimation
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import org.lwjgl.input.Keyboard
|
||||
import totoro.ocelot.brain.entity.traits.Environment
|
||||
import totoro.ocelot.brain.network
|
||||
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
|
||||
trait Node extends Widget with DragHandler with ClickHandler with HoverHandler {
|
||||
var workspaceView: WorkspaceView = _
|
||||
@ -25,6 +28,8 @@ trait Node extends Widget with DragHandler with ClickHandler with HoverHandler {
|
||||
private var isMoving = false
|
||||
private var grabPoint: Vector2D = Vector2D(0, 0)
|
||||
|
||||
protected val _connections: ArrayBuffer[(NodePort, Node, NodePort)] = ArrayBuffer[(NodePort, Node, NodePort)]()
|
||||
|
||||
size = minimumSize
|
||||
|
||||
override def receiveMouseEvents = true
|
||||
@ -37,6 +42,9 @@ trait Node extends Widget with DragHandler with ClickHandler with HoverHandler {
|
||||
if (!KeyEvents.isDown(Keyboard.KEY_LSHIFT)) {
|
||||
grabPoint = pos - position
|
||||
startMoving()
|
||||
} else {
|
||||
val port = portsBounds.flatMap(p => p._2.map(a => (p._1, a))).minBy(p => (p._2.center - pos).lengthSquared)._1
|
||||
workspaceView.newConnection = Some((this, port, pos))
|
||||
}
|
||||
|
||||
case DragEvent(DragEvent.State.Drag, MouseEvent.Button.Left, pos) =>
|
||||
@ -50,9 +58,8 @@ trait Node extends Widget with DragHandler with ClickHandler with HoverHandler {
|
||||
workspaceView.resolveCollision(this)
|
||||
if (workspaceView.collides(this) || (position - desiredPos).lengthSquared > 50 * 50)
|
||||
position = oldPos
|
||||
// UiHandler.cursor = Cursor.Hand
|
||||
} else {
|
||||
workspaceView.newConnection = Some((this, pos))
|
||||
workspaceView.newConnection = Some((this, workspaceView.newConnection.get._2, pos))
|
||||
}
|
||||
|
||||
case DragEvent(DragEvent.State.Stop, MouseEvent.Button.Left, _) =>
|
||||
@ -78,6 +85,69 @@ trait Node extends Widget with DragHandler with ClickHandler with HoverHandler {
|
||||
|
||||
def iconColor: Color = RGBAColor(255, 255, 255)
|
||||
|
||||
def ports: Array[NodePort] = Array(NodePort())
|
||||
|
||||
def getNodeByPort(port: NodePort): network.Node = environment.node
|
||||
|
||||
def connections: Iterator[(NodePort, Node, NodePort)] = _connections.iterator
|
||||
|
||||
def connect(portA: NodePort, node: Node, portB: NodePort): Unit = {
|
||||
this._connections.append((portA, node, portB))
|
||||
node._connections.append((portB, this, portA))
|
||||
this.onConnectionAdded(portA, node, portB)
|
||||
node.onConnectionAdded(portB, this, portA)
|
||||
}
|
||||
|
||||
def disconnect(portA: NodePort, node: Node, portB: NodePort): Unit = {
|
||||
this._connections -= ((portA, node, portB))
|
||||
node._connections -= ((portB, this, portA))
|
||||
this.onConnectionRemoved(portA, node, portB)
|
||||
node.onConnectionRemoved(portB, this, portA)
|
||||
}
|
||||
|
||||
def isConnected(portA: NodePort, node: Node, portB: NodePort): Boolean = {
|
||||
_connections.contains((portA, node, portB))
|
||||
}
|
||||
|
||||
def onConnectionAdded(portA: NodePort, node: Node, portB: NodePort): Unit = {
|
||||
getNodeByPort(portA).connect(node.getNodeByPort(portB))
|
||||
}
|
||||
|
||||
def onConnectionRemoved(portA: NodePort, node: Node, portB: NodePort): Unit = {
|
||||
getNodeByPort(portA).disconnect(node.getNodeByPort(portB))
|
||||
}
|
||||
|
||||
def portsBounds: Iterator[(NodePort, Array[Rect2D])] = {
|
||||
val length = -4
|
||||
val thickness = 4
|
||||
val stride = thickness + 4
|
||||
val hsize = Size2D(length, thickness)
|
||||
val vsize = Size2D(thickness, length)
|
||||
|
||||
val centers = bounds.edgeCenters
|
||||
val ports = this.ports
|
||||
val numPorts = ports.length
|
||||
|
||||
ports.sorted.iterator.zipWithIndex.map { case (port, portIdx) =>
|
||||
val top = Rect2D(centers(0) + Vector2D(-thickness / 2, -length), vsize)
|
||||
val right = Rect2D(centers(1) + Vector2D(0, -thickness / 2), hsize)
|
||||
val bottom = Rect2D(centers(2) + Vector2D(-thickness / 2, 0), vsize)
|
||||
val left = Rect2D(centers(3) + Vector2D(-length, -thickness / 2), hsize)
|
||||
val centersBounds = Array[Rect2D](top, right, bottom, left)
|
||||
|
||||
val portBounds = (0 until 4).map(side => {
|
||||
val offset = thickness - numPorts * stride / 2 + portIdx * stride
|
||||
val rect = centersBounds(side)
|
||||
side match {
|
||||
case 0 | 2 => rect.mapX(_ + offset)
|
||||
case 1 | 3 => rect.mapY(_ + offset)
|
||||
}
|
||||
})
|
||||
|
||||
(port, portBounds.toArray)
|
||||
}
|
||||
}
|
||||
|
||||
// noinspection VarCouldBeVal
|
||||
var label: Option[String] = None
|
||||
|
||||
@ -98,7 +168,7 @@ trait Node extends Widget with DragHandler with ClickHandler with HoverHandler {
|
||||
highlight.goto(NoHighlight)
|
||||
}
|
||||
|
||||
protected def getShortLabel: String =
|
||||
def getShortLabel: String =
|
||||
StringUtils.substring(label.orElse(Option(environment.node.address)).getOrElse("unknown"), 0, 8)
|
||||
|
||||
override def draw(g: Graphics): Unit = {
|
||||
@ -115,5 +185,13 @@ trait Node extends Widget with DragHandler with ClickHandler with HoverHandler {
|
||||
g.setNormalFont()
|
||||
}
|
||||
|
||||
def drawPorts(g: Graphics): Unit = {
|
||||
for ((port, rects) <- portsBounds) {
|
||||
val color = port.getColor
|
||||
for (rect <- rects)
|
||||
g.rect(rect, color)
|
||||
}
|
||||
}
|
||||
|
||||
lazy val window: Option[Window] = None
|
||||
}
|
||||
|
||||
18
src/main/scala/ocelot/desktop/node/NodePort.scala
Normal file
18
src/main/scala/ocelot/desktop/node/NodePort.scala
Normal file
@ -0,0 +1,18 @@
|
||||
package ocelot.desktop.node
|
||||
|
||||
import ocelot.desktop.color.{Color, IntColor, RGBAColor}
|
||||
import totoro.ocelot.brain.util.Direction
|
||||
|
||||
case class NodePort(direction: Option[Direction.Value] = None) extends Ordered[NodePort] {
|
||||
def getColor: Color = direction match {
|
||||
case Some(Direction.Down) => IntColor(0x8382d8)
|
||||
case Some(Direction.Up) => IntColor(0x75bdc1)
|
||||
case Some(Direction.North) => IntColor(0xc8ca5f)
|
||||
case Some(Direction.East) => IntColor(0xdb7d75)
|
||||
case Some(Direction.West) => IntColor(0x7ec95f)
|
||||
case Some(Direction.South) => IntColor(0x990da3)
|
||||
case None => IntColor(0x9b9b9b)
|
||||
}
|
||||
|
||||
override def compare(that: NodePort): Int = this.direction.compare(that.direction)
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
package ocelot.desktop.node
|
||||
|
||||
import totoro.ocelot.brain.entity.{Case, Screen}
|
||||
import ocelot.desktop.node.nodes.{ComputerNode, RelayNode, ScreenNode}
|
||||
import totoro.ocelot.brain.entity.{Case, Relay, Screen}
|
||||
|
||||
import scala.collection.mutable
|
||||
|
||||
@ -11,6 +12,10 @@ object NodeRegistry {
|
||||
types += t
|
||||
}
|
||||
|
||||
register(NodeType("Relay", "nodes/Relay", -1, () => {
|
||||
new RelayNode(new Relay)
|
||||
}))
|
||||
|
||||
for (tier <- 0 to 2) {
|
||||
register(NodeType("Screen" + tier, "nodes/Screen", tier, () => {
|
||||
new ScreenNode(new Screen(tier))
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
package ocelot.desktop.node
|
||||
package ocelot.desktop.node.nodes
|
||||
|
||||
import ocelot.desktop.OcelotDesktop
|
||||
import ocelot.desktop.color.Color
|
||||
import ocelot.desktop.graphics.Graphics
|
||||
import ocelot.desktop.node.Node
|
||||
import ocelot.desktop.util.TierColor
|
||||
import totoro.ocelot.brain.entity.traits.Computer
|
||||
import totoro.ocelot.brain.entity.{CPU, Case, EEPROM, GraphicsCard, HDDManaged, InternetCard, Memory}
|
||||
import totoro.ocelot.brain.entity.{CPU, Case, EEPROM, GraphicsCard, HDDManaged, InternetCard, Memory, NetworkCard}
|
||||
import totoro.ocelot.brain.loot.Loot
|
||||
import totoro.ocelot.brain.util.Tier
|
||||
|
||||
@ -17,6 +18,7 @@ class ComputerNode(val computer: Case) extends Node {
|
||||
computer.add(new InternetCard)
|
||||
computer.add(new Memory(Tier.Six))
|
||||
computer.add(new Memory(Tier.Six))
|
||||
computer.add(new NetworkCard)
|
||||
computer.add(new HDDManaged(java.util.UUID.randomUUID().toString, Tier.Three, "hello"))
|
||||
private val eeprom = Loot.OpenOsEEPROM.create()
|
||||
eeprom.asInstanceOf[EEPROM].readonly = false
|
||||
@ -1,4 +1,4 @@
|
||||
package ocelot.desktop.node
|
||||
package ocelot.desktop.node.nodes
|
||||
|
||||
import ocelot.desktop.geometry.Size2D
|
||||
import ocelot.desktop.graphics.Graphics
|
||||
27
src/main/scala/ocelot/desktop/node/nodes/RelayNode.scala
Normal file
27
src/main/scala/ocelot/desktop/node/nodes/RelayNode.scala
Normal file
@ -0,0 +1,27 @@
|
||||
package ocelot.desktop.node.nodes
|
||||
|
||||
import ocelot.desktop.OcelotDesktop
|
||||
import ocelot.desktop.node.{Node, NodePort}
|
||||
import totoro.ocelot.brain.entity.Relay
|
||||
import totoro.ocelot.brain.network
|
||||
import totoro.ocelot.brain.util.Direction
|
||||
|
||||
class RelayNode(relay: Relay) extends Node {
|
||||
OcelotDesktop.workspace.add(relay)
|
||||
|
||||
override def environment: Relay = relay
|
||||
|
||||
override val icon: String = "nodes/Relay"
|
||||
|
||||
override def ports: Array[NodePort] = Array(
|
||||
NodePort(Some(Direction.North)),
|
||||
NodePort(Some(Direction.South)),
|
||||
NodePort(Some(Direction.East)),
|
||||
NodePort(Some(Direction.West)),
|
||||
NodePort(Some(Direction.Up)),
|
||||
NodePort(Some(Direction.Down)))
|
||||
|
||||
override def getShortLabel: String = ""
|
||||
|
||||
override def getNodeByPort(port: NodePort): network.Node = relay.sidedNode(port.direction.get)
|
||||
}
|
||||
@ -1,8 +1,9 @@
|
||||
package ocelot.desktop.node
|
||||
package ocelot.desktop.node.nodes
|
||||
|
||||
import ocelot.desktop.OcelotDesktop
|
||||
import ocelot.desktop.color.Color
|
||||
import ocelot.desktop.graphics.Graphics
|
||||
import ocelot.desktop.node.Node
|
||||
import ocelot.desktop.util.TierColor
|
||||
import totoro.ocelot.brain.entity.traits.Environment
|
||||
import totoro.ocelot.brain.entity.{Keyboard, Screen}
|
||||
@ -1,4 +1,4 @@
|
||||
package ocelot.desktop.node
|
||||
package ocelot.desktop.node.nodes
|
||||
|
||||
import ocelot.desktop.color.{IntColor, RGBAColor, RGBAColorNorm}
|
||||
import ocelot.desktop.geometry.{Rect2D, Size2D, Vector2D}
|
||||
@ -3,7 +3,8 @@ package ocelot.desktop.ui.widget
|
||||
import ocelot.desktop.color.{Color, RGBAColor, RGBAColorNorm}
|
||||
import ocelot.desktop.geometry.{Rect2D, Size2D, Vector2D}
|
||||
import ocelot.desktop.graphics.Graphics
|
||||
import ocelot.desktop.node.{ComputerNode, Node, ScreenNode}
|
||||
import ocelot.desktop.node.nodes.{ComputerNode, ScreenNode}
|
||||
import ocelot.desktop.node.{Node, NodePort}
|
||||
import ocelot.desktop.ui.event._
|
||||
import ocelot.desktop.ui.event.handlers.{ClickHandler, DragHandler, HoverHandler}
|
||||
import ocelot.desktop.ui.layout.{CopyLayout, Layout}
|
||||
@ -23,10 +24,11 @@ class WorkspaceView extends Widget with DragHandler with ClickHandler with Hover
|
||||
val nodeSelector = new NodeSelector
|
||||
|
||||
var cameraOffset: Vector2D = Vector2D(0, 0)
|
||||
var newConnection: Option[(Node, Vector2D)] = None
|
||||
var newConnection: Option[(Node, NodePort, Vector2D)] = None
|
||||
|
||||
private var newNodePos = Vector2D(0, 0)
|
||||
private val gridAlpha = new ValueAnimation(0, 1f)
|
||||
private val portsAlpha = new ValueAnimation(0, 7f)
|
||||
|
||||
override protected val layout: Layout = new CopyLayout(this)
|
||||
|
||||
@ -45,21 +47,22 @@ class WorkspaceView extends Widget with DragHandler with ClickHandler with Hover
|
||||
}
|
||||
|
||||
def buildNewConnection(): Unit = {
|
||||
val (start, endPoint) = newConnection.get
|
||||
val end = nodes.find(_.bounds.contains(endPoint))
|
||||
if (end.isDefined && end.get != start) {
|
||||
if (end.get.environment.node.neighbors.exists(_ == start.environment.node))
|
||||
end.get.environment.disconnect(start.environment)
|
||||
val (node, port, _) = newConnection.get
|
||||
newConnectionTarget match {
|
||||
case Some((target, targetPort)) =>
|
||||
if (node.isConnected(port, target, targetPort))
|
||||
node.disconnect(port, target, targetPort)
|
||||
else
|
||||
end.get.environment.connect(start.environment)
|
||||
node.connect(port, target, targetPort)
|
||||
case None =>
|
||||
}
|
||||
newConnection = None
|
||||
}
|
||||
|
||||
def createDefaultWorkspace(): Unit = {
|
||||
addNode(new ComputerNode(new Case(Tier.Six)))
|
||||
addNode(new ComputerNode(new Case(Tier.Four)))
|
||||
addNode(new ScreenNode(new Screen(Tier.Two)), Vector2D(200, 100))
|
||||
nodes(0).environment.connect(nodes(1).environment)
|
||||
nodes(0).connect(NodePort(), nodes(1), NodePort())
|
||||
}
|
||||
|
||||
override def receiveMouseEvents = true
|
||||
@ -144,40 +147,39 @@ class WorkspaceView extends Widget with DragHandler with ClickHandler with Hover
|
||||
!nodes.exists(_.bounds.collides(clearance))
|
||||
}
|
||||
|
||||
private def findSuitableConnections(a: Rect2D, b: Rect2D, checkCollision: Boolean,
|
||||
forceParallel: Boolean, clampMin: Boolean): Iterator[Array[Vector2D]] = {
|
||||
var pairs =
|
||||
for (aSide <- a.edgeCenters.iterator; bSide <- b.edgeCenters.iterator)
|
||||
yield (aSide, bSide)
|
||||
private def findSuitableConnections(a: Array[(Vector2D, Vector2D)],
|
||||
b: Array[(Vector2D, Vector2D)],
|
||||
checkCollision: Boolean,
|
||||
forceParallel: Boolean,
|
||||
clampMin: Boolean): Array[Array[Vector2D]] = {
|
||||
val product = for (x <- a; y <- b) yield (x, y)
|
||||
var iter = product.map { case ((aSide, aCenter), (bSide, bCenter)) =>
|
||||
val (aLen, bLen) = connectorLen(aSide, aCenter, bSide, bCenter, clampMin)
|
||||
((aSide, aCenter, aLen), (bSide, bCenter, bLen))
|
||||
}
|
||||
|
||||
if (checkCollision)
|
||||
pairs = pairs.filter(pair => {
|
||||
val (aSide, bSide) = pair
|
||||
val (aLen, bLen) = connectorLen(a.center, aSide, b.center, bSide, clampMin)
|
||||
checkConnectorCollision(aSide, a.center, aLen) &&
|
||||
checkConnectorCollision(bSide, b.center, bLen)
|
||||
})
|
||||
iter = iter.filter { case ((aSide, aCenter, aLen), (bSide, bCenter, bLen)) =>
|
||||
checkConnectorCollision(aSide, aCenter, aLen) &&
|
||||
checkConnectorCollision(bSide, bCenter, bLen)
|
||||
}
|
||||
|
||||
var paths = pairs.map(p => {
|
||||
val (aLen, bLen) = connectorLen(a.center, p._1, b.center, p._2, clampMin)
|
||||
val aEnd = p._1 + (p._1 - a.center).normalizeAxisAligned * aLen
|
||||
val bEnd = p._2 + (p._2 - b.center).normalizeAxisAligned * bLen
|
||||
Array(p._1, aEnd, bEnd, p._2)
|
||||
}).filter(DrawUtils.isValidPolyline)
|
||||
var paths = iter.map { case ((aSide, aCenter, aLen), (bSide, bCenter, bLen)) =>
|
||||
val aEnd = aSide + (aSide - aCenter).normalizeAxisAligned * aLen
|
||||
val bEnd = bSide + (bSide - bCenter).normalizeAxisAligned * bLen
|
||||
Array(aSide, aEnd, bEnd, bSide)
|
||||
}.filter(DrawUtils.isValidPolyline)
|
||||
|
||||
if (forceParallel)
|
||||
paths = paths.filter(p => {
|
||||
val Array(aStart, aEnd, bEnd, bStart) = p
|
||||
val aDir = aEnd - aStart
|
||||
val bDir = bEnd - bStart
|
||||
aDir.dot(bDir) != 0f
|
||||
})
|
||||
paths = paths.filter { case Array(aStart, aEnd, bEnd, bStart) =>
|
||||
(aEnd - aStart).dot(bEnd - bStart) != 0f
|
||||
}
|
||||
|
||||
paths
|
||||
}
|
||||
|
||||
private def connectorLen(aCenter: Vector2D, aSide: Vector2D,
|
||||
bCenter: Vector2D, bSide: Vector2D,
|
||||
private def connectorLen(aSide: Vector2D, aCenter: Vector2D,
|
||||
bSide: Vector2D, bCenter: Vector2D,
|
||||
clampMin: Boolean): (Float, Float) = {
|
||||
val aDir = aSide - aCenter
|
||||
val bDir = bSide - bCenter
|
||||
@ -191,32 +193,111 @@ class WorkspaceView extends Widget with DragHandler with ClickHandler with Hover
|
||||
(aLen.min(40f), bLen.min(40f))
|
||||
}
|
||||
|
||||
def drawConnection(g: Graphics, a: Rect2D, b: Rect2D,
|
||||
clampMin: Boolean = true,
|
||||
checkCollision: Boolean = true,
|
||||
forceParallel: Boolean = false,
|
||||
drawBorder: Boolean = true,
|
||||
thickness: Float = 4, col: Color = RGBAColor(150, 150, 150)): Unit = {
|
||||
if (a.collides(b)) return
|
||||
private def portDirections(node: Node, port: NodePort): Array[(Vector2D, Vector2D)] = {
|
||||
val rect = node.bounds
|
||||
val sides = node.portsBounds.find(_._1 == port).get._2
|
||||
.map(rects => rects.edgeCenters.maxBy(c => (c - rect.center).lengthSquared))
|
||||
|
||||
val paths = findSuitableConnections(a, b, checkCollision, forceParallel, clampMin)
|
||||
sides.zipWithIndex.map { case (side, i) =>
|
||||
val center = if (i % 2 == 0) rect.center.copy(x = side.x) else rect.center.copy(y = side.y)
|
||||
(side, center)
|
||||
}
|
||||
}
|
||||
|
||||
private def drawConnection(g: Graphics, aNode: Node, aPort: NodePort, bNode: Node, bPort: NodePort,
|
||||
color: Color = RGBAColor(150, 150, 150)): Unit = {
|
||||
val (aRect, bRect) = (aNode.bounds, bNode.bounds)
|
||||
if (aRect.collides(bRect)) return
|
||||
if (aRect.x < bRect.x) {
|
||||
drawConnection(g, bNode, bPort, aNode, aPort, color)
|
||||
return
|
||||
}
|
||||
|
||||
val paths = findSuitableConnections(portDirections(aNode, aPort),
|
||||
portDirections(bNode, bPort), checkCollision = true, forceParallel = false, clampMin = true)
|
||||
|
||||
if (paths.isEmpty) {
|
||||
val canGiveUp = checkCollision || clampMin
|
||||
if (canGiveUp) {
|
||||
if (drawBorder)
|
||||
g.line(a.center, b.center, thickness + 4, RGBAColorNorm(0.1f, 0.1f, 0.1f))
|
||||
g.line(a.center, b.center, thickness, col)
|
||||
}
|
||||
// give up
|
||||
g.line(aRect.center, bRect.center, 8, RGBAColorNorm(0.1f, 0.1f, 0.1f))
|
||||
g.line(aRect.center, bRect.center, 4, color)
|
||||
} else {
|
||||
val path = paths.minBy(p => {
|
||||
val Array(_, aEnd, bEnd, _) = p
|
||||
(aEnd - bEnd).lengthSquared
|
||||
})
|
||||
val path = paths.minBy { case Array(_, aEnd, bEnd, _) => (aEnd - bEnd).lengthSquared }
|
||||
DrawUtils.polyline(g, path, 8, RGBAColorNorm(0.1f, 0.1f, 0.1f))
|
||||
DrawUtils.polyline(g, path, 4, color)
|
||||
|
||||
if (drawBorder)
|
||||
DrawUtils.polyline(g, path, thickness + 4, RGBAColorNorm(0.1f, 0.1f, 0.1f))
|
||||
DrawUtils.polyline(g, path, thickness, col)
|
||||
val aDir = (path(1) - path(0)).normalizeAxisAligned * 8
|
||||
g.line(path(0), path(0) + aDir, 4, aPort.getColor)
|
||||
val bDir = (path(2) - path(3)).normalizeAxisAligned * 8
|
||||
g.line(path(3), path(3) + bDir, 4, bPort.getColor)
|
||||
}
|
||||
}
|
||||
|
||||
private def drawNewConnectionNoTarget(g: Graphics, node: Node, port: NodePort, endpoint: Vector2D): Unit = {
|
||||
val color = RGBAColorNorm(0.3f, 0.5f, 0.4f)
|
||||
val rect = node.bounds
|
||||
|
||||
val filtered = portDirections(node, port)
|
||||
.map { case (side, center) =>
|
||||
val (len, _) = connectorLen(side, side, endpoint, endpoint, clampMin = true)
|
||||
(center, side, len)
|
||||
}
|
||||
.filter { case (center, side, len) => checkConnectorCollision(side, center, len) }
|
||||
.map { case (center, side, len) =>
|
||||
val end = side + (side - center).normalizeAxisAligned * len
|
||||
(side, end, (end - endpoint).lengthSquared)
|
||||
}
|
||||
.filter { case (side, end, _) => DrawUtils.isValidPolyline(Array(side, end, endpoint)) }
|
||||
|
||||
if (filtered.isEmpty)
|
||||
g.line(rect.center, endpoint, 4, color)
|
||||
else {
|
||||
val (side, end, _) = filtered.minBy(_._3)
|
||||
DrawUtils.polyline(g, Array(side, end, endpoint), 4, color)
|
||||
val dir = (end - side).normalizeAxisAligned * 8
|
||||
g.line(side, side + dir, 4, port.getColor)
|
||||
}
|
||||
}
|
||||
|
||||
private def newConnectionTarget: Option[(Node, NodePort)] = {
|
||||
val (node, _, endpoint) = newConnection.get
|
||||
val validTargets = nodes.iterator.filter(n => n != node && n.bounds.inflate(20).contains(endpoint))
|
||||
|
||||
if (validTargets.nonEmpty) {
|
||||
val target = validTargets.minBy(n => (n.bounds.center - endpoint).lengthSquared)
|
||||
val targetPort = target.portsBounds.flatMap(p => p._2.map(a => (p._1, a)))
|
||||
.minBy(p => (p._2.center - endpoint).lengthSquared)._1
|
||||
Some((target, targetPort))
|
||||
} else
|
||||
None
|
||||
}
|
||||
|
||||
private def drawNewConnection(g: Graphics): Unit = {
|
||||
val (node, port, endpoint) = newConnection.get
|
||||
if (node.bounds.contains(endpoint)) return
|
||||
|
||||
val color = RGBAColorNorm(0.3f, 0.5f, 0.4f)
|
||||
|
||||
newConnectionTarget match {
|
||||
case Some((target, targetPort)) =>
|
||||
drawConnection(g, node, port, target, targetPort, color = color)
|
||||
case None =>
|
||||
drawNewConnectionNoTarget(g, node, port, endpoint)
|
||||
}
|
||||
}
|
||||
|
||||
private def drawSelectorConnection(g: Graphics, aRect: Rect2D, bRect: Rect2D,
|
||||
thickness: Float = 4,
|
||||
color: Color = RGBAColor(150, 150, 150)): Unit = {
|
||||
if (aRect.collides(bRect)) return
|
||||
val (a, b) = if (aRect.x > bRect.x) (aRect, bRect) else (bRect, aRect)
|
||||
|
||||
val paths = findSuitableConnections(a.edgeCenters.map((_, a.center)),
|
||||
b.edgeCenters.map((_, b.center)),
|
||||
checkCollision = false, forceParallel = true, clampMin = false)
|
||||
|
||||
if (paths.nonEmpty) {
|
||||
val path = paths.minBy { case Array(_, aEnd, bEnd, _) => (aEnd - bEnd).lengthSquared }
|
||||
DrawUtils.polyline(g, path, thickness, color)
|
||||
}
|
||||
}
|
||||
|
||||
@ -263,37 +344,43 @@ class WorkspaceView extends Widget with DragHandler with ClickHandler with Hover
|
||||
gridAlpha.goto(0f)
|
||||
}
|
||||
|
||||
val drawn = mutable.HashSet[(Node, Node)]()
|
||||
val drawn = mutable.HashSet[(Node, NodePort, Node, NodePort)]()
|
||||
|
||||
for (node <- nodes) {
|
||||
for (neighbour <- node.environment.node.neighbors)
|
||||
for (other <- nodes.find(_.environment.node == neighbour)) {
|
||||
if (!drawn.contains((node, other)) && !drawn.contains((other, node))) {
|
||||
val gray = ((node.environment.node.address + other.environment.node.address).hashCode % 25 + 133).toShort
|
||||
for (nodeA <- nodes) {
|
||||
nodeA.connections.foreach { case (portA, nodeB, portB) =>
|
||||
if (!drawn.contains((nodeA, portA, nodeB, portB)) && !drawn.contains((nodeB, portB, nodeA, portA))) {
|
||||
val gray = ((nodeA.getShortLabel + nodeB.getShortLabel).hashCode % 25 + 133).toShort
|
||||
val color = RGBAColor(gray, gray, gray)
|
||||
drawn.add((node, other))
|
||||
drawConnection(g, node.bounds, other.bounds, col = color)
|
||||
drawn += ((nodeA, portA, nodeB, portB))
|
||||
drawConnection(g, nodeA, portA, nodeB, portB, color = color)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for ((start, endpoint) <- newConnection) {
|
||||
drawConnection(g, start.bounds, Rect2D(endpoint - Vector2D(32, 32), Size2D(64, 64)),
|
||||
col = RGBAColorNorm(0.3f, 0.5f, 0.4f))
|
||||
if (newConnection.isDefined) {
|
||||
drawNewConnection(g)
|
||||
}
|
||||
|
||||
nodes.foreach(_.draw(g))
|
||||
nodes.foreach(_.drawLabel(g))
|
||||
|
||||
val col = nodeSelector.ringColor
|
||||
portsAlpha.update()
|
||||
portsAlpha.goto(if (newConnection.isDefined) 1 else 0)
|
||||
|
||||
g.save()
|
||||
g.alphaMultiplier *= portsAlpha.value
|
||||
nodes.foreach(_.drawPorts(g))
|
||||
g.restore()
|
||||
|
||||
val color = nodeSelector.ringColor
|
||||
if (nodeSelector.isShown) {
|
||||
g.sprite("nodes/NewNode", newNodePos.x + cameraOffset.x,
|
||||
newNodePos.y + cameraOffset.y, 64, 64, col)
|
||||
newNodePos.y + cameraOffset.y, 64, 64, color)
|
||||
}
|
||||
|
||||
if (nodeSelector.isShown)
|
||||
drawConnection(g, Rect2D(newNodePos.x + cameraOffset.x, newNodePos.y + cameraOffset.y, 64, 64),
|
||||
nodeSelector.bounds, col = col, checkCollision = false, drawBorder = false, forceParallel = true, clampMin = false)
|
||||
drawSelectorConnection(g, Rect2D(newNodePos.x + cameraOffset.x, newNodePos.y + cameraOffset.y, 64, 64),
|
||||
nodeSelector.bounds, color = color)
|
||||
|
||||
drawChildren(g)
|
||||
}
|
||||
|
||||
@ -43,7 +43,7 @@ object DrawUtils {
|
||||
|
||||
def polyline(g: Graphics, points: Array[Vector2D], thickness: Float = 4, color: Color = RGBAColor(255, 255, 255)): Unit = {
|
||||
if (points.length < 2) return
|
||||
if (!isValidPolyline(points)) return
|
||||
// if (!isValidPolyline(points)) return
|
||||
var start = points(0)
|
||||
for ((end, i) <- points.iterator.zipWithIndex.drop(1)) {
|
||||
if (end != points.last) {
|
||||
|
||||
@ -10,5 +10,5 @@ object TierColor {
|
||||
|
||||
val Tiers: Array[Color] = Array(Tier0, Tier1, Tier2, Tier3)
|
||||
|
||||
def get(tier: Int): Color = Tiers(tier.min(3))
|
||||
def get(tier: Int): Color = if (tier >= 0 && tier <= 3) Tiers(tier.min(3)) else IntColor(0xFFFFFF)
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user