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
|
BackgroundPattern 0 0 304 304
|
||||||
Empty 337 197 1 1
|
Empty 337 204 1 1
|
||||||
ShadowBorder 305 197 1 24
|
ShadowBorder 305 204 1 24
|
||||||
ShadowCorner 424 0 24 24
|
ShadowCorner 424 0 24 24
|
||||||
buttons/PowerOff 441 25 18 18
|
buttons/PowerOff 441 25 18 18
|
||||||
buttons/PowerOn 460 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/ComputerErrorOverlay 373 180 16 16
|
||||||
nodes/ComputerOnOverlay 390 180 16 16
|
nodes/ComputerOnOverlay 390 180 16 16
|
||||||
nodes/NewNode 407 180 16 16
|
nodes/NewNode 407 180 16 16
|
||||||
nodes/Screen 424 180 16 16
|
nodes/Relay 424 180 16 16
|
||||||
nodes/ScreenOnOverlay 441 180 16 16
|
nodes/Screen 441 180 16 16
|
||||||
screen/BorderB 310 197 2 8
|
nodes/ScreenOnOverlay 458 180 16 16
|
||||||
screen/BorderT 307 197 2 10
|
screen/BorderB 310 204 2 8
|
||||||
screen/CornerBL 476 180 8 8
|
screen/BorderT 307 204 2 10
|
||||||
screen/CornerBR 485 180 8 8
|
screen/CornerBL 493 180 8 8
|
||||||
screen/CornerTL 458 180 8 10
|
screen/CornerBR 502 180 8 8
|
||||||
screen/CornerTR 467 180 8 10
|
screen/CornerTL 475 180 8 10
|
||||||
window/BorderDark 333 197 1 4
|
screen/CornerTR 484 180 8 10
|
||||||
window/BorderLight 335 197 1 4
|
window/BorderDark 333 204 1 4
|
||||||
window/CloseButton 494 180 7 6
|
window/BorderLight 335 204 1 4
|
||||||
window/CornerBL 313 197 4 4
|
window/CloseButton 305 197 7 6
|
||||||
window/CornerBR 318 197 4 4
|
window/CornerBL 313 204 4 4
|
||||||
window/CornerTL 323 197 4 4
|
window/CornerBR 318 204 4 4
|
||||||
window/CornerTR 328 197 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 = {
|
def manhattanDistanceTo(that: Rect2D): Float = {
|
||||||
((center - that.center).abs - (extent + that.extent)).max(Vector2D(0, 0)).manhattanLength
|
((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
|
package ocelot.desktop.graphics
|
||||||
|
|
||||||
import ocelot.desktop.color.{Color, RGBAColorNorm}
|
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.mesh.{Mesh, MeshInstance}
|
||||||
import ocelot.desktop.graphics.render.InstanceRenderer
|
import ocelot.desktop.graphics.render.InstanceRenderer
|
||||||
import ocelot.desktop.util.{Font, Logging, Spritesheet}
|
import ocelot.desktop.util.{Font, Logging, Spritesheet}
|
||||||
@ -227,6 +227,10 @@ class Graphics extends Logging {
|
|||||||
_rect(x, y, width, height)
|
_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 = {
|
def rect(x: Float, y: Float, width: Float, height: Float, color: Color = RGBAColorNorm(1f, 1f, 1f)): Unit = {
|
||||||
sprite("Empty", x, y, width, height, color)
|
sprite("Empty", x, y, width, height, color)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,18 +1,21 @@
|
|||||||
package ocelot.desktop.node
|
package ocelot.desktop.node
|
||||||
|
|
||||||
import ocelot.desktop.color.{Color, RGBAColor}
|
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.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.handlers.{ClickHandler, DragHandler, HoverHandler}
|
||||||
import ocelot.desktop.ui.event.sources.KeyEvents
|
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.window.Window
|
||||||
|
import ocelot.desktop.ui.widget.{Widget, WorkspaceView}
|
||||||
import ocelot.desktop.util.DrawUtils
|
import ocelot.desktop.util.DrawUtils
|
||||||
import ocelot.desktop.util.animation.ColorAnimation
|
import ocelot.desktop.util.animation.ColorAnimation
|
||||||
import org.apache.commons.lang3.StringUtils
|
import org.apache.commons.lang3.StringUtils
|
||||||
import org.lwjgl.input.Keyboard
|
import org.lwjgl.input.Keyboard
|
||||||
import totoro.ocelot.brain.entity.traits.Environment
|
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 {
|
trait Node extends Widget with DragHandler with ClickHandler with HoverHandler {
|
||||||
var workspaceView: WorkspaceView = _
|
var workspaceView: WorkspaceView = _
|
||||||
@ -25,6 +28,8 @@ trait Node extends Widget with DragHandler with ClickHandler with HoverHandler {
|
|||||||
private var isMoving = false
|
private var isMoving = false
|
||||||
private var grabPoint: Vector2D = Vector2D(0, 0)
|
private var grabPoint: Vector2D = Vector2D(0, 0)
|
||||||
|
|
||||||
|
protected val _connections: ArrayBuffer[(NodePort, Node, NodePort)] = ArrayBuffer[(NodePort, Node, NodePort)]()
|
||||||
|
|
||||||
size = minimumSize
|
size = minimumSize
|
||||||
|
|
||||||
override def receiveMouseEvents = true
|
override def receiveMouseEvents = true
|
||||||
@ -37,6 +42,9 @@ trait Node extends Widget with DragHandler with ClickHandler with HoverHandler {
|
|||||||
if (!KeyEvents.isDown(Keyboard.KEY_LSHIFT)) {
|
if (!KeyEvents.isDown(Keyboard.KEY_LSHIFT)) {
|
||||||
grabPoint = pos - position
|
grabPoint = pos - position
|
||||||
startMoving()
|
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) =>
|
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)
|
workspaceView.resolveCollision(this)
|
||||||
if (workspaceView.collides(this) || (position - desiredPos).lengthSquared > 50 * 50)
|
if (workspaceView.collides(this) || (position - desiredPos).lengthSquared > 50 * 50)
|
||||||
position = oldPos
|
position = oldPos
|
||||||
// UiHandler.cursor = Cursor.Hand
|
|
||||||
} else {
|
} else {
|
||||||
workspaceView.newConnection = Some((this, pos))
|
workspaceView.newConnection = Some((this, workspaceView.newConnection.get._2, pos))
|
||||||
}
|
}
|
||||||
|
|
||||||
case DragEvent(DragEvent.State.Stop, MouseEvent.Button.Left, _) =>
|
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 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
|
// noinspection VarCouldBeVal
|
||||||
var label: Option[String] = None
|
var label: Option[String] = None
|
||||||
|
|
||||||
@ -98,7 +168,7 @@ trait Node extends Widget with DragHandler with ClickHandler with HoverHandler {
|
|||||||
highlight.goto(NoHighlight)
|
highlight.goto(NoHighlight)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected def getShortLabel: String =
|
def getShortLabel: String =
|
||||||
StringUtils.substring(label.orElse(Option(environment.node.address)).getOrElse("unknown"), 0, 8)
|
StringUtils.substring(label.orElse(Option(environment.node.address)).getOrElse("unknown"), 0, 8)
|
||||||
|
|
||||||
override def draw(g: Graphics): Unit = {
|
override def draw(g: Graphics): Unit = {
|
||||||
@ -115,5 +185,13 @@ trait Node extends Widget with DragHandler with ClickHandler with HoverHandler {
|
|||||||
g.setNormalFont()
|
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
|
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
|
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
|
import scala.collection.mutable
|
||||||
|
|
||||||
@ -11,6 +12,10 @@ object NodeRegistry {
|
|||||||
types += t
|
types += t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
register(NodeType("Relay", "nodes/Relay", -1, () => {
|
||||||
|
new RelayNode(new Relay)
|
||||||
|
}))
|
||||||
|
|
||||||
for (tier <- 0 to 2) {
|
for (tier <- 0 to 2) {
|
||||||
register(NodeType("Screen" + tier, "nodes/Screen", tier, () => {
|
register(NodeType("Screen" + tier, "nodes/Screen", tier, () => {
|
||||||
new ScreenNode(new 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.OcelotDesktop
|
||||||
import ocelot.desktop.color.Color
|
import ocelot.desktop.color.Color
|
||||||
import ocelot.desktop.graphics.Graphics
|
import ocelot.desktop.graphics.Graphics
|
||||||
|
import ocelot.desktop.node.Node
|
||||||
import ocelot.desktop.util.TierColor
|
import ocelot.desktop.util.TierColor
|
||||||
import totoro.ocelot.brain.entity.traits.Computer
|
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.loot.Loot
|
||||||
import totoro.ocelot.brain.util.Tier
|
import totoro.ocelot.brain.util.Tier
|
||||||
|
|
||||||
@ -17,6 +18,7 @@ class ComputerNode(val computer: Case) extends Node {
|
|||||||
computer.add(new InternetCard)
|
computer.add(new InternetCard)
|
||||||
computer.add(new Memory(Tier.Six))
|
computer.add(new Memory(Tier.Six))
|
||||||
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"))
|
computer.add(new HDDManaged(java.util.UUID.randomUUID().toString, Tier.Three, "hello"))
|
||||||
private val eeprom = Loot.OpenOsEEPROM.create()
|
private val eeprom = Loot.OpenOsEEPROM.create()
|
||||||
eeprom.asInstanceOf[EEPROM].readonly = false
|
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.geometry.Size2D
|
||||||
import ocelot.desktop.graphics.Graphics
|
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.OcelotDesktop
|
||||||
import ocelot.desktop.color.Color
|
import ocelot.desktop.color.Color
|
||||||
import ocelot.desktop.graphics.Graphics
|
import ocelot.desktop.graphics.Graphics
|
||||||
|
import ocelot.desktop.node.Node
|
||||||
import ocelot.desktop.util.TierColor
|
import ocelot.desktop.util.TierColor
|
||||||
import totoro.ocelot.brain.entity.traits.Environment
|
import totoro.ocelot.brain.entity.traits.Environment
|
||||||
import totoro.ocelot.brain.entity.{Keyboard, Screen}
|
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.color.{IntColor, RGBAColor, RGBAColorNorm}
|
||||||
import ocelot.desktop.geometry.{Rect2D, Size2D, Vector2D}
|
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.color.{Color, RGBAColor, RGBAColorNorm}
|
||||||
import ocelot.desktop.geometry.{Rect2D, Size2D, Vector2D}
|
import ocelot.desktop.geometry.{Rect2D, Size2D, Vector2D}
|
||||||
import ocelot.desktop.graphics.Graphics
|
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._
|
||||||
import ocelot.desktop.ui.event.handlers.{ClickHandler, DragHandler, HoverHandler}
|
import ocelot.desktop.ui.event.handlers.{ClickHandler, DragHandler, HoverHandler}
|
||||||
import ocelot.desktop.ui.layout.{CopyLayout, Layout}
|
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
|
val nodeSelector = new NodeSelector
|
||||||
|
|
||||||
var cameraOffset: Vector2D = Vector2D(0, 0)
|
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 var newNodePos = Vector2D(0, 0)
|
||||||
private val gridAlpha = new ValueAnimation(0, 1f)
|
private val gridAlpha = new ValueAnimation(0, 1f)
|
||||||
|
private val portsAlpha = new ValueAnimation(0, 7f)
|
||||||
|
|
||||||
override protected val layout: Layout = new CopyLayout(this)
|
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 = {
|
def buildNewConnection(): Unit = {
|
||||||
val (start, endPoint) = newConnection.get
|
val (node, port, _) = newConnection.get
|
||||||
val end = nodes.find(_.bounds.contains(endPoint))
|
newConnectionTarget match {
|
||||||
if (end.isDefined && end.get != start) {
|
case Some((target, targetPort)) =>
|
||||||
if (end.get.environment.node.neighbors.exists(_ == start.environment.node))
|
if (node.isConnected(port, target, targetPort))
|
||||||
end.get.environment.disconnect(start.environment)
|
node.disconnect(port, target, targetPort)
|
||||||
else
|
else
|
||||||
end.get.environment.connect(start.environment)
|
node.connect(port, target, targetPort)
|
||||||
|
case None =>
|
||||||
}
|
}
|
||||||
newConnection = None
|
newConnection = None
|
||||||
}
|
}
|
||||||
|
|
||||||
def createDefaultWorkspace(): Unit = {
|
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))
|
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
|
override def receiveMouseEvents = true
|
||||||
@ -144,40 +147,39 @@ class WorkspaceView extends Widget with DragHandler with ClickHandler with Hover
|
|||||||
!nodes.exists(_.bounds.collides(clearance))
|
!nodes.exists(_.bounds.collides(clearance))
|
||||||
}
|
}
|
||||||
|
|
||||||
private def findSuitableConnections(a: Rect2D, b: Rect2D, checkCollision: Boolean,
|
private def findSuitableConnections(a: Array[(Vector2D, Vector2D)],
|
||||||
forceParallel: Boolean, clampMin: Boolean): Iterator[Array[Vector2D]] = {
|
b: Array[(Vector2D, Vector2D)],
|
||||||
var pairs =
|
checkCollision: Boolean,
|
||||||
for (aSide <- a.edgeCenters.iterator; bSide <- b.edgeCenters.iterator)
|
forceParallel: Boolean,
|
||||||
yield (aSide, bSide)
|
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)
|
if (checkCollision)
|
||||||
pairs = pairs.filter(pair => {
|
iter = iter.filter { case ((aSide, aCenter, aLen), (bSide, bCenter, bLen)) =>
|
||||||
val (aSide, bSide) = pair
|
checkConnectorCollision(aSide, aCenter, aLen) &&
|
||||||
val (aLen, bLen) = connectorLen(a.center, aSide, b.center, bSide, clampMin)
|
checkConnectorCollision(bSide, bCenter, bLen)
|
||||||
checkConnectorCollision(aSide, a.center, aLen) &&
|
}
|
||||||
checkConnectorCollision(bSide, b.center, bLen)
|
|
||||||
})
|
|
||||||
|
|
||||||
var paths = pairs.map(p => {
|
var paths = iter.map { case ((aSide, aCenter, aLen), (bSide, bCenter, bLen)) =>
|
||||||
val (aLen, bLen) = connectorLen(a.center, p._1, b.center, p._2, clampMin)
|
val aEnd = aSide + (aSide - aCenter).normalizeAxisAligned * aLen
|
||||||
val aEnd = p._1 + (p._1 - a.center).normalizeAxisAligned * aLen
|
val bEnd = bSide + (bSide - bCenter).normalizeAxisAligned * bLen
|
||||||
val bEnd = p._2 + (p._2 - b.center).normalizeAxisAligned * bLen
|
Array(aSide, aEnd, bEnd, bSide)
|
||||||
Array(p._1, aEnd, bEnd, p._2)
|
}.filter(DrawUtils.isValidPolyline)
|
||||||
}).filter(DrawUtils.isValidPolyline)
|
|
||||||
|
|
||||||
if (forceParallel)
|
if (forceParallel)
|
||||||
paths = paths.filter(p => {
|
paths = paths.filter { case Array(aStart, aEnd, bEnd, bStart) =>
|
||||||
val Array(aStart, aEnd, bEnd, bStart) = p
|
(aEnd - aStart).dot(bEnd - bStart) != 0f
|
||||||
val aDir = aEnd - aStart
|
}
|
||||||
val bDir = bEnd - bStart
|
|
||||||
aDir.dot(bDir) != 0f
|
|
||||||
})
|
|
||||||
|
|
||||||
paths
|
paths
|
||||||
}
|
}
|
||||||
|
|
||||||
private def connectorLen(aCenter: Vector2D, aSide: Vector2D,
|
private def connectorLen(aSide: Vector2D, aCenter: Vector2D,
|
||||||
bCenter: Vector2D, bSide: Vector2D,
|
bSide: Vector2D, bCenter: Vector2D,
|
||||||
clampMin: Boolean): (Float, Float) = {
|
clampMin: Boolean): (Float, Float) = {
|
||||||
val aDir = aSide - aCenter
|
val aDir = aSide - aCenter
|
||||||
val bDir = bSide - bCenter
|
val bDir = bSide - bCenter
|
||||||
@ -191,32 +193,111 @@ class WorkspaceView extends Widget with DragHandler with ClickHandler with Hover
|
|||||||
(aLen.min(40f), bLen.min(40f))
|
(aLen.min(40f), bLen.min(40f))
|
||||||
}
|
}
|
||||||
|
|
||||||
def drawConnection(g: Graphics, a: Rect2D, b: Rect2D,
|
private def portDirections(node: Node, port: NodePort): Array[(Vector2D, Vector2D)] = {
|
||||||
clampMin: Boolean = true,
|
val rect = node.bounds
|
||||||
checkCollision: Boolean = true,
|
val sides = node.portsBounds.find(_._1 == port).get._2
|
||||||
forceParallel: Boolean = false,
|
.map(rects => rects.edgeCenters.maxBy(c => (c - rect.center).lengthSquared))
|
||||||
drawBorder: Boolean = true,
|
|
||||||
thickness: Float = 4, col: Color = RGBAColor(150, 150, 150)): Unit = {
|
|
||||||
if (a.collides(b)) return
|
|
||||||
|
|
||||||
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) {
|
if (paths.isEmpty) {
|
||||||
val canGiveUp = checkCollision || clampMin
|
// give up
|
||||||
if (canGiveUp) {
|
g.line(aRect.center, bRect.center, 8, RGBAColorNorm(0.1f, 0.1f, 0.1f))
|
||||||
if (drawBorder)
|
g.line(aRect.center, bRect.center, 4, color)
|
||||||
g.line(a.center, b.center, thickness + 4, RGBAColorNorm(0.1f, 0.1f, 0.1f))
|
|
||||||
g.line(a.center, b.center, thickness, col)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
val path = paths.minBy(p => {
|
val path = paths.minBy { case Array(_, aEnd, bEnd, _) => (aEnd - bEnd).lengthSquared }
|
||||||
val Array(_, aEnd, bEnd, _) = p
|
DrawUtils.polyline(g, path, 8, RGBAColorNorm(0.1f, 0.1f, 0.1f))
|
||||||
(aEnd - bEnd).lengthSquared
|
DrawUtils.polyline(g, path, 4, color)
|
||||||
})
|
|
||||||
|
|
||||||
if (drawBorder)
|
val aDir = (path(1) - path(0)).normalizeAxisAligned * 8
|
||||||
DrawUtils.polyline(g, path, thickness + 4, RGBAColorNorm(0.1f, 0.1f, 0.1f))
|
g.line(path(0), path(0) + aDir, 4, aPort.getColor)
|
||||||
DrawUtils.polyline(g, path, thickness, col)
|
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)
|
gridAlpha.goto(0f)
|
||||||
}
|
}
|
||||||
|
|
||||||
val drawn = mutable.HashSet[(Node, Node)]()
|
val drawn = mutable.HashSet[(Node, NodePort, Node, NodePort)]()
|
||||||
|
|
||||||
for (node <- nodes) {
|
for (nodeA <- nodes) {
|
||||||
for (neighbour <- node.environment.node.neighbors)
|
nodeA.connections.foreach { case (portA, nodeB, portB) =>
|
||||||
for (other <- nodes.find(_.environment.node == neighbour)) {
|
if (!drawn.contains((nodeA, portA, nodeB, portB)) && !drawn.contains((nodeB, portB, nodeA, portA))) {
|
||||||
if (!drawn.contains((node, other)) && !drawn.contains((other, node))) {
|
val gray = ((nodeA.getShortLabel + nodeB.getShortLabel).hashCode % 25 + 133).toShort
|
||||||
val gray = ((node.environment.node.address + other.environment.node.address).hashCode % 25 + 133).toShort
|
val color = RGBAColor(gray, gray, gray)
|
||||||
val color = RGBAColor(gray, gray, gray)
|
drawn += ((nodeA, portA, nodeB, portB))
|
||||||
drawn.add((node, other))
|
drawConnection(g, nodeA, portA, nodeB, portB, color = color)
|
||||||
drawConnection(g, node.bounds, other.bounds, col = color)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for ((start, endpoint) <- newConnection) {
|
if (newConnection.isDefined) {
|
||||||
drawConnection(g, start.bounds, Rect2D(endpoint - Vector2D(32, 32), Size2D(64, 64)),
|
drawNewConnection(g)
|
||||||
col = RGBAColorNorm(0.3f, 0.5f, 0.4f))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
nodes.foreach(_.draw(g))
|
nodes.foreach(_.draw(g))
|
||||||
nodes.foreach(_.drawLabel(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) {
|
if (nodeSelector.isShown) {
|
||||||
g.sprite("nodes/NewNode", newNodePos.x + cameraOffset.x,
|
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)
|
if (nodeSelector.isShown)
|
||||||
drawConnection(g, Rect2D(newNodePos.x + cameraOffset.x, newNodePos.y + cameraOffset.y, 64, 64),
|
drawSelectorConnection(g, Rect2D(newNodePos.x + cameraOffset.x, newNodePos.y + cameraOffset.y, 64, 64),
|
||||||
nodeSelector.bounds, col = col, checkCollision = false, drawBorder = false, forceParallel = true, clampMin = false)
|
nodeSelector.bounds, color = color)
|
||||||
|
|
||||||
drawChildren(g)
|
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 = {
|
def polyline(g: Graphics, points: Array[Vector2D], thickness: Float = 4, color: Color = RGBAColor(255, 255, 255)): Unit = {
|
||||||
if (points.length < 2) return
|
if (points.length < 2) return
|
||||||
if (!isValidPolyline(points)) return
|
// if (!isValidPolyline(points)) return
|
||||||
var start = points(0)
|
var start = points(0)
|
||||||
for ((end, i) <- points.iterator.zipWithIndex.drop(1)) {
|
for ((end, i) <- points.iterator.zipWithIndex.drop(1)) {
|
||||||
if (end != points.last) {
|
if (end != points.last) {
|
||||||
|
|||||||
@ -10,5 +10,5 @@ object TierColor {
|
|||||||
|
|
||||||
val Tiers: Array[Color] = Array(Tier0, Tier1, Tier2, Tier3)
|
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