diff --git a/sprites/nodes/NewNode.png b/sprites/nodes/NewNode.png new file mode 100644 index 0000000..d164c91 Binary files /dev/null and b/sprites/nodes/NewNode.png differ diff --git a/src/main/resources/ocelot/desktop/spritesheet.png b/src/main/resources/ocelot/desktop/spritesheet.png index 371be5b..f023214 100644 Binary files a/src/main/resources/ocelot/desktop/spritesheet.png and b/src/main/resources/ocelot/desktop/spritesheet.png differ diff --git a/src/main/resources/ocelot/desktop/spritesheet.txt b/src/main/resources/ocelot/desktop/spritesheet.txt index ea04a11..db3ea9d 100644 --- a/src/main/resources/ocelot/desktop/spritesheet.txt +++ b/src/main/resources/ocelot/desktop/spritesheet.txt @@ -77,17 +77,18 @@ nodes/Computer 339 180 16 16 nodes/ComputerActivityOverlay 356 180 16 16 nodes/ComputerErrorOverlay 373 180 16 16 nodes/ComputerOnOverlay 390 180 16 16 -nodes/Screen 407 180 16 16 -nodes/ScreenOnOverlay 424 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 459 180 8 8 -screen/CornerBR 468 180 8 8 -screen/CornerTL 441 180 8 10 -screen/CornerTR 450 180 8 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 477 180 7 6 +window/CloseButton 494 180 7 6 window/CornerBL 313 197 4 4 window/CornerBR 318 197 4 4 window/CornerTL 323 197 4 4 diff --git a/src/main/scala/ocelot/desktop/geometry/Rect2D.scala b/src/main/scala/ocelot/desktop/geometry/Rect2D.scala index be68482..9339b67 100644 --- a/src/main/scala/ocelot/desktop/geometry/Rect2D.scala +++ b/src/main/scala/ocelot/desktop/geometry/Rect2D.scala @@ -7,6 +7,8 @@ object Rect2D { case class Rect2D(x: Float, y: Float, w: Float, h: Float) { def contains(p: Vector2D): Boolean = p.x >= x && p.y >= y && p.x <= x + w && p.y <= y + h + def contains(r: Rect2D): Boolean = r.x >= x && r.y >= y && r.x + r.w <= x + w && r.y + r.h <= y + h + def origin: Vector2D = Vector2D(x, y) def min: Vector2D = origin diff --git a/src/main/scala/ocelot/desktop/ui/widget/ScrollView.scala b/src/main/scala/ocelot/desktop/ui/widget/ScrollView.scala index 3b82a90..7681be1 100644 --- a/src/main/scala/ocelot/desktop/ui/widget/ScrollView.scala +++ b/src/main/scala/ocelot/desktop/ui/widget/ScrollView.scala @@ -3,8 +3,8 @@ package ocelot.desktop.ui.widget import ocelot.desktop.color.RGBAColorNorm import ocelot.desktop.geometry.{Rect2D, Size2D, Vector2D} import ocelot.desktop.graphics.Graphics -import ocelot.desktop.ui.{KeyEvents, UiHandler} import ocelot.desktop.ui.event.{MouseEvent, ScrollEvent} +import ocelot.desktop.ui.{KeyEvents, UiHandler} import ocelot.desktop.util.Logging class ScrollView(val inner: Widget) extends Widget with Logging { @@ -114,13 +114,13 @@ class ScrollView(val inner: Widget) extends Widget with Logging { private def drawVThumb(g: Graphics): Unit = { val b = vThumbBounds g.rect(position.x + size.width - 11, position.y, 10, size.height, RGBAColorNorm(0.9f, 0.9f, 0.9f, vAnim * 0.15f)) - g.rect(b.x + 3, b.y, b.w - 6, b.h, RGBAColorNorm(0.6f, 0.15f, 0.35f, vAnim * 0.5f + 0.4f)) + g.rect(b.x + 3, b.y, b.w - 6, b.h, RGBAColorNorm(0.8f, 0.25f, 0.45f, vAnim * 0.5f + 0.4f)) } private def drawHThumb(g: Graphics): Unit = { val b = hThumbBounds g.rect(position.x, position.y + size.height - 11, size.width - 12, 10, RGBAColorNorm(0.9f, 0.9f, 0.9f, hAnim * 0.15f)) - g.rect(b.x, b.y + 3, b.w, b.h - 6, RGBAColorNorm(0.6f, 0.15f, 0.35f, hAnim * 0.5f + 0.4f)) + g.rect(b.x, b.y + 3, b.w, b.h - 6, RGBAColorNorm(0.8f, 0.25f, 0.45f, hAnim * 0.5f + 0.4f)) } private def maxXOffset: Float = inner.size.width - size.width diff --git a/src/main/scala/ocelot/desktop/ui/window/NodeSelector.scala b/src/main/scala/ocelot/desktop/ui/window/NodeSelector.scala index 980d567..cf96277 100644 --- a/src/main/scala/ocelot/desktop/ui/window/NodeSelector.scala +++ b/src/main/scala/ocelot/desktop/ui/window/NodeSelector.scala @@ -1,5 +1,6 @@ package ocelot.desktop.ui.window +import ocelot.desktop.color.RGBAColorNorm import ocelot.desktop.geometry.{Padding2D, Rect2D} import ocelot.desktop.graphics.Graphics import ocelot.desktop.ui.UiHandler @@ -7,7 +8,7 @@ import ocelot.desktop.ui.event.CloseWindowEvent import ocelot.desktop.ui.layout.LinearLayout import ocelot.desktop.ui.widget.{PaddingBox, ScrollView, Widget} import ocelot.desktop.ui.workspace.{NodeRegistry, NodeTypeWidget} -import ocelot.desktop.util.animation.UnitAnimation +import ocelot.desktop.util.animation.{ColorAnimation, UnitAnimation} import ocelot.desktop.util.{DrawUtils, Logging, Orientation} class NodeSelector extends Window with Logging { @@ -52,10 +53,14 @@ class NodeSelector extends Window with Logging { private var heightAnimation: UnitAnimation = _ private var animationTime: Float = 0f + private var FinalRingColor: RGBAColorNorm = RGBAColorNorm(0.3f, 0.5f, 0.4f) + val colorAnimation: ColorAnimation = new ColorAnimation(FinalRingColor.copy(a = 0f), speed = 3f) + def isShown: Boolean = _isShown override def update(): Unit = { if (isShown) { + colorAnimation.update() heightAnimation.update() if (isClosing) { @@ -81,7 +86,9 @@ class NodeSelector extends Window with Logging { override def draw(g: Graphics): Unit = { if (!isShown) return - DrawUtils.windowWithShadow(g, position.x, position.y, size.width, size.height, 0.5f, 0.3f) + + g.rect(position.x, position.y, size.width, size.height, RGBAColorNorm(0, 0, 0, colorAnimation.getHSVA.a * 0.4f)) + DrawUtils.ring(g, position.x, position.y, size.width, size.height, thickness = 1, color = colorAnimation.color) super.draw(g) } @@ -113,6 +120,8 @@ class NodeSelector extends Window with Logging { override def show(): Unit = { if (isClosing) return; + colorAnimation.goto(FinalRingColor.copy(a = 1f)) + _isOpen = true _isFocused = true _isShown = true @@ -125,6 +134,8 @@ class NodeSelector extends Window with Logging { override def hide(): Unit = { if (isClosing) return; + colorAnimation.goto(FinalRingColor.copy(a = 0f)) + heightAnimation = UnitAnimation.easeInQuad(0.25f) heightAnimation.time = 1 heightAnimation.goDown() diff --git a/src/main/scala/ocelot/desktop/ui/workspace/NodeRegistry.scala b/src/main/scala/ocelot/desktop/ui/workspace/NodeRegistry.scala index 62fe004..558866f 100644 --- a/src/main/scala/ocelot/desktop/ui/workspace/NodeRegistry.scala +++ b/src/main/scala/ocelot/desktop/ui/workspace/NodeRegistry.scala @@ -9,7 +9,15 @@ object NodeRegistry { types += t; } - for (i <- 0 to 100) { - register(NodeType("Screen" + i, "nodes/Screen", () => new ScreenNode)) + for (i <- 0 to 79) { + register(NodeType("Screen" + i, "nodes/Screen", i % 4, () => new ScreenNode)) + } + + for (i <- 0 to 3) { + register(NodeType("Screen" + i, "nodes/Screen", i % 4, () => new ScreenNode)) + } + + for (i <- 0 to 3) { + register(NodeType("Computer" + i, "nodes/Computer", i % 4, () => new ScreenNode)) } } diff --git a/src/main/scala/ocelot/desktop/ui/workspace/NodeType.scala b/src/main/scala/ocelot/desktop/ui/workspace/NodeType.scala index 425da3a..eeaa2ae 100644 --- a/src/main/scala/ocelot/desktop/ui/workspace/NodeType.scala +++ b/src/main/scala/ocelot/desktop/ui/workspace/NodeType.scala @@ -1,5 +1,5 @@ package ocelot.desktop.ui.workspace -case class NodeType(name: String, icon: String, factory: () => Node) extends Ordered[NodeType] { +case class NodeType(name: String, icon: String, tier: Int, factory: () => Node) extends Ordered[NodeType] { override def compare(that: NodeType): Int = this.name.compare(that.name) } \ No newline at end of file diff --git a/src/main/scala/ocelot/desktop/ui/workspace/NodeTypeWidget.scala b/src/main/scala/ocelot/desktop/ui/workspace/NodeTypeWidget.scala index dc2b1c2..332bcb9 100644 --- a/src/main/scala/ocelot/desktop/ui/workspace/NodeTypeWidget.scala +++ b/src/main/scala/ocelot/desktop/ui/workspace/NodeTypeWidget.scala @@ -3,6 +3,7 @@ package ocelot.desktop.ui.workspace import ocelot.desktop.geometry.Size2D import ocelot.desktop.graphics.Graphics import ocelot.desktop.ui.widget.Widget +import ocelot.desktop.util.TierColor class NodeTypeWidget(nodeType: NodeType) extends Widget { override def minimumSize: Size2D = Size2D(68, 68) @@ -12,6 +13,6 @@ class NodeTypeWidget(nodeType: NodeType) extends Widget { size = maximumSize override def draw(g: Graphics): Unit = { - g.sprite(nodeType.icon, position.x + 2, position.y + 2, size.width - 4, size.height - 4) + g.sprite(nodeType.icon, position.x + 2, position.y + 2, size.width - 4, size.height - 4, TierColor.get(nodeType.tier)) } } diff --git a/src/main/scala/ocelot/desktop/ui/workspace/WorkspaceView.scala b/src/main/scala/ocelot/desktop/ui/workspace/WorkspaceView.scala index d9e6eed..cabf6ae 100644 --- a/src/main/scala/ocelot/desktop/ui/workspace/WorkspaceView.scala +++ b/src/main/scala/ocelot/desktop/ui/workspace/WorkspaceView.scala @@ -1,7 +1,7 @@ package ocelot.desktop.ui.workspace import ocelot.desktop.OcelotDesktop -import ocelot.desktop.color.RGBAColor +import ocelot.desktop.color.{Color, RGBAColor} import ocelot.desktop.geometry.{Rect2D, Size2D, Vector2D} import ocelot.desktop.graphics.Graphics import ocelot.desktop.ui.event._ @@ -19,8 +19,9 @@ class WorkspaceView extends Widget with DragHandler with ClickHandler { private var nodeGrabPoint = Vector2D(0, 0) private var cameraOffset = Vector2D(0, 0) - private val nodeSelector: NodeSelector = new NodeSelector - private val windowPool: WindowPool = new WindowPool + private var newNodePos = Vector2D(0, 0) + private val nodeSelector = new NodeSelector + private val windowPool = new WindowPool children +:= windowPool override def minimumSize: Size2D = Size2D(200, 200) @@ -67,12 +68,31 @@ class WorkspaceView extends Widget with DragHandler with ClickHandler { if (nodeSelector.isShown) { nodeSelector.hide() } else { - windowPool.handleEvent(OpenWindowEvent(nodeSelector)) + openSelector(pos) } - } } + private def openSelector(p: Vector2D): Unit = { + val pos = p - Vector2D(32, 32) + windowPool.handleEvent(OpenWindowEvent(nodeSelector)) + newNodePos = pos - cameraOffset + + val size = Size2D(nodeSelector.maximumSize.width, 250) + + val offsets = Array( + Vector2D(-size.width / 2 + 40, 100), + Vector2D(-size.width - 100, -size.height / 2 + 40), + Vector2D(100, -size.height / 2 + 40), + Vector2D(-size.width / 2 + 40, -size.height - 100), + ) + + for (offset <- offsets) { + nodeSelector.position = (p + offset).max(Vector2D(20, 20)).min(Vector2D(width - size.width - 20, height - size.height - 20)) + if (!Rect2D(pos.x, pos.y, 64, 64).collides(Rect2D(nodeSelector.position, size))) return + } + } + private def checkCollision(node: Node, obstacle: Node): Unit = { val a = node.bounds val b = obstacle.bounds @@ -83,7 +103,9 @@ class WorkspaceView extends Widget with DragHandler with ClickHandler { node.position -= penetrationVector } - private def drawConnection(g: Graphics, a: Rect2D, b: Rect2D, thickness: Float = 4): Unit = { + private def drawConnection(g: Graphics, a: Rect2D, b: Rect2D, thickness: Float = 4, col: Color = RGBAColor(150, 150, 150)): Unit = { + if (a.collides(b)) return + val dist = a.distanceTo(b) / 2f val addition = if (dist <= 58.885f) @@ -96,16 +118,20 @@ class WorkspaceView extends Widget with DragHandler with ClickHandler { val aRect = a.inflate(addition.toFloat) val bRect = b.inflate(addition.toFloat) - val pairs: Array[(Vector2D, Vector2D)] = - for (a <- aRect.centerPoints; b <- bRect.centerPoints) - yield (a, b) + val aCPInf = aRect.centerPoints + val bCPInf = bRect.centerPoints + val aCP = a.centerPoints + val bCP = b.centerPoints - val pair = pairs.minBy(pair => (pair._1 - pair._2).length.toInt) - val (start, end) = pair - val col = RGBAColor(150, 150, 150) - g.line(start, end, thickness, col) - g.line(start, a.center, thickness, col) - g.line(end, b.center, thickness, col) + val pairs = + for (a <- 0 to 3; b <- 0 to 3) + yield (aCP(a), bCP(b), aCPInf(a), bCPInf(b)) + + val pair = pairs.minBy(pair => (pair._3 - pair._4).length.toInt) + val (aStart, bStart, aEnd, bEnd) = pair + g.line(aEnd, bEnd, thickness, col) + g.line(aStart, aEnd, thickness, col) + g.line(bStart, bEnd, thickness, col) } override def draw(g: Graphics): Unit = { @@ -129,9 +155,19 @@ class WorkspaceView extends Widget with DragHandler with ClickHandler { g.save() g.translate(cameraOffset.x + position.x, cameraOffset.y + position.y) drawConnection(g, nodes(0).bounds, nodes(1).bounds) + nodes.foreach(_.draw(g)) + + val col = nodeSelector.colorAnimation.color + if (nodeSelector.isShown) { + g.sprite("nodes/NewNode", newNodePos.x, newNodePos.y, 64, 64, col) + } + g.restore() + if (nodeSelector.isShown) + drawConnection(g, Rect2D(newNodePos.x + cameraOffset.x, newNodePos.y + cameraOffset.y, 64, 64), nodeSelector.bounds, col = col) + drawChildren(g) } diff --git a/src/main/scala/ocelot/desktop/util/DrawUtils.scala b/src/main/scala/ocelot/desktop/util/DrawUtils.scala index da9e07c..ee61385 100644 --- a/src/main/scala/ocelot/desktop/util/DrawUtils.scala +++ b/src/main/scala/ocelot/desktop/util/DrawUtils.scala @@ -33,6 +33,13 @@ object DrawUtils { DrawUtils.windowBorder(g, x, y, w, h, RGBAColorNorm(1, 1, 1, backgroundAlpha)) } + def ring(g: Graphics, x: Float, y: Float, w: Float, h: Float, thickness: Float = 4, color: Color = RGBAColor(255, 255, 255)): Unit = { + g.rect(x, y, thickness, h, color) + g.rect(x + w - thickness, y, thickness, h, color) + g.rect(x + thickness, y, w - thickness * 2f, thickness, color) + g.rect(x + thickness, y + h - thickness, w - thickness * 2f, thickness, color) + } + def windowBorder(g: Graphics, x: Float, y: Float, w: Float, h: Float, color: Color = RGBAColor(255, 255, 255)): Unit = { g.sprite("window/CornerTL", x, y, 8, 8, color)