Show FM/AM routing
BIN
sprites/icons/WireArrowLeft.png
Normal file
|
After Width: | Height: | Size: 498 B |
BIN
sprites/icons/WireArrowRight.png
Normal file
|
After Width: | Height: | Size: 507 B |
|
Before Width: | Height: | Size: 539 B After Width: | Height: | Size: 515 B |
|
Before Width: | Height: | Size: 531 B After Width: | Height: | Size: 530 B |
@ -123,7 +123,15 @@ PlotFill = #ccfdcc25
|
|||||||
PlotLine = #ccfdcc
|
PlotLine = #ccfdcc
|
||||||
PlotText = #333333
|
PlotText = #333333
|
||||||
|
|
||||||
WaveOff = #33663340
|
SoundCardWaveOff = #33663340
|
||||||
WaveActive = #339933
|
SoundCardWaveActive = #339933
|
||||||
|
|
||||||
WireOff = #929292
|
SoundCardWireOff = #929292
|
||||||
|
SoundCardWire0 = #237f23
|
||||||
|
SoundCardWire1 = #237f77
|
||||||
|
SoundCardWire2 = #23567f
|
||||||
|
SoundCardWire3 = #69237f
|
||||||
|
SoundCardWire4 = #7f2331
|
||||||
|
SoundCardWire5 = #7f5123
|
||||||
|
SoundCardWire6 = #7f7723
|
||||||
|
SoundCardWire7 = #000000
|
||||||
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB |
@ -1,7 +1,7 @@
|
|||||||
BackgroundPattern 0 0 304 304
|
BackgroundPattern 0 0 304 304
|
||||||
BarSegment 29 355 16 4
|
BarSegment 29 355 16 4
|
||||||
ComputerMotherboard 305 0 79 70
|
ComputerMotherboard 305 0 79 70
|
||||||
Empty 12 405 1 1
|
Empty 12 414 1 1
|
||||||
EmptySlot 458 147 18 18
|
EmptySlot 458 147 18 18
|
||||||
Knob 385 0 50 50
|
Knob 385 0 50 50
|
||||||
KnobCenter 436 0 50 50
|
KnobCenter 436 0 50 50
|
||||||
@ -43,6 +43,8 @@ icons/WaveSawtooth 86 305 24 10
|
|||||||
icons/WaveSine 111 305 24 10
|
icons/WaveSine 111 305 24 10
|
||||||
icons/WaveSquare 136 305 24 10
|
icons/WaveSquare 136 305 24 10
|
||||||
icons/WaveTriangle 161 305 24 10
|
icons/WaveTriangle 161 305 24 10
|
||||||
|
icons/WireArrowLeft 2 394 4 8
|
||||||
|
icons/WireArrowRight 7 394 4 8
|
||||||
items/APU0 373 122 16 96
|
items/APU0 373 122 16 96
|
||||||
items/APU1 390 122 16 96
|
items/APU1 390 122 16 96
|
||||||
items/APU2 407 122 16 96
|
items/APU2 407 122 16 96
|
||||||
@ -101,17 +103,17 @@ items/Server3 424 105 16 16
|
|||||||
items/SoundCard 356 122 16 128
|
items/SoundCard 356 122 16 128
|
||||||
items/WirelessNetworkCard0 441 105 16 16
|
items/WirelessNetworkCard0 441 105 16 16
|
||||||
items/WirelessNetworkCard1 458 105 16 16
|
items/WirelessNetworkCard1 458 105 16 16
|
||||||
light-panel/BookmarkLeft 391 219 20 14
|
light-panel/BookmarkLeft 17 305 18 14
|
||||||
light-panel/BookmarkRight 17 305 18 14
|
light-panel/BookmarkRight 391 219 20 14
|
||||||
light-panel/BorderB 8 394 4 4
|
light-panel/BorderB 8 403 4 4
|
||||||
light-panel/BorderL 98 394 4 2
|
light-panel/BorderL 98 403 4 2
|
||||||
light-panel/BorderR 13 394 4 4
|
light-panel/BorderR 13 403 4 4
|
||||||
light-panel/BorderT 18 394 4 4
|
light-panel/BorderT 18 403 4 4
|
||||||
light-panel/CornerBL 23 394 4 4
|
light-panel/CornerBL 23 403 4 4
|
||||||
light-panel/CornerBR 28 394 4 4
|
light-panel/CornerBR 28 403 4 4
|
||||||
light-panel/CornerTL 33 394 4 4
|
light-panel/CornerTL 33 403 4 4
|
||||||
light-panel/CornerTR 38 394 4 4
|
light-panel/CornerTR 38 403 4 4
|
||||||
light-panel/Fill 6 405 2 2
|
light-panel/Fill 6 414 2 2
|
||||||
light-panel/Vent 0 355 2 38
|
light-panel/Vent 0 355 2 38
|
||||||
nodes/Cable 3 366 8 8
|
nodes/Cable 3 366 8 8
|
||||||
nodes/Computer 475 105 16 16
|
nodes/Computer 475 105 16 16
|
||||||
@ -127,26 +129,26 @@ nodes/NoteBlock 453 51 16 16
|
|||||||
nodes/Relay 470 51 16 16
|
nodes/Relay 470 51 16 16
|
||||||
nodes/Screen 487 51 16 16
|
nodes/Screen 487 51 16 16
|
||||||
nodes/ScreenOnOverlay 0 305 16 16
|
nodes/ScreenOnOverlay 0 305 16 16
|
||||||
panel/BorderB 43 394 4 4
|
panel/BorderB 43 403 4 4
|
||||||
panel/BorderL 103 394 4 2
|
panel/BorderL 103 403 4 2
|
||||||
panel/BorderR 48 394 4 4
|
panel/BorderR 48 403 4 4
|
||||||
panel/BorderT 53 394 4 4
|
panel/BorderT 53 403 4 4
|
||||||
panel/CornerBL 58 394 4 4
|
panel/CornerBL 58 403 4 4
|
||||||
panel/CornerBR 63 394 4 4
|
panel/CornerBR 63 403 4 4
|
||||||
panel/CornerTL 68 394 4 4
|
panel/CornerTL 68 403 4 4
|
||||||
panel/CornerTR 73 394 4 4
|
panel/CornerTR 73 403 4 4
|
||||||
panel/Fill 9 405 2 2
|
panel/Fill 9 414 2 2
|
||||||
particles/Note 21 355 7 10
|
particles/Note 21 355 7 10
|
||||||
screen/BorderB 5 394 2 8
|
screen/BorderB 5 403 2 8
|
||||||
screen/BorderT 2 394 2 10
|
screen/BorderT 2 403 2 10
|
||||||
screen/CornerBL 12 366 8 8
|
screen/CornerBL 12 366 8 8
|
||||||
screen/CornerBR 21 366 8 8
|
screen/CornerBR 21 366 8 8
|
||||||
screen/CornerTL 3 355 8 10
|
screen/CornerTL 3 355 8 10
|
||||||
screen/CornerTR 12 355 8 10
|
screen/CornerTR 12 355 8 10
|
||||||
window/BorderDark 2 405 1 4
|
window/BorderDark 2 414 1 4
|
||||||
window/BorderLight 4 405 1 4
|
window/BorderLight 4 414 1 4
|
||||||
window/CloseButton 30 366 7 6
|
window/CloseButton 30 366 7 6
|
||||||
window/CornerBL 78 394 4 4
|
window/CornerBL 78 403 4 4
|
||||||
window/CornerBR 83 394 4 4
|
window/CornerBR 83 403 4 4
|
||||||
window/CornerTL 88 394 4 4
|
window/CornerTL 88 403 4 4
|
||||||
window/CornerTR 93 394 4 4
|
window/CornerTR 93 403 4 4
|
||||||
|
|||||||
@ -2,7 +2,7 @@ package ocelot.desktop.ui.widget.card
|
|||||||
|
|
||||||
import ocelot.desktop.ColorScheme
|
import ocelot.desktop.ColorScheme
|
||||||
import ocelot.desktop.color.Color
|
import ocelot.desktop.color.Color
|
||||||
import ocelot.desktop.geometry.{Padding2D, Rect2D, Size2D}
|
import ocelot.desktop.geometry.{Padding2D, Rect2D, Size2D, Vector2D}
|
||||||
import ocelot.desktop.graphics.Graphics
|
import ocelot.desktop.graphics.Graphics
|
||||||
import ocelot.desktop.ui.layout.{Layout, LinearLayout}
|
import ocelot.desktop.ui.layout.{Layout, LinearLayout}
|
||||||
import ocelot.desktop.ui.widget.card.SoundCardWindow._
|
import ocelot.desktop.ui.widget.card.SoundCardWindow._
|
||||||
@ -31,6 +31,12 @@ class SoundCardWindow(card: SoundCard) extends BasicWindow {
|
|||||||
new Channel(proc, osc)
|
new Channel(proc, osc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val patchbays: Array[Patchbay] = (0 until numChannels / 2)
|
||||||
|
.map(i => new Patchbay(2 * i, 2 * i + 1, isFirst = i == 0, isLast = i == numChannels / 2 - 1))
|
||||||
|
.toArray
|
||||||
|
|
||||||
|
private val existingConnectors = new mutable.ArrayBuffer[Connector]()
|
||||||
|
|
||||||
private var eventSub: Option[EventBus.Subscription] = None
|
private var eventSub: Option[EventBus.Subscription] = None
|
||||||
|
|
||||||
children :+= new PaddingBox(new Widget {
|
children :+= new PaddingBox(new Widget {
|
||||||
@ -44,20 +50,13 @@ class SoundCardWindow(card: SoundCard) extends BasicWindow {
|
|||||||
|
|
||||||
children :+= new PaddingBox(masterOscilloscope, Padding2D(left = 4, right = 4))
|
children :+= new PaddingBox(masterOscilloscope, Padding2D(left = 4, right = 4))
|
||||||
|
|
||||||
for (i <- process.channels.indices by 2) {
|
for (i <- 0 until numChannels / 2) {
|
||||||
children :+= new Widget {
|
children :+= new Widget {
|
||||||
children :+= channelWidgets(i)
|
children :+= channelWidgets(2 * i)
|
||||||
children :+= new Patchbay(i, i + 1, isFirst = i == 0, isLast = i == numChannels - 2)
|
children :+= patchbays(i)
|
||||||
children :+= channelWidgets(i + 1)
|
children :+= channelWidgets(2 * i + 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (numChannels % 2 == 1) {
|
|
||||||
val i = numChannels - 1
|
|
||||||
children :+= channelWidgets(i)
|
|
||||||
children :+= new Patchbay(i, -1, isFirst = false, isLast = true)
|
|
||||||
children :+= new Widget
|
|
||||||
}
|
|
||||||
}, Padding2D.equal(8))
|
}, Padding2D.equal(8))
|
||||||
|
|
||||||
private def numChannels: Int = process.channels.length
|
private def numChannels: Int = process.channels.length
|
||||||
@ -84,9 +83,118 @@ class SoundCardWindow(card: SoundCard) extends BasicWindow {
|
|||||||
beginDraw(g)
|
beginDraw(g)
|
||||||
DrawUtils.windowWithShadow(g, position.x, position.y, size.width, size.height, 1f, 0.5f)
|
DrawUtils.windowWithShadow(g, position.x, position.y, size.width, size.height, 1f, 0.5f)
|
||||||
drawChildren(g)
|
drawChildren(g)
|
||||||
|
drawConnectors(g)
|
||||||
endDraw(g)
|
endDraw(g)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private def drawConnectors(g: Graphics): Unit = {
|
||||||
|
val initColor = ColorScheme("Label")
|
||||||
|
for (i <- 0 until numChannels) {
|
||||||
|
setColCh(i, initColor)
|
||||||
|
setColAm(i, initColor)
|
||||||
|
setColFm(i, initColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
existingConnectors.clear()
|
||||||
|
|
||||||
|
var colorIdx = 0
|
||||||
|
var color = ColorScheme("SoundCardWire0")
|
||||||
|
val px = patchbays(0).posLeftCh.x
|
||||||
|
|
||||||
|
for (i <- 0 until numChannels) {
|
||||||
|
var hadAny = false
|
||||||
|
|
||||||
|
for (j <- 0 until numChannels) {
|
||||||
|
if (i != j) {
|
||||||
|
val dstCh = process.channels(j)
|
||||||
|
if (dstCh.amplitudeMod.exists(_.modulatorIndex == i)) {
|
||||||
|
setColAm(j, color)
|
||||||
|
drawConnector(g, px, posCh(i), posAm(j), colorIdx)
|
||||||
|
hadAny = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dstCh.frequencyMod.exists(_.modulatorIndex == i)) {
|
||||||
|
setColFm(j, color)
|
||||||
|
drawConnector(g, px, posCh(i), posFm(j), colorIdx)
|
||||||
|
hadAny = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hadAny) {
|
||||||
|
setColCh(i, color)
|
||||||
|
colorIdx += 1
|
||||||
|
color = ColorScheme(s"SoundCardWire${colorIdx % 8}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def drawConnector(g: Graphics, px: Float, a: Vector2D, b: Vector2D, colorIdx: Int): Unit = {
|
||||||
|
var from = a.y
|
||||||
|
var to = b.y
|
||||||
|
if (from > to) {
|
||||||
|
from = b.y
|
||||||
|
to = a.y
|
||||||
|
}
|
||||||
|
|
||||||
|
var column = 0
|
||||||
|
val existing = existingConnectors.find(con => con.colorIdx == colorIdx)
|
||||||
|
if (existing.isDefined) {
|
||||||
|
column = existing.get.column
|
||||||
|
} else {
|
||||||
|
var done = false
|
||||||
|
while (column >= 0 && column < 6 && !done) {
|
||||||
|
val conflict = existingConnectors.exists(con => {
|
||||||
|
con.column == column && con.from <= to && con.to >= from
|
||||||
|
})
|
||||||
|
if (conflict) {
|
||||||
|
column += 1
|
||||||
|
} else {
|
||||||
|
done = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val mx = px + column * 4 + 9
|
||||||
|
val col = ColorScheme(s"SoundCardWire${colorIdx % 8}")
|
||||||
|
|
||||||
|
g.line(a, Vector2D(mx, a.y), 2, col)
|
||||||
|
g.line(Vector2D(mx, from - 1), Vector2D(mx, to + 1), 2, col)
|
||||||
|
g.line(b, Vector2D(mx, b.y), 2, col)
|
||||||
|
|
||||||
|
if (b.x > px) {
|
||||||
|
g.sprite("icons/WireArrowRight", b.x - 4, b.y - 4, col)
|
||||||
|
} else {
|
||||||
|
g.sprite("icons/WireArrowLeft", b.x, b.y - 4, col)
|
||||||
|
}
|
||||||
|
|
||||||
|
existingConnectors += Connector(column, colorIdx, from, to)
|
||||||
|
}
|
||||||
|
|
||||||
|
private def posCh(i: Int): Vector2D = {
|
||||||
|
if (i % 2 == 0) patchbays(i / 2).posLeftCh else patchbays(i / 2).posRightCh
|
||||||
|
}
|
||||||
|
|
||||||
|
private def posAm(i: Int): Vector2D = {
|
||||||
|
if (i % 2 == 0) patchbays(i / 2).posLeftAm else patchbays(i / 2).posRightAm
|
||||||
|
}
|
||||||
|
|
||||||
|
private def posFm(i: Int): Vector2D = {
|
||||||
|
if (i % 2 == 0) patchbays(i / 2).posLeftFm else patchbays(i / 2).posRightFm
|
||||||
|
}
|
||||||
|
|
||||||
|
private def setColCh(i: Int, col: Color): Unit = {
|
||||||
|
if (i % 2 == 0) patchbays(i / 2).colLeftCh = col else patchbays(i / 2).colRightCh = col
|
||||||
|
}
|
||||||
|
|
||||||
|
private def setColAm(i: Int, col: Color): Unit = {
|
||||||
|
if (i % 2 == 0) patchbays(i / 2).colLeftAm = col else patchbays(i / 2).colRightAm = col
|
||||||
|
}
|
||||||
|
|
||||||
|
private def setColFm(i: Int, col: Color): Unit = {
|
||||||
|
if (i % 2 == 0) patchbays(i / 2).colLeftFm = col else patchbays(i / 2).colRightFm = col
|
||||||
|
}
|
||||||
|
|
||||||
override def update(): Unit = {
|
override def update(): Unit = {
|
||||||
super.update()
|
super.update()
|
||||||
|
|
||||||
@ -183,7 +291,7 @@ object SoundCardWindow {
|
|||||||
case ADSREnvelope.Phase.Release => releaseStart + elapsedMs
|
case ADSREnvelope.Phase.Release => releaseStart + elapsedMs
|
||||||
}
|
}
|
||||||
|
|
||||||
var col = ColorScheme("WaveActive").withAlpha(0.6f)
|
var col = ColorScheme("SoundCardWaveActive").withAlpha(0.6f)
|
||||||
for (sx <- 0 until bounds.w.toInt) {
|
for (sx <- 0 until bounds.w.toInt) {
|
||||||
val t = (sx / bounds.w) * envDuration
|
val t = (sx / bounds.w) * envDuration
|
||||||
if (t >= elapsedTime) col = col.withAlpha(0.2f)
|
if (t >= elapsedTime) col = col.withAlpha(0.2f)
|
||||||
@ -202,7 +310,7 @@ object SoundCardWindow {
|
|||||||
val ay = bounds.y + bounds.h
|
val ay = bounds.y + bounds.h
|
||||||
val by = bounds.y
|
val by = bounds.y
|
||||||
val cy = bounds.y + (1 - env.sustain) * bounds.h
|
val cy = bounds.y + (1 - env.sustain) * bounds.h
|
||||||
col = ColorScheme("WaveActive")
|
col = ColorScheme("SoundCardWaveActive")
|
||||||
|
|
||||||
var ax = bounds.x
|
var ax = bounds.x
|
||||||
var bx = bounds.x + env.attack * unit
|
var bx = bounds.x + env.attack * unit
|
||||||
@ -233,7 +341,7 @@ object SoundCardWindow {
|
|||||||
override def draw(g: Graphics): Unit = {
|
override def draw(g: Graphics): Unit = {
|
||||||
for (((icon, clazz), i) <- waves.zipWithIndex) {
|
for (((icon, clazz), i) <- waves.zipWithIndex) {
|
||||||
val active = clazz.isInstance(channel.generator)
|
val active = clazz.isInstance(channel.generator)
|
||||||
val color = if (active) ColorScheme("WaveActive") else ColorScheme("WaveOff")
|
val color = if (active) ColorScheme("SoundCardWaveActive") else ColorScheme("SoundCardWaveOff")
|
||||||
g.sprite(icon, position.x + 24 * i + 2, position.y + 4, color)
|
g.sprite(icon, position.x + 24 * i + 2, position.y + 4, color)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -276,9 +384,28 @@ object SoundCardWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private class Patchbay(leftIdx: Int, rightIdx: Int, isFirst: Boolean, isLast: Boolean) extends Widget {
|
private class Patchbay(leftIdx: Int, rightIdx: Int, isFirst: Boolean, isLast: Boolean) extends Widget {
|
||||||
override def minimumSize: Size2D = Size2D(68, 100)
|
var colLeftCh: Color = ColorScheme("Label")
|
||||||
|
var colLeftAm: Color = ColorScheme("Label")
|
||||||
|
var colLeftFm: Color = ColorScheme("Label")
|
||||||
|
var colRightCh: Color = ColorScheme("Label")
|
||||||
|
var colRightAm: Color = ColorScheme("Label")
|
||||||
|
var colRightFm: Color = ColorScheme("Label")
|
||||||
|
|
||||||
override def maximumSize: Size2D = Size2D(68, Float.PositiveInfinity)
|
override def minimumSize: Size2D = Size2D(74, 100)
|
||||||
|
|
||||||
|
override def maximumSize: Size2D = Size2D(74, Float.PositiveInfinity)
|
||||||
|
|
||||||
|
def posLeftCh: Vector2D = Vector2D(position.x + 18, position.y + 22)
|
||||||
|
|
||||||
|
def posLeftAm: Vector2D = Vector2D(position.x + 18, position.y + 38)
|
||||||
|
|
||||||
|
def posLeftFm: Vector2D = Vector2D(position.x + 18, position.y + 54)
|
||||||
|
|
||||||
|
def posRightCh: Vector2D = Vector2D(position.x + width - 20, position.y + 22)
|
||||||
|
|
||||||
|
def posRightAm: Vector2D = Vector2D(position.x + width - 20, position.y + 38)
|
||||||
|
|
||||||
|
def posRightFm: Vector2D = Vector2D(position.x + width - 20, position.y + 54)
|
||||||
|
|
||||||
override def draw(g: Graphics): Unit = {
|
override def draw(g: Graphics): Unit = {
|
||||||
val x = position.x
|
val x = position.x
|
||||||
@ -309,33 +436,40 @@ object SoundCardWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (i <- 0 until 6) {
|
for (i <- 0 until 6) {
|
||||||
g.rect(x + 23 + i * 4, y - 2, 2, h + 4, ColorScheme("WireOff"))
|
g.rect(x + 26 + i * 4, y - 2, 2, h + 4, ColorScheme("SoundCardWireOff"))
|
||||||
}
|
}
|
||||||
|
|
||||||
y -= sy
|
y -= sy
|
||||||
h += sh
|
h += sh
|
||||||
|
|
||||||
g.sprite("light-panel/BookmarkLeft", x, y + 16, 20, 14)
|
g.sprite("light-panel/BookmarkLeft", x, y + 16, 18, 14)
|
||||||
g.sprite("light-panel/BookmarkLeft", x, y + 32, 20, 14)
|
g.sprite("light-panel/BookmarkLeft", x, y + 32, 18, 14)
|
||||||
g.sprite("light-panel/BookmarkLeft", x, y + 48, 20, 14)
|
g.sprite("light-panel/BookmarkLeft", x, y + 48, 18, 14)
|
||||||
|
|
||||||
g.sprite("light-panel/BookmarkRight", x + w - 20, y + 16, 20, 14)
|
g.sprite("light-panel/BookmarkRight", x + w - 20, y + 16, 20, 14)
|
||||||
g.sprite("light-panel/BookmarkRight", x + w - 20, y + 32, 20, 14)
|
g.sprite("light-panel/BookmarkRight", x + w - 20, y + 32, 20, 14)
|
||||||
g.sprite("light-panel/BookmarkRight", x + w - 20, y + 48, 20, 14)
|
g.sprite("light-panel/BookmarkRight", x + w - 20, y + 48, 20, 14)
|
||||||
|
|
||||||
g.setSmallFont()
|
g.setSmallFont()
|
||||||
g.foreground = ColorScheme("Label")
|
|
||||||
g.background = Color.Transparent
|
g.background = Color.Transparent
|
||||||
|
|
||||||
|
g.foreground = colLeftCh
|
||||||
g.text(x, y + 18, s"C${leftIdx + 1}")
|
g.text(x, y + 18, s"C${leftIdx + 1}")
|
||||||
|
g.foreground = colLeftAm
|
||||||
g.text(x, y + 34, "AM")
|
g.text(x, y + 34, "AM")
|
||||||
|
g.foreground = colLeftFm
|
||||||
g.text(x, y + 50, "FM")
|
g.text(x, y + 50, "FM")
|
||||||
|
|
||||||
|
g.foreground = colRightCh
|
||||||
g.text(x + w - 18, y + 18, s"C${rightIdx + 1}")
|
g.text(x + w - 18, y + 18, s"C${rightIdx + 1}")
|
||||||
|
g.foreground = colRightAm
|
||||||
g.text(x + w - 18, y + 34, "AM")
|
g.text(x + w - 18, y + 34, "AM")
|
||||||
|
g.foreground = colRightFm
|
||||||
g.text(x + w - 18, y + 50, "FM")
|
g.text(x + w - 18, y + 50, "FM")
|
||||||
|
|
||||||
g.setNormalFont()
|
g.setNormalFont()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private case class Connector(column: Int, colorIdx: Int, from: Float, to: Float)
|
||||||
}
|
}
|
||||||