Code Sketch


yoiiiii
By: Mhalsakant School
Category: Programming
import java.awt.BasicStroke
import java.awt.BorderLayout
import java.awt.Color
import java.awt.Cursor
import java.awt.Dimension
import java.awt.FlowLayout
import java.awt.Font
import java.awt.GradientPaint
import java.awt.Graphics
import java.awt.Graphics2D
import java.awt.Point
import java.awt.Toolkit
import java.awt.image.BufferedImage
import java.awt.RenderingHints
import java.awt.event.ActionEvent
import java.awt.event.ActionListener
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import java.awt.event.MouseMotionAdapter
import java.awt.event.KeyAdapter
import java.awt.event.KeyEvent
import java.awt.geom.Path2D
import java.util.Random

import javax.swing.DefaultListModel
import javax.swing.JButton
import javax.swing.JColorChooser
import javax.swing.JFileChooser
import javax.swing.JFrame
import javax.swing.JLabel
import javax.swing.JList
import javax.swing.JOptionPane
import javax.swing.JPanel
import javax.swing.JScrollPane
import javax.swing.JSplitPane
import javax.swing.JTabbedPane
import javax.swing.JTextArea
import javax.swing.JTextField
import javax.swing.ListSelectionModel
import javax.swing.SwingConstants
import javax.swing.SwingUtilities
import javax.swing.Timer
import javax.swing.WindowConstants

import scala.collection.mutable.ArrayBuffer

// ============================================================
// ULTRA LEGEND X9
// DRAW + OBJECT + SHAPE + ANIMATION + GAMES STUDIO
// FULL STABLE VERSION
// ============================================================

// ============================================================
// CONSTANTS
// ============================================================

val CANVAS_W: Int = 1000
val CANVAS_H: Int = 650

val CENTER_X: Int = CANVAS_W / 2
val CENTER_Y: Int = CANVAS_H / 2

val RANDOM_GEN =
  new Random()

// ============================================================
// DATA MODELS
// ============================================================

case class DrawItem(
  points: Vector[Point],
  stroke: Color,
  fill: Color,
  filled: Boolean,
  name: String,
  visible: Boolean,
  locked: Boolean
)

case class PaddleLine(
  p1: Point,
  p2: Point
)

case class GameInfo(
  name: String,
  mode: String,
  description: String
)

// ============================================================
// GLOBAL DRAW STATE
// ============================================================

var drawItems =
  ArrayBuffer[DrawItem]()

var undoStack =
  ArrayBuffer[ArrayBuffer[DrawItem]]()

var redoStack =
  ArrayBuffer[ArrayBuffer[DrawItem]]()

var activeTool: String =
  "PENCIL"

var strokeColor: Color =
  new Color(25, 90, 220)

var fillColor: Color =
  new Color(255, 180, 40)

var brushWidth: Float =
  5.0f

var showGrid: Boolean =
  true

var snapToGrid: Boolean =
  false

var selectedIndex: Int =
  -1

var drawingNow: Boolean =
  false

var movingNow: Boolean =
  false

var resizingNow: Boolean =
  false

var resizeHandle: String =
  ""

var animationFrames =
  ArrayBuffer[ArrayBuffer[DrawItem]]()

var animationClipboard =
  ArrayBuffer[DrawItem]()

var animationCurrentFrame: Int =
  0

var animationFps: Int =
  8

var animationStudioOpen: Boolean =
  false

var lastMousePoint: Point =
  new Point(0, 0)

var originalPoints: Vector[Point] =
  Vector.empty

var tempPoints =
  ArrayBuffer[Point]()

var generatedCode: String =
  ""

var generatedAnimationCode: String =
  ""

var mainFrame: JFrame =
  null

var canvas: JPanel =
  null

var codeArea: JTextArea =
  null

var statusLabel: JLabel =
  null

var objectSearch: JTextField =
  null

var objectList: JList[String] =
  null

var shapeList: JList[String] =
  null

// ============================================================
// UI HELPERS
// ============================================================

def button(text: String): JButton = {
  val b =
    new JButton(text)

  b.setFocusable(false)

  b.setFont(
    new Font(
      "Arial",
      Font.BOLD,
      11
    )
  )

  b
}

def setStatus(text: String): Unit = {
  if (statusLabel != null) {
    statusLabel.setText(text)
  }
}

def repaintCanvas(): Unit = {
  if (canvas != null) {
    canvas.repaint()
  }
}

// ============================================================
// HISTORY
// ============================================================

def cloneItems(
  source: ArrayBuffer[DrawItem]
): ArrayBuffer[DrawItem] = {

  val output =
    ArrayBuffer[DrawItem]()

  var i: Int = 0

  while (i < source.length) {
    val item =
      source(i)

    val pts =
      item.points.map { p =>
        new Point(
          p.x,
          p.y
        )
      }

    output +=
      item.copy(
        points = pts
      )

    i += 1
  }

  output
}

def saveHistory(): Unit = {
  undoStack +=
    cloneItems(drawItems)

  redoStack.clear()

  while (undoStack.length > 60) {
    undoStack.remove(0)
  }
}

def undo(): Unit = {

  if (undoStack.nonEmpty) {

    redoStack +=
      cloneItems(drawItems)

    val previous =
      undoStack.last

    undoStack.remove(
      undoStack.length - 1
    )

    drawItems.clear()

    drawItems ++=
      cloneItems(previous)

    selectedIndex =
      -1

    repaintCanvas()

    setStatus(
      "UNDO COMPLETE"
    )

  } else {
    setStatus(
      "NOTHING TO UNDO"
    )
  }
}

def redo(): Unit = {

  if (redoStack.nonEmpty) {

    undoStack +=
      cloneItems(drawItems)

    val nextState =
      redoStack.last

    redoStack.remove(
      redoStack.length - 1
    )

    drawItems.clear()

    drawItems ++=
      cloneItems(nextState)

    selectedIndex =
      -1

    repaintCanvas()

    setStatus(
      "REDO COMPLETE"
    )

  } else {
    setStatus(
      "NOTHING TO REDO"
    )
  }
}

// ============================================================
// POINT HELPERS
// ============================================================

def distance(
  a: Point,
  b: Point
): Double = {
  math.hypot(
    b.x - a.x,
    b.y - a.y
  )
}

def snapped(value: Int): Int = {
  if (snapToGrid) {
    (value / 10) * 10
  } else {
    value
  }
}

def canvasPoint(
  e: MouseEvent
): Point = {

  val x =
    math.max(
      0,
      math.min(
        CANVAS_W - 1,
        snapped(e.getX)
      )
    )

  val y =
    math.max(
      0,
      math.min(
        CANVAS_H - 1,
        snapped(e.getY)
      )
    )

  new Point(
    x,
    y
  )
}

def closed(
  points: Iterable[Point]
): Boolean = {

  val seq =
    points.toVector

  seq.length >= 3 &&
  distance(
    seq.head,
    seq.last
  ) <= 45.0
}

// ============================================================
// BOUNDS
// ============================================================

def boundsOf(
  item: DrawItem
): java.awt.Rectangle = {

  if (item.points.isEmpty) {

    new java.awt.Rectangle(
      0,
      0,
      1,
      1
    )

  } else {

    var minX =
      item.points.head.x

    var maxX =
      item.points.head.x

    var minY =
      item.points.head.y

    var maxY =
      item.points.head.y

    var i: Int = 1

    while (i < item.points.length) {

      val p =
        item.points(i)

      if (p.x < minX) {
        minX = p.x
      }

      if (p.x > maxX) {
        maxX = p.x
      }

      if (p.y < minY) {
        minY = p.y
      }

      if (p.y > maxY) {
        maxY = p.y
      }

      i += 1
    }

    new java.awt.Rectangle(
      minX,
      minY,
      math.max(
        1,
        maxX - minX
      ),
      math.max(
        1,
        maxY - minY
      )
    )
  }
}

// ============================================================
// SEGMENT HIT TEST
// ============================================================

def segmentDistance(
  p: Point,
  a: Point,
  b: Point
): Double = {

  val dx: Double =
    b.x - a.x

  val dy: Double =
    b.y - a.y

  if (
    dx == 0.0 &&
    dy == 0.0
  ) {
    distance(
      p,
      a
    )
  } else {

    val dot: Double =
      (p.x - a.x) * dx +
      (p.y - a.y) * dy

    val len2: Double =
      dx * dx +
      dy * dy

    val t: Double =
      math.max(
        0.0,
        math.min(
          1.0,
          dot / len2
        )
      )

    val cx: Double =
      a.x + t * dx

    val cy: Double =
      a.y + t * dy

    math.hypot(
      p.x - cx,
      p.y - cy
    )
  }
}

// ============================================================
// POLYGON HIT TEST
// ============================================================

def insidePolygon(
  p: Point,
  points: Seq[Point]
): Boolean = {

  if (points.length < 3) {
    false
  } else {

    var inside: Boolean =
      false

    var j: Int =
      points.length - 1

    var i: Int =
      0

    while (i < points.length) {

      val xi =
        points(i).x

      val yi =
        points(i).y

      val xj =
        points(j).x

      val yj =
        points(j).y

      val hit =
        ((yi > p.y) != (yj > p.y)) &&
        (
          p.x <
          (
            xj - xi
          ).toDouble *
          (
            p.y - yi
          ).toDouble /
          (
            yj - yi +
            0.000001
          ) +
          xi
        )

      if (hit) {
        inside =
          !inside
      }

      j =
        i

      i += 1
    }

    inside
  }
}

// ============================================================
// OBJECT HIT TEST
// ============================================================

def findObject(
  p: Point
): Int = {

  var result: Int =
    -1

  var i: Int =
    drawItems.length - 1

  while (
    i >= 0 &&
    result < 0
  ) {

    val item =
      drawItems(i)

    if (item.visible) {

      val r =
        boundsOf(item)

      val expanded =
        new java.awt.Rectangle(
          r.x - 14,
          r.y - 14,
          r.width + 28,
          r.height + 28
        )

      if (expanded.contains(p)) {

        if (
          item.filled &&
          closed(item.points) &&
          insidePolygon(
            p,
            item.points.toSeq
          )
        ) {

          result =
            i

        } else {

          var j: Int =
            1

          while (
            j < item.points.length &&
            result < 0
          ) {

            if (
              segmentDistance(
                p,
                item.points(j - 1),
                item.points(j)
              ) < 16.0
            ) {
              result =
                i
            }

            j += 1
          }
        }
      }
    }

    i -= 1
  }

  result
}

// ============================================================
// RESIZE HANDLES
// ============================================================

def handles(
  r: java.awt.Rectangle
): Array[(String, Point)] = {

  val mx =
    r.x + r.width / 2

  val my =
    r.y + r.height / 2

  Array(
    ("TL", new Point(r.x, r.y)),
    ("TM", new Point(mx, r.y)),
    ("TR", new Point(r.x + r.width, r.y)),
    ("ML", new Point(r.x, my)),
    ("MR", new Point(r.x + r.width, my)),
    ("BL", new Point(r.x, r.y + r.height)),
    ("BM", new Point(mx, r.y + r.height)),
    ("BR", new Point(r.x + r.width, r.y + r.height))
  )
}

def resizeHandle(
  item: DrawItem,
  p: Point
): String = {

  val r =
    boundsOf(item)

  val expanded =
    new java.awt.Rectangle(
      r.x - 8,
      r.y - 8,
      r.width + 16,
      r.height + 16
    )

  val hs =
    handles(expanded)

  var result =
    ""

  var i: Int =
    0

  while (
    i < hs.length &&
    result == ""
  ) {

    if (
      distance(
        p,
        hs(i)._2
      ) <= 13.0
    ) {

      result =
        hs(i)._1
    }

    i += 1
  }

  result
}

// ============================================================
// MOVE
// ============================================================

def moveSelected(
  dx: Int,
  dy: Int
): Unit = {

  if (
    selectedIndex >= 0 &&
    selectedIndex < drawItems.length
  ) {

    val item =
      drawItems(selectedIndex)

    if (!item.locked) {

      val moved =
        item.points.map { p =>
          new Point(
            p.x + dx,
            p.y + dy
          )
        }

      drawItems(selectedIndex) =
        item.copy(
          points = moved
        )
    }
  }
}

// ============================================================
// RESIZE
// ============================================================

def resizeSelected(
  p: Point
): Unit = {

  if (
    selectedIndex >= 0 &&
    selectedIndex < drawItems.length &&
    originalPoints.nonEmpty
  ) {

    val item =
      drawItems(selectedIndex)

    var minX =
      originalPoints.head.x

    var maxX =
      originalPoints.head.x

    var minY =
      originalPoints.head.y

    var maxY =
      originalPoints.head.y

    var i: Int =
      1

    while (
      i < originalPoints.length
    ) {

      val q =
        originalPoints(i)

      if (q.x < minX) {
        minX =
          q.x
      }

      if (q.x > maxX) {
        maxX =
          q.x
      }

      if (q.y < minY) {
        minY =
          q.y
      }

      if (q.y > maxY) {
        maxY =
          q.y
      }

      i += 1
    }

    val oldW =
      math.max(
        1,
        maxX - minX
      )

    val oldH =
      math.max(
        1,
        maxY - minY
      )

    var newMinX =
      minX

    var newMaxX =
      maxX

    var newMinY =
      minY

    var newMaxY =
      maxY

    if (resizeHandle == "TL") {
      newMinX = p.x
      newMinY = p.y

    } else if (resizeHandle == "TM") {
      newMinY = p.y

    } else if (resizeHandle == "TR") {
      newMaxX = p.x
      newMinY = p.y

    } else if (resizeHandle == "ML") {
      newMinX = p.x

    } else if (resizeHandle == "MR") {
      newMaxX = p.x

    } else if (resizeHandle == "BL") {
      newMinX = p.x
      newMaxY = p.y

    } else if (resizeHandle == "BM") {
      newMaxY = p.y

    } else if (resizeHandle == "BR") {
      newMaxX = p.x
      newMaxY = p.y
    }

    val newW =
      math.max(
        5,
        newMaxX - newMinX
      )

    val newH =
      math.max(
        5,
        newMaxY - newMinY
      )

    val scaled =
      originalPoints.map { q =>

        val nx =
          newMinX +
          (
            (
              q.x - minX
            ).toDouble *
            newW /
            oldW
          ).toInt

        val ny =
          newMinY +
          (
            (
              q.y - minY
            ).toDouble *
            newH /
            oldH
          ).toInt

        new Point(
          nx,
          ny
        )
      }

    drawItems(selectedIndex) =
      item.copy(
        points = scaled
      )
  }
}

// ============================================================
// GEOMETRY
// ============================================================

def polygon(
  sides: Int,
  radius: Int,
  start: Double
): Vector[Point] = {

  val out =
    ArrayBuffer[Point]()

  var i: Int =
    0

  while (i <= sides) {

    val angle =
      start +
      i.toDouble *
      2.0 *
      math.Pi /
      sides.toDouble

    out +=
      new Point(
        CENTER_X +
        (
          math.cos(angle) *
          radius
        ).toInt,
        CENTER_Y +
        (
          math.sin(angle) *
          radius
        ).toInt
      )

    i += 1
  }

  out.toVector
}

def ellipse(
  rx: Int,
  ry: Int
): Vector[Point] = {

  val out =
    ArrayBuffer[Point]()

  var deg: Int =
    0

  while (deg <= 360) {

    val a =
      deg *
      math.Pi /
      180.0

    out +=
      new Point(
        CENTER_X +
        (
          math.cos(a) *
          rx
        ).toInt,
        CENTER_Y +
        (
          math.sin(a) *
          ry
        ).toInt
      )

    deg += 3
  }

  out.toVector
}

def star(
  count: Int,
  radius: Int
): Vector[Point] = {

  val out =
    ArrayBuffer[Point]()

  var i: Int =
    0

  while (
    i <= count * 2
  ) {

    val angle =
      -math.Pi / 2.0 +
      i.toDouble *
      math.Pi /
      count.toDouble

    val rr =
      if (i % 2 == 0) {
        radius
      } else {
        (radius * 0.45).toInt
      }

    out +=
      new Point(
        CENTER_X +
        (
          math.cos(angle) *
          rr
        ).toInt,
        CENTER_Y +
        (
          math.sin(angle) *
          rr
        ).toInt
      )

    i += 1
  }

  out.toVector
}

def heart(): Vector[Point] = {

  val out =
    ArrayBuffer[Point]()

  var i: Int =
    0

  while (i <= 180) {

    val t =
      i *
      math.Pi /
      180.0

    val x =
      16.0 *
      math.pow(
        math.sin(t),
        3
      )

    val y =
      13.0 *
      math.cos(t) -
      5.0 *
      math.cos(2.0 * t) -
      2.0 *
      math.cos(3.0 * t) -
      math.cos(4.0 * t)

    out +=
      new Point(
        CENTER_X +
        (x * 13.0).toInt,
        CENTER_Y -
        (y * 13.0).toInt
      )

    i += 1
  }

  out.toVector
}

// ============================================================
// SHAPE LIBRARY
// ============================================================

val shapes =
  Array(
    "LINE",
    "SQUARE",
    "RECTANGLE",
    "CIRCLE",
    "ELLIPSE",
    "TRIANGLE",
    "DIAMOND",
    "PENTAGON",
    "HEXAGON",
    "OCTAGON",
    "STAR",
    "STAR 6",
    "HEART",
    "ARROW",
    "PLUS",
    "CLOUD",
    "SUN",
    "MOON",
    "RING",
    "WAVE",
    "SPIRAL",
    "BURST",
    "SNOWFLAKE"
  )

var shapeIndex: Int =
  0

val shapeModel =
  new DefaultListModel[String]()

while (
  shapeIndex < shapes.length
) {

  shapeModel.addElement(
    shapes(shapeIndex)
  )

  shapeIndex += 1
}

// ============================================================
// SHAPE GENERATOR
// ============================================================

def shapeGeometry(
  name: String
): Vector[Point] = {

  val n =
    name.toUpperCase

  if (n == "LINE") {

    Vector(
      new Point(100,120),
      new Point(900,530)
    )

  } else if (n == "SQUARE") {

    Vector(
      new Point(300,150),
      new Point(700,150),
      new Point(700,500),
      new Point(300,500),
      new Point(300,150)
    )

  } else if (n == "RECTANGLE") {

    Vector(
      new Point(170,210),
      new Point(830,210),
      new Point(830,440),
      new Point(170,440),
      new Point(170,210)
    )

  } else if (n == "CIRCLE") {

    ellipse(
      210,
      210
    )

  } else if (n == "ELLIPSE") {

    ellipse(
      290,
      160
    )

  } else if (n == "TRIANGLE") {

    polygon(
      3,
      250,
      -math.Pi / 2.0
    )

  } else if (n == "DIAMOND") {

    Vector(
      new Point(CENTER_X,70),
      new Point(860,CENTER_Y),
      new Point(CENTER_X,580),
      new Point(140,CENTER_Y),
      new Point(CENTER_X,70)
    )

  } else if (n == "PENTAGON") {

    polygon(
      5,
      240,
      -math.Pi / 2.0
    )

  } else if (n == "HEXAGON") {

    polygon(
      6,
      235,
      0.0
    )

  } else if (n == "OCTAGON") {

    polygon(
      8,
      235,
      math.Pi / 8.0
    )

  } else if (n == "STAR") {

    star(
      5,
      250
    )

  } else if (n == "STAR 6") {

    star(
      6,
      250
    )

  } else if (n == "HEART") {

    heart()

  } else if (n == "ARROW") {

    Vector(
      new Point(130,245),
      new Point(620,245),
      new Point(620,135),
      new Point(870,325),
      new Point(620,515),
      new Point(620,405),
      new Point(130,405),
      new Point(130,245)
    )

  } else if (n == "PLUS") {

    Vector(
      new Point(430,90),
      new Point(570,90),
      new Point(570,240),
      new Point(720,240),
      new Point(720,410),
      new Point(570,410),
      new Point(570,560),
      new Point(430,560),
      new Point(430,410),
      new Point(280,410),
      new Point(280,240),
      new Point(430,240),
      new Point(430,90)
    )

  } else if (n == "CLOUD") {

    Vector(
      new Point(160,470),
      new Point(200,360),
      new Point(330,270),
      new Point(450,300),
      new Point(535,195),
      new Point(665,220),
      new Point(790,320),
      new Point(840,390),
      new Point(810,470),
      new Point(160,470)
    )

  } else if (n == "SUN") {

    star(
      16,
      245
    )

  } else if (n == "MOON") {

    ellipse(
      225,
      225
    )

  } else if (n == "RING") {

    ellipse(
      225,
      225
    )

  } else if (n == "WAVE") {

    val out =
      ArrayBuffer[Point]()

    var x: Int =
      30

    while (x <= 970) {

      out +=
        new Point(
          x,
          CENTER_Y +
          (
            math.sin(
              (x - 30) * 0.045
            ) * 100.0
          ).toInt
        )

      x += 5
    }

    out.toVector

  } else if (n == "SPIRAL") {

    val out =
      ArrayBuffer[Point]()

    var angle: Double =
      0.0

    var radius: Double =
      4.0

    while (
      angle < math.Pi * 8.0
    ) {

      out +=
        new Point(
          CENTER_X +
          (
            math.cos(angle) *
            radius
          ).toInt,
          CENTER_Y +
          (
            math.sin(angle) *
            radius
          ).toInt
        )

      angle += 0.08
      radius += 1.5
    }

    out.toVector

  } else if (n == "BURST") {

    star(
      20,
      250
    )

  } else if (n == "SNOWFLAKE") {

    Vector(
      new Point(CENTER_X,70),
      new Point(CENTER_X,580),
      new Point(240,160),
      new Point(760,490),
      new Point(240,490),
      new Point(760,160)
    )

  } else {

    ellipse(
      220,
      160
    )
  }
}

// ============================================================
// OBJECT LIBRARY
// ============================================================

val objects =
  Array(
    "CAR",
    "SPORTS CAR",
    "TRUCK",
    "BUS",
    "TRAIN",
    "AIRPLANE",
    "JET",
    "HELICOPTER",
    "ROCKET",
    "BICYCLE",
    "MOTORBIKE",
    "BOAT",
    "SHIP",
    "SUBMARINE",
    "DRONE",
    "UFO",
    "HOUSE",
    "VILLA",
    "CASTLE",
    "TOWER",
    "BRIDGE",
    "CITY",
    "TREE",
    "PALM TREE",
    "FLOWER",
    "CACTUS",
    "MOUNTAIN",
    "VOLCANO",
    "ISLAND",
    "RAINBOW",
    "SUN",
    "MOON",
    "PLANET",
    "EARTH",
    "SATURN",
    "BOY",
    "GIRL",
    "PERSON",
    "ROBOT",
    "ASTRONAUT",
    "KING",
    "QUEEN",
    "CAT",
    "DOG",
    "BIRD",
    "FISH",
    "SHARK",
    "WHALE",
    "DOLPHIN",
    "LION",
    "TIGER",
    "ELEPHANT",
    "HORSE",
    "RABBIT",
    "PANDA",
    "MONKEY",
    "FOX",
    "DEER",
    "BUTTERFLY",
    "BEE",
    "SNAKE",
    "TURTLE",
    "APPLE",
    "BANANA",
    "MANGO",
    "PIZZA",
    "BURGER",
    "CAKE",
    "GUITAR",
    "PIANO",
    "CAMERA",
    "COMPUTER",
    "PHONE",
    "BOOK",
    "CLOCK",
    "KEY",
    "LAMP",
    "CHAIR",
    "TABLE",
    "STAR",
    "HEART",
    "DIAMOND",
    "CROWN",
    "TROPHY",
    "GIFT",
    "BALLOON",
    "UMBRELLA",
    "FOOTBALL",
    "BASKETBALL",
    "CRICKET BAT",
    "CRICKET BALL",
    "FUTURE CAR",
    "CYBER CITY",
    "NEON TOWER",
    "TIME MACHINE"
  )

val objectModel =
  new DefaultListModel[String]()

var objectInit: Int =
  0

while (
  objectInit < objects.length
) {

  objectModel.addElement(
    objects(objectInit)
  )

  objectInit += 1
}

// ============================================================
// OBJECT GEOMETRY
// ============================================================

def objectGeometry(
  name: String
): Vector[Point] = {

  val n =
    name.toUpperCase

  if (n.indexOf("CAR") >= 0) {

    Vector(
      new Point(100,470),
      new Point(215,365),
      new Point(390,355),
      new Point(470,245),
      new Point(660,245),
      new Point(775,355),
      new Point(900,370),
      new Point(950,470),
      new Point(100,470)
    )

  } else if (
    n == "HOUSE" ||
    n == "VILLA" ||
    n == "CASTLE"
  ) {

    Vector(
      new Point(220,555),
      new Point(220,300),
      new Point(500,70),
      new Point(780,300),
      new Point(780,555),
      new Point(220,555)
    )

  } else if (
    n.indexOf("TREE") >= 0
  ) {

    Vector(
      new Point(455,580),
      new Point(545,580),
      new Point(545,350),
      new Point(680,270),
      new Point(600,150),
      new Point(500,215),
      new Point(400,150),
      new Point(320,270),
      new Point(455,350),
      new Point(455,580)
    )

  } else if (n == "BIRD") {

    Vector(
      new Point(180,350),
      new Point(365,215),
      new Point(500,300),
      new Point(660,180),
      new Point(820,330),
      new Point(630,415),
      new Point(450,400),
      new Point(310,470),
      new Point(180,350)
    )

  } else if (
    n == "FISH" ||
    n == "SHARK" ||
    n == "WHALE" ||
    n == "DOLPHIN"
  ) {

    Vector(
      new Point(120,325),
      new Point(300,210),
      new Point(650,210),
      new Point(865,325),
      new Point(650,440),
      new Point(300,440),
      new Point(120,325)
    )

  } else if (
    n.indexOf("ROCKET") >= 0 ||
    n == "UFO"
  ) {

    Vector(
      new Point(425,525),
      new Point(450,220),
      new Point(500,60),
      new Point(550,220),
      new Point(575,525),
      new Point(500,600),
      new Point(425,525)
    )

  } else if (
    n.indexOf("ROBOT") >= 0
  ) {

    Vector(
      new Point(350,555),
      new Point(350,300),
      new Point(330,300),
      new Point(330,130),
      new Point(670,130),
      new Point(670,300),
      new Point(650,300),
      new Point(650,555),
      new Point(350,555)
    )

  } else if (
    n.indexOf("MOUNTAIN") >= 0
  ) {

    Vector(
      new Point(40,560),
      new Point(280,205),
      new Point(430,400),
      new Point(620,75),
      new Point(960,560),
      new Point(40,560)
    )

  } else if (n == "FLOWER") {

    Vector(
      new Point(500,585),
      new Point(500,315),
      new Point(390,225),
      new Point(500,115),
      new Point(610,225),
      new Point(500,315),
      new Point(500,585)
    )

  } else if (n == "STAR") {

    star(
      5,
      250
    )

  } else if (n == "HEART") {

    heart()

  } else if (n == "CROWN") {

    Vector(
      new Point(230,490),
      new Point(195,155),
      new Point(390,295),
      new Point(500,80),
      new Point(610,295),
      new Point(805,155),
      new Point(770,490),
      new Point(230,490)
    )

  } else {

    polygon(
      6,
      230,
      0.0
    )
  }
}

// ============================================================
// ADD OBJECTS
// ============================================================

def addShape(
  name: String
): Unit = {

  saveHistory()

  val pts =
    shapeGeometry(name)

  drawItems +=
    DrawItem(
      pts,
      strokeColor,
      fillColor,
      closed(pts),
      name,
      true,
      false
    )

  selectedIndex =
    drawItems.length - 1

  activeTool =
    "SELECT"

  repaintCanvas()

  setStatus(
    "SHAPE ADDED: " + name
  )
}

def addObject(
  name: String
): Unit = {

  saveHistory()

  val pts =
    objectGeometry(name)

  drawItems +=
    DrawItem(
      pts,
      strokeColor,
      fillColor,
      closed(pts),
      name,
      true,
      false
    )

  selectedIndex =
    drawItems.length - 1

  activeTool =
    "SELECT"

  repaintCanvas()

  setStatus(
    "OBJECT ADDED: " + name
  )
}

// ============================================================
// FILL
// ============================================================

def fillSelected(): Unit = {

  if (
    selectedIndex >= 0 &&
    selectedIndex < drawItems.length
  ) {

    val item =
      drawItems(selectedIndex)

    if (
      closed(item.points)
    ) {

      saveHistory()

      drawItems(selectedIndex) =
        item.copy(
          fill = fillColor,
          filled = true
        )

      repaintCanvas()

      setStatus(
        "FILL COMPLETE"
      )

    } else {

      setStatus(
        "CLOSED SHAPE REQUIRED"
      )
    }

  } else {

    setStatus(
      "SELECT OBJECT FIRST"
    )
  }
}

// ============================================================
// DELETE / CLEAR
// ============================================================

def deleteSelected(): Unit = {

  if (
    selectedIndex >= 0 &&
    selectedIndex < drawItems.length
  ) {

    saveHistory()

    drawItems.remove(
      selectedIndex
    )

    selectedIndex =
      -1

    repaintCanvas()

    setStatus(
      "OBJECT DELETED"
    )
  }
}

def clearAll(): Unit = {

  if (drawItems.nonEmpty) {
    saveHistory()
  }

  drawItems.clear()

  tempPoints.clear()

  selectedIndex =
    -1

  if (codeArea != null) {
    codeArea.setText(
      "// CANVAS CLEARED"
    )
  }

  repaintCanvas()

  setStatus(
    "CANVAS CLEARED"
  )
}

// ============================================================
// COLOR NAME
// ============================================================

def colorName(
  c: Color
): String = {

  val r =
    c.getRed

  val g =
    c.getGreen

  val b =
    c.getBlue

  if (
    r > 220 &&
    g < 100 &&
    b < 100
  ) {
    "red"

  } else if (
    r < 100 &&
    g > 170 &&
    b < 120
  ) {
    "green"

  } else if (
    r < 100 &&
    g < 160 &&
    b > 170
  ) {
    "blue"

  } else if (
    r > 220 &&
    g > 170 &&
    b < 130
  ) {
    "yellow"

  } else if (
    r > 220 &&
    g > 100 &&
    b < 120
  ) {
    "orange"

  } else if (
    r > 160 &&
    b > 160
  ) {
    "magenta"

  } else if (
    r > 170 &&
    g > 170 &&
    b > 170
  ) {
    "white"

  } else {
    "black"
  }
}

// ============================================================
// GENERATE KOJO CODE
// ============================================================

def generateCode(): Unit = {

  val sb =
    new StringBuilder()

  sb.append(
    "// ULTRA LEGEND GENERATED KOJO CODE\n"
  )

  sb.append(
    "cleari()\n"
  )

  sb.append(
    "setAnimationDelay(5)\n\n"
  )

  if (drawItems.isEmpty) {

    sb.append(
      "// NOTHING DRAWN\n"
    )

  } else {

    var i: Int =
      0

    while (
      i < drawItems.length
    ) {

      val item =
        drawItems(i)

      if (
        item.visible &&
        item.points.nonEmpty
      ) {

        sb.append(
          "// " +
          item.name +
          "\n"
        )

        sb.append(
          "setPenColor(" +
          colorName(
            item.stroke
          ) +
          ")\n"
        )

        sb.append(
          "penUp()\n"
        )

        sb.append(
          "setPosition(" +
          (
            item.points.head.x -
            CENTER_X
          ) +
          ", " +
          (
            CENTER_Y -
            item.points.head.y
          ) +
          ")\n"
        )

        sb.append(
          "penDown()\n"
        )

        var j: Int =
          1

        while (
          j < item.points.length
        ) {

          sb.append(
            "lineTo(" +
            (
              item.points(j).x -
              CENTER_X
            ) +
            ", " +
            (
              CENTER_Y -
              item.points(j).y
            ) +
            ")\n"
          )

          j += 1
        }

        if (item.filled && item.points.length >= 3) {
          sb.append(
            "setFillColor(" +
            item.fill.getRed + "," +
            item.fill.getGreen + "," +
            item.fill.getBlue +
            ")\n"
          )
          sb.append(
            "// Fill RGB: " +
            item.fill.getRed + "," +
            item.fill.getGreen + "," +
            item.fill.getBlue +
            "\n"
          )
        }

        sb.append(
          "\n"
        )
      }

      i += 1
    }
  }

  generatedCode =
    sb.toString

  codeArea.setText(
    generatedCode
  )

  setStatus(
    "DRAW CODE GENERATED"
  )
}

// ============================================================
// COPY / SAVE
// ============================================================

def copyText(
  text: String
): Unit = {

  try {

    val clipboard =
      java.awt.Toolkit
        .getDefaultToolkit
        .getSystemClipboard

    clipboard.setContents(
      new java.awt.datatransfer.StringSelection(
        text
      ),
      null
    )

    setStatus(
      "COPIED"
    )

  } catch {
    case _: Exception =>
      setStatus(
        "COPY FAILED"
      )
  }
}

def saveText(
  text: String
): Unit = {

  val chooser =
    new JFileChooser()

  chooser.setDialogTitle(
    "SAVE SCALA CODE"
  )

  if (
    chooser.showSaveDialog(
      mainFrame
    ) ==
    JFileChooser.APPROVE_OPTION
  ) {

    try {

      var file =
        chooser.getSelectedFile

      if (
        !file.getName
          .toLowerCase
          .endsWith(".scala")
      ) {

        file =
          new java.io.File(
            file.getAbsolutePath +
            ".scala"
          )
      }

      val out =
        new java.io.PrintWriter(
          file
        )

      out.write(text)
      out.close()

      setStatus(
        "FILE SAVED"
      )

    } catch {

      case _: Exception =>
        setStatus(
          "SAVE FAILED"
        )
    }
  }
}

// ============================================================
// ANIMATION CODE
// ============================================================

def generateAnimationCode(): Unit = {

  val sb =
    new StringBuilder()

  sb.append(
    "// ULTRA LEGEND ANIMATION CODE\n"
  )

  sb.append(
    "cleari()\n"
  )

  sb.append(
    "val p = Picture.circle(40)\n"
  )

  sb.append(
    "p.setPosition(0,0)\n"
  )

  sb.append(
    "draw(p)\n\n"
  )

  sb.append(
    "animate {\n"
  )

  sb.append(
    "  p.forward(3)\n"
  )

  sb.append(
    "  p.rotate(3)\n"
  )

  sb.append(
    "}\n"
  )

  generatedAnimationCode =
    sb.toString

  codeArea.setText(
    generatedAnimationCode
  )

  setStatus(
    "ANIMATION CODE GENERATED"
  )
}

// ============================================================
// ============================================================
// REAL-TIME 2D / 3D DRAWING VIEWER
// ============================================================

def openPreview3D(): Unit = {

  val win =
    new JFrame(
      "ULTRA DRAW 2D / 3D STUDIO"
    )

  win.setDefaultCloseOperation(
    WindowConstants.DISPOSE_ON_CLOSE
  )

  win.setSize(
    1100,
    780
  )

  win.setLocationRelativeTo(
    mainFrame
  )

  var angle = 0.0
  var running = true
  var is3D = true
  var depth = 35.0

  val viewer =
    new JPanel {

      override def paintComponent(
        graphics: Graphics
      ): Unit = {

        super.paintComponent(
          graphics
        )

        val gg =
          graphics.asInstanceOf[Graphics2D]

        gg.setRenderingHint(
          RenderingHints.KEY_ANTIALIASING,
          RenderingHints.VALUE_ANTIALIAS_ON
        )

        gg.setColor(
          new Color(
            10,
            14,
            32
          )
        )

        gg.fillRect(
          0,
          0,
          getWidth,
          getHeight
        )

        val old =
          gg.getTransform

        gg.translate(
          55,
          70
        )

        val scale =
          math.min(
            (getWidth - 110).toDouble / CANVAS_W.toDouble,
            (getHeight - 125).toDouble / CANVAS_H.toDouble
          )

        gg.scale(
          scale,
          scale
        )

        // Viewer background
        gg.setColor(
          new Color(
            248,
            250,
            255
          )
        )

        gg.fillRoundRect(
          0,
          0,
          CANVAS_W,
          CANVAS_H,
          20,
          20
        )

        val source =
          if (
            drawItems.nonEmpty
          ) {
            drawItems
          } else {
            ArrayBuffer[DrawItem]()
          }

        def draw2D(
          item: DrawItem
        ): Unit = {

          if (
            item.visible &&
            item.points.length >= 2
          ) {

            if (
              item.filled &&
              item.points.length >= 3
            ) {
              gg.setColor(
                item.fill
              )

              val xs =
                item.points.map(_.x).toArray

              val ys =
                item.points.map(_.y).toArray

              gg.fillPolygon(
                xs,
                ys,
                xs.length
              )
            }

            gg.setColor(
              item.stroke
            )

            gg.setStroke(
              new BasicStroke(
                math.max(
                  1.0f,
                  brushWidth
                )
              )
            )

            var j = 1

            while (
              j < item.points.length
            ) {
              gg.drawLine(
                item.points(j - 1).x,
                item.points(j - 1).y,
                item.points(j).x,
                item.points(j).y
              )
              j += 1
            }
          }
        }

        def project3D(
          p: Point
        ): Point = {

          val x0 =
            p.x.toDouble -
            CENTER_X.toDouble

          val y0 =
            p.y.toDouble -
            CENTER_Y.toDouble

          val z0 =
            0.0

          val ca =
            math.cos(angle)

          val sa =
            math.sin(angle)

          val xr =
            x0 * ca +
            z0 * sa

          val zr =
            -x0 * sa +
            z0 * ca

          val perspective =
            1.0 /
            math.max(
              0.45,
              1.0 + zr / 900.0
            )

          new Point(
            (CENTER_X + xr * perspective).toInt,
            (CENTER_Y + y0 * perspective).toInt
          )
        }

        def draw3D(
          item: DrawItem
        ): Unit = {

          if (
            !item.visible ||
            item.points.length < 2
          ) {
            return
          }

          val front =
            item.points.map(
              project3D
            ).toVector

          val back =
            item.points.map { p =>

              val shifted =
                new Point(
                  p.x + depth.toInt,
                  p.y - depth.toInt / 2
                )

              project3D(
                shifted
              )
            }.toVector

          if (
            item.filled &&
            front.length >= 3
          ) {
            gg.setColor(
              item.fill
            )

            val xs =
              front.map(_.x).toArray

            val ys =
              front.map(_.y).toArray

            gg.fillPolygon(
              xs,
              ys,
              xs.length
            )

            gg.setColor(
              new Color(
                math.max(
                  0,
                  item.fill.getRed - 35
                ),
                math.max(
                  0,
                  item.fill.getGreen - 35
                ),
                math.max(
                  0,
                  item.fill.getBlue - 35
                )
              )
            )

            var k = 1

            while (
              k < front.length
            ) {

              val sideX =
                Array(
                  front(k - 1).x,
                  front(k).x,
                  back(k).x,
                  back(k - 1).x
                )

              val sideY =
                Array(
                  front(k - 1).y,
                  front(k).y,
                  back(k).y,
                  back(k - 1).y
                )

              gg.fillPolygon(
                sideX,
                sideY,
                4
              )

              k += 1
            }
          }

          gg.setColor(
            item.stroke
          )

          gg.setStroke(
            new BasicStroke(
              math.max(
                1.0f,
                brushWidth
              )
            )
          )

          var j = 1

          while (
            j < front.length
          ) {

            gg.drawLine(
              front(j - 1).x,
              front(j - 1).y,
              front(j).x,
              front(j).y
            )

            gg.drawLine(
              back(j - 1).x,
              back(j - 1).y,
              back(j).x,
              back(j).y
            )

            gg.drawLine(
              front(j).x,
              front(j).y,
              back(j).x,
              back(j).y
            )

            j += 1
          }

          if (
            front.length >= 2
          ) {
            gg.drawLine(
              front.head.x,
              front.head.y,
              back.head.x,
              back.head.y
            )
          }
        }

        var i = 0

        while (
          i < source.length
        ) {

          if (is3D) {
            draw3D(
              source(i)
            )
          } else {
            draw2D(
              source(i)
            )
          }

          i += 1
        }

        // Center axis grid for depth perception
        if (is3D) {
          gg.setColor(
            new Color(
              120,
              130,
              150,
              100
            )
          )

          gg.drawLine(
            CENTER_X - 250,
            CENTER_Y,
            CENTER_X + 250,
            CENTER_Y
          )

          gg.drawLine(
            CENTER_X,
            CENTER_Y - 200,
            CENTER_X,
            CENTER_Y + 200
          )
        }

        gg.setTransform(old)

        gg.setColor(Color.WHITE)

        gg.setFont(
          new Font(
            "Arial",
            Font.BOLD,
            24
          )
        )

        gg.drawString(
          if (is3D) {
            "REAL-TIME 3D EXTRUDED VIEW"
          } else {
            "2D DRAWING VIEW"
          },
          25,
          35
        )

        gg.setFont(
          new Font(
            "Arial",
            Font.PLAIN,
            16
          )
        )

        gg.drawString(
          "Depth: " +
          depth.toInt +
          "   |   Objects: " +
          source.length,
          700,
          35
        )
      }
    }

  val twoDB =
    button(
      "2D"
    )

  val threeDB =
    button(
      "3D"
    )

  val rotateB =
    button(
      "AUTO ROTATE"
    )

  val stopB =
    button(
      "STOP"
    )

  val depthDownB =
    button(
      "DEPTH -"
    )

  val depthUpB =
    button(
      "DEPTH +"
    )

  val closeB =
    button(
      "CLOSE"
    )

  val controls =
    new JPanel(
      new FlowLayout(
        FlowLayout.CENTER,
        8,
        8
      )
    )

  controls.add(twoDB)
  controls.add(threeDB)
  controls.add(rotateB)
  controls.add(stopB)
  controls.add(depthDownB)
  controls.add(depthUpB)
  controls.add(closeB)

  twoDB.addActionListener(
    new ActionListener {
      override def actionPerformed(
        e: ActionEvent
      ): Unit = {
        is3D = false
        viewer.repaint()
      }
    }
  )

  threeDB.addActionListener(
    new ActionListener {
      override def actionPerformed(
        e: ActionEvent
      ): Unit = {
        is3D = true
        viewer.repaint()
      }
    }
  )

  rotateB.addActionListener(
    new ActionListener {
      override def actionPerformed(
        e: ActionEvent
      ): Unit = {
        running = true
      }
    }
  )

  stopB.addActionListener(
    new ActionListener {
      override def actionPerformed(
        e: ActionEvent
      ): Unit = {
        running = false
      }
    }
  )

  depthDownB.addActionListener(
    new ActionListener {
      override def actionPerformed(
        e: ActionEvent
      ): Unit = {
        depth =
          math.max(
            5.0,
            depth - 5.0
          )
        viewer.repaint()
      }
    }
  )

  depthUpB.addActionListener(
    new ActionListener {
      override def actionPerformed(
        e: ActionEvent
      ): Unit = {
        depth =
          math.min(
            120.0,
            depth + 5.0
          )
        viewer.repaint()
      }
    }
  )

  closeB.addActionListener(
    new ActionListener {
      override def actionPerformed(
        e: ActionEvent
      ): Unit = {
        running = false
        win.dispose()
      }
    }
  )

  val timer =
    new Timer(
      30,
      new ActionListener {
        override def actionPerformed(
          e: ActionEvent
        ): Unit = {
          if (running && is3D) {
            angle += 0.035
            viewer.repaint()
          }
        }
      }
    )

  timer.start()

  win.addWindowListener(
    new java.awt.event.WindowAdapter {
      override def windowClosed(
        e: java.awt.event.WindowEvent
      ): Unit = {
        running = false
        timer.stop()
      }
    }
  )

  win.setLayout(
    new BorderLayout()
  )

  win.add(
    viewer,
    BorderLayout.CENTER
  )

  win.add(
    controls,
    BorderLayout.SOUTH
  )

  win.setVisible(true)
}

// ============================================================
// GAME DATABASE
// ============================================================

val gameDatabase =
  Array(
    GameInfo(
      "SLING BALL",
      "MOUSE",
      "Drag and release the ball toward the target."
    ),

    GameInfo(
      "PONG",
      "MOUSE",
      "Move the paddle and keep the ball alive."
    ),

    GameInfo(
      "SNAKE",
      "KEYBOARD",
      "Use arrows or WASD to collect food."
    ),

    GameInfo(
      "BREAKOUT",
      "MOUSE",
      "Move the paddle and break blocks."
    ),

    GameInfo(
      "FLAPPY BIRD",
      "MOUSE",
      "Click to flap and pass the obstacles."
    ),

    GameInfo(
      "CAR DODGE",
      "KEYBOARD",
      "Move left and right to avoid traffic."
    ),

    GameInfo(
      "SPACE SHOOTER",
      "KEYBOARD",
      "Move and fire at falling enemies."
    ),

    GameInfo(
      "TARGET RUSH",
      "MOUSE",
      "Click targets as quickly as possible."
    ),

    GameInfo(
      "BALL CATCH",
      "MOUSE",
      "Move the basket and catch falling balls."
    ),

    GameInfo(
      "MAZE BALL",
      "MOUSE",
      "Drag the ball through a maze."
    ),

    GameInfo(
      "TIC TAC TOE",
      "MOUSE",
      "Play against a simple computer."
    ),

    GameInfo(
      "KEYBOARD RUNNER",
      "KEYBOARD",
      "Run, jump, collect coins and avoid enemies."
    ),

    GameInfo(
      "KEYBOARD SPACE",
      "KEYBOARD",
      "Control a spaceship and shoot enemies."
    ),

    GameInfo(
      "PLATFORM QUEST",
      "KEYBOARD",
      "Jump, collect coins and reach the goal."
    ),

    GameInfo(
      "DODGE FIELD",
      "MOUSE",
      "Move around and avoid falling enemies."
    )
  )

// ============================================================
// GAME WINDOW
// ============================================================

def gameFrame(
  title: String
): JFrame = {

  val frame =
    new JFrame(title)

  frame.setDefaultCloseOperation(
    WindowConstants.DISPOSE_ON_CLOSE
  )

  frame.setSize(
    900,
    650
  )

  frame.setLocationRelativeTo(
    mainFrame
  )

  frame
}

// ============================================================
// GAME 1 - SLING BALL
// ============================================================

def playSlingBall(): Unit = {

  val frame =
    gameFrame(
      "SLING BALL - MOUSE"
    )

  var ballX: Double =
    110.0

  var ballY: Double =
    510.0

  var vx: Double =
    0.0

  var vy: Double =
    0.0

  var launched: Boolean =
    false

  var won: Boolean =
    false

  val target =
    new java.awt.Rectangle(
      760,
      90,
      70,
      70
    )

  val panel =
    new JPanel {

      override def paintComponent(
        graphics: Graphics
      ): Unit = {

        super.paintComponent(
          graphics
        )

        val g =
          graphics.asInstanceOf[
            Graphics2D
          ]

        g.setPaint(
          new GradientPaint(
            0,
            0,
            new Color(30,100,160),
            0,
            getHeight,
            new Color(10,20,40)
          )
        )

        g.fillRect(
          0,
          0,
          getWidth,
          getHeight
        )

        g.setColor(
          Color.RED
        )

        g.fillOval(
          target.x,
          target.y,
          target.width,
          target.height
        )

        g.setColor(
          Color.YELLOW
        )

        g.fillOval(
          ballX.toInt - 10,
          ballY.toInt - 10,
          20,
          20
        )

        g.setColor(
          Color.WHITE
        )

        g.drawString(
          if (won)
            "YOU WIN!"
          else
            "MOVE MOUSE + RELEASE TO LAUNCH",
          20,
          30
        )
      }
    }

  panel.addMouseListener(
    new MouseAdapter {

      override def mouseReleased(
        e: MouseEvent
      ): Unit = {

        if (!launched) {

          vx =
            (
              ballX -
              e.getX
            ) * 0.08

          vy =
            (
              ballY -
              e.getY
            ) * 0.08

          launched =
            true
        }
      }
    }
  )

  val timer: Timer =
    new Timer(
      25,
      new ActionListener {

        override def actionPerformed(
          e: ActionEvent
        ): Unit = {

          if (
            launched &&
            !won
          ) {

            vy +=
              0.25

            ballX +=
              vx

            ballY +=
              vy

            if (
              ballX < 10 ||
              ballX > frame.getWidth - 10
            ) {
              vx =
                -vx
            }

            if (ballY < 10) {
              vy =
                -vy
            }

            if (
              ballY >
              frame.getHeight + 20
            ) {

              ballX =
                110

              ballY =
                510

              vx =
                0

              vy =
                0

              launched =
                false
            }

            if (
              target.contains(
                ballX.toInt,
                ballY.toInt
              )
            ) {
              won =
                true
            }

            panel.repaint()
          }
        }
      }
    )

  timer.start()

  frame.addWindowListener(
    new java.awt.event.WindowAdapter {

      override def windowClosed(
        e: java.awt.event.WindowEvent
      ): Unit = {
        timer.stop()
      }
    }
  )

  frame.add(
    panel
  )

  frame.setVisible(
    true
  )
}

// ============================================================
// GAME 2 - PONG
// ============================================================

def playPong(): Unit = {

  val frame =
    gameFrame(
      "PONG - MOUSE"
    )

  var paddleX: Double =
    350.0

  var ballX: Double =
    420.0

  var ballY: Double =
    280.0

  var vx: Double =
    5.0

  var vy: Double =
    4.0

  var score: Int =
    0

  val panel =
    new JPanel {

      override def paintComponent(
        graphics: Graphics
      ): Unit = {

        super.paintComponent(
          graphics
        )

        val g =
          graphics.asInstanceOf[
            Graphics2D
          ]

        g.setColor(
          Color.BLACK
        )

        g.fillRect(
          0,
          0,
          getWidth,
          getHeight
        )

        g.setColor(
          Color.WHITE
        )

        g.fillOval(
          ballX.toInt - 10,
          ballY.toInt - 10,
          20,
          20
        )

        g.fillRoundRect(
          paddleX.toInt,
          getHeight - 60,
          150,
          20,
          10,
          10
        )

        g.drawString(
          "SCORE: " + score,
          20,
          30
        )
      }
    }

  panel.addMouseMotionListener(
    new MouseMotionAdapter {

      override def mouseMoved(
        e: MouseEvent
      ): Unit = {

        paddleX =
          math.max(
            0,
            math.min(
              panel.getWidth - 150,
              e.getX - 75
            )
          )
      }
    }
  )

  val pongTimer: Timer =
    new Timer(
      25,
      new ActionListener {

        override def actionPerformed(
          e: ActionEvent
        ): Unit = {

          ballX +=
            vx

          ballY +=
            vy

          if (
            ballX < 10 ||
            ballX > panel.getWidth - 10
          ) {
            vx =
              -vx
          }

          if (ballY < 10) {
            vy =
              -vy
          }

          if (
            ballY >
            panel.getHeight - 85 &&
            ballX >= paddleX &&
            ballX <= paddleX + 150
          ) {

            vy =
              -math.abs(vy)

            score +=
              1
          }

          if (
            ballY >
            panel.getHeight
          ) {

            ballX =
              panel.getWidth / 2.0

            ballY =
              panel.getHeight / 2.0

            vx =
              5.0

            vy =
              -4.0

            score =
              0
          }

          panel.repaint()
        }
      }
    )

  pongTimer.start()

  frame.addWindowListener(
    new java.awt.event.WindowAdapter {

      override def windowClosed(
        e: java.awt.event.WindowEvent
      ): Unit = {
        pongTimer.stop()
      }
    }
  )

  frame.add(
    panel
  )

  frame.setVisible(
    true
  )
}

// ============================================================
// GAME 3 - KEYBOARD SNAKE
// ============================================================

def playSnake(): Unit = {

  val frame =
    gameFrame(
      "SNAKE - KEYBOARD"
    )

  val cell: Int =
    25

  val body =
    ArrayBuffer[Point](
      new Point(10,10),
      new Point(9,10),
      new Point(8,10)
    )

  var food =
    new Point(
      16,
      10
    )

  var dirX: Int =
    1

  var dirY: Int =
    0

  var score: Int =
    0

  var running: Boolean =
    true

  val panel =
    new JPanel {

      override def paintComponent(
        graphics: Graphics
      ): Unit = {

        super.paintComponent(
          graphics
        )

        val g =
          graphics.asInstanceOf[
            Graphics2D
          ]

        g.setColor(
          new Color(15,50,25)
        )

        g.fillRect(
          0,
          0,
          getWidth,
          getHeight
        )

        g.setColor(
          new Color(
            255,
            255,
            255,
            20
          )
        )

        var gx: Int =
          0

        while (
          gx <= getWidth
        ) {

          g.drawLine(
            gx,
            0,
            gx,
            getHeight
          )

          gx +=
            cell
        }

        var gy: Int =
          0

        while (
          gy <= getHeight
        ) {

          g.drawLine(
            0,
            gy,
            getWidth,
            gy
          )

          gy +=
            cell
        }

        g.setColor(
          Color.RED
        )

        g.fillOval(
          food.x * cell + 3,
          food.y * cell + 3,
          cell - 6,
          cell - 6
        )

        var i: Int =
          0

        while (
          i < body.length
        ) {

          g.setColor(
            if (i == 0)
              new Color(80,250,100)
            else
              new Color(35,175,65)
          )

          g.fillRoundRect(
            body(i).x * cell + 2,
            body(i).y * cell + 2,
            cell - 4,
            cell - 4,
            8,
            8
          )

          i +=
            1
        }

        g.setColor(
          Color.WHITE
        )

        g.setFont(
          new Font(
            "Arial",
            Font.BOLD,
            18
          )
        )

        g.drawString(
          "ARROWS / WASD   SCORE: " + score,
          15,
          25
        )

        if (!running) {

          g.setFont(
            new Font(
              "Arial",
              Font.BOLD,
              42
            )
          )

          g.drawString(
            "GAME OVER",
            290,
            300
          )
        }
      }
    }

  panel.setFocusable(
    true
  )

  panel.addKeyListener(
    new KeyAdapter {

      override def keyPressed(
        e: KeyEvent
      ): Unit = {

        val k =
          e.getKeyCode

        if (
          k == KeyEvent.VK_LEFT ||
          k == KeyEvent.VK_A
        ) {

          if (dirX != 1) {
            dirX =
              -1

            dirY =
              0
          }

        } else if (
          k == KeyEvent.VK_RIGHT ||
          k == KeyEvent.VK_D
        ) {

          if (dirX != -1) {
            dirX =
              1

            dirY =
              0
          }

        } else if (
          k == KeyEvent.VK_UP ||
          k == KeyEvent.VK_W
        ) {

          if (dirY != 1) {
            dirX =
              0

            dirY =
              -1
          }

        } else if (
          k == KeyEvent.VK_DOWN ||
          k == KeyEvent.VK_S
        ) {

          if (dirY != -1) {
            dirX =
              0

            dirY =
              1
          }
        }
      }
    }
  )

  val snakeTimer: Timer =
    new Timer(
      110,
      new ActionListener {

        override def actionPerformed(
          e: ActionEvent
        ): Unit = {

          if (
            running &&
            body.nonEmpty
          ) {

            val head =
              body.head

            val nx =
              head.x +
              dirX

            val ny =
              head.y +
              dirY

            val cols =
              math.max(
                1,
                panel.getWidth / cell
              )

            val rows =
              math.max(
                1,
                panel.getHeight / cell
              )

            if (
              nx < 0 ||
              ny < 0 ||
              nx >= cols ||
              ny >= rows
            ) {

              running =
                false

            } else {

              val newHead =
                new Point(
                  nx,
                  ny
                )

              var hitSelf: Boolean =
                false

              var i: Int =
                0

              while (
                i < body.length
              ) {

                if (
                  body(i).x == newHead.x &&
                  body(i).y == newHead.y
                ) {
                  hitSelf =
                    true
                }

                i +=
                  1
              }

              if (hitSelf) {

                running =
                  false

              } else {

                body.prepend(
                  newHead
                )

                if (
                  newHead.x == food.x &&
                  newHead.y == food.y
                ) {

                  score +=
                    1

                  var foundFood: Boolean =
                    false

                  while (!foundFood) {

                    val fx =
                      (
                        RANDOM_GEN.nextDouble() *
                        cols
                      ).toInt

                    val fy =
                      (
                        RANDOM_GEN.nextDouble() *
                        rows
                      ).toInt

                    foundFood =
                      true

                    var q: Int =
                      0

                    while (
                      q < body.length
                    ) {

                      if (
                        body(q).x == fx &&
                        body(q).y == fy
                      ) {
                        foundFood =
                          false
                      }

                      q +=
                        1
                    }

                    if (foundFood) {
                      food =
                        new Point(
                          fx,
                          fy
                        )
                    }
                  }

                } else {

                  body.remove(
                    body.length - 1
                  )
                }
              }
            }

            panel.repaint()
          }
        }
      }
    )

  snakeTimer.start()

  frame.addWindowListener(
    new java.awt.event.WindowAdapter {

      override def windowClosed(
        e: java.awt.event.WindowEvent
      ): Unit = {
        snakeTimer.stop()
      }
    }
  )

  frame.add(
    panel
  )

  frame.setVisible(
    true
  )

  panel.requestFocusInWindow()
}

// ============================================================
// GAME 4 - BREAKOUT
// ============================================================

def playBreakout(): Unit = {

  val frame =
    gameFrame(
      "BREAKOUT - MOUSE"
    )

  var paddleX: Double =
    350.0

  var bx: Double =
    450.0

  var by: Double =
    420.0

  var vx: Double =
    4.0

  var vy: Double =
    -4.0

  var score: Int =
    0

  val blocks =
    ArrayBuffer[Point]()

  var r: Int =
    0

  while (
    r < 5
  ) {

    var c: Int =
      0

    while (
      c < 10
    ) {

      blocks +=
        new Point(
          40 + c * 75,
          70 + r * 32
        )

      c +=
        1
    }

    r +=
      1
  }

  val panel =
    new JPanel {

      override def paintComponent(
        graphics: Graphics
      ): Unit = {

        super.paintComponent(
          graphics
        )

        val g =
          graphics.asInstanceOf[
            Graphics2D
          ]

        g.setColor(
          new Color(18,18,40)
        )

        g.fillRect(
          0,
          0,
          getWidth,
          getHeight
        )

        g.setColor(
          Color.WHITE
        )

        g.fillOval(
          bx.toInt - 9,
          by.toInt - 9,
          18,
          18
        )

        g.setColor(
          Color.CYAN
        )

        g.fillRoundRect(
          paddleX.toInt,
          getHeight - 55,
          150,
          18,
          10,
          10
        )

        g.setColor(
          Color.ORANGE
        )

        var i: Int =
          0

        while (
          i < blocks.length
        ) {

          g.fillRect(
            blocks(i).x,
            blocks(i).y,
            65,
            22
          )

          i +=
            1
        }

        g.setColor(
          Color.WHITE
        )

        g.drawString(
          "SCORE: " + score,
          20,
          28
        )
      }
    }

  panel.addMouseMotionListener(
    new MouseMotionAdapter {

      override def mouseMoved(
        e: MouseEvent
      ): Unit = {

        paddleX =
          math.max(
            0,
            math.min(
              panel.getWidth - 150,
              e.getX - 75
            )
          )
      }
    }
  )

  val breakoutTimer: Timer =
    new Timer(
      25,
      new ActionListener {

        override def actionPerformed(
          e: ActionEvent
        ): Unit = {

          bx +=
            vx

          by +=
            vy

          if (
            bx < 10 ||
            bx > panel.getWidth - 10
          ) {
            vx =
              -vx
          }

          if (by < 10) {
            vy =
              -vy
          }

          if (
            by >
            panel.getHeight - 75 &&
            bx >= paddleX &&
            bx <= paddleX + 150
          ) {
            vy =
              -math.abs(vy)
          }

          var hitBlock: Int =
            -1

          var i: Int =
            0

          while (
            i < blocks.length
          ) {

            if (
              bx >= blocks(i).x &&
              bx <= blocks(i).x + 65 &&
              by >= blocks(i).y &&
              by <= blocks(i).y + 22
            ) {

              hitBlock =
                i
            }

            i +=
              1
          }

          if (
            hitBlock >= 0
          ) {

            blocks.remove(
              hitBlock
            )

            vy =
              -vy

            score +=
              1
          }

          if (
            by >
            panel.getHeight
          ) {

            bx =
              panel.getWidth / 2.0

            by =
              panel.getHeight / 2.0

            vx =
              4.0

            vy =
              -4.0

            score =
              0
          }

          panel.repaint()
        }
      }
    )

  breakoutTimer.start()

  frame.addWindowListener(
    new java.awt.event.WindowAdapter {

      override def windowClosed(
        e: java.awt.event.WindowEvent
      ): Unit = {
        breakoutTimer.stop()
      }
    }
  )

  frame.add(
    panel
  )

  frame.setVisible(
    true
  )
}

// ============================================================
// GAME 5 - FLAPPY
// ============================================================

def playFlappy(): Unit = {

  val frame =
    gameFrame(
      "FLAPPY BIRD - MOUSE"
    )

  var birdY: Double =
    280.0

  var birdV: Double =
    0.0

  var pipeX: Double =
    850.0

  var gap: Double =
    290.0

  var score: Int =
    0

  val panel =
    new JPanel {

      override def paintComponent(
        graphics: Graphics
      ): Unit = {

        super.paintComponent(
          graphics
        )

        val g =
          graphics.asInstanceOf[
            Graphics2D
          ]

        g.setPaint(
          new GradientPaint(
            0,
            0,
            new Color(70,190,255),
            0,
            getHeight,
            Color.WHITE
          )
        )

        g.fillRect(
          0,
          0,
          getWidth,
          getHeight
        )

        g.setColor(
          Color.YELLOW
        )

        g.fillOval(
          150,
          birdY.toInt,
          45,
          35
        )

        g.setColor(
          Color.GREEN
        )

        g.fillRect(
          pipeX.toInt,
          0,
          70,
          gap.toInt - 85
        )

        g.fillRect(
          pipeX.toInt,
          gap.toInt + 85,
          70,
          getHeight
        )

        g.setColor(
          Color.BLACK
        )

        g.drawString(
          "CLICK TO FLAP   SCORE: " + score,
          20,
          30
        )
      }
    }

  panel.addMouseListener(
    new MouseAdapter {

      override def mousePressed(
        e: MouseEvent
      ): Unit = {

        birdV =
          -9.0
      }
    }
  )

  val flappyTimer: Timer =
    new Timer(
      25,
      new ActionListener {

        override def actionPerformed(
          e: ActionEvent
        ): Unit = {

          birdV +=
            0.4

          birdY +=
            birdV

          pipeX -=
            5.0

          if (
            pipeX <
            -80
          ) {

            pipeX =
              panel.getWidth + 20

            gap =
              170 +
              RANDOM_GEN.nextDouble() *
              220

            score +=
              1
          }

          if (
            birdY < 0 ||
            birdY > panel.getHeight
          ) {

            birdY =
              280

            birdV =
              0

            pipeX =
              panel.getWidth

            score =
              0
          }

          panel.repaint()
        }
      }
    )

  flappyTimer.start()

  frame.addWindowListener(
    new java.awt.event.WindowAdapter {

      override def windowClosed(
        e: java.awt.event.WindowEvent
      ): Unit = {
        flappyTimer.stop()
      }
    }
  )

  frame.add(
    panel
  )

  frame.setVisible(
    true
  )
}

// ============================================================
// GAME 6 - KEYBOARD CAR
// ============================================================

def playKeyboardCar(): Unit = {

  val frame =
    gameFrame(
      "CAR DODGE - KEYBOARD"
    )

  var playerX: Double =
    420.0

  var enemyX: Double =
    240.0

  var enemyY: Double =
    -120.0

  var score: Int =
    0

  var running: Boolean =
    true

  val panel =
    new JPanel {

      override def paintComponent(
        graphics: Graphics
      ): Unit = {

        super.paintComponent(
          graphics
        )

        val g =
          graphics.asInstanceOf[
            Graphics2D
          ]

        g.setColor(
          new Color(45,45,50)
        )

        g.fillRect(
          0,
          0,
          getWidth,
          getHeight
        )

        g.setColor(
          Color.WHITE
        )

        var roadY: Int =
          0

        while (
          roadY < getHeight
        ) {

          g.fillRect(
            getWidth / 2 - 5,
            roadY,
            10,
            60
          )

          roadY +=
            100
        }

        g.setColor(
          Color.BLUE
        )

        g.fillRoundRect(
          playerX.toInt,
          getHeight - 125,
          60,
          100,
          18,
          18
        )

        g.setColor(
          Color.RED
        )

        g.fillRoundRect(
          enemyX.toInt,
          enemyY.toInt,
          60,
          100,
          18,
          18
        )

        g.setColor(
          Color.WHITE
        )

        g.drawString(
          "LEFT / RIGHT   SCORE: " + score,
          15,
          28
        )

        if (!running) {

          g.setFont(
            new Font(
              "Arial",
              Font.BOLD,
              42
            )
          )

          g.drawString(
            "GAME OVER",
            300,
            300
          )
        }
      }
    }

  panel.setFocusable(
    true
  )

  panel.addKeyListener(
    new KeyAdapter {

      override def keyPressed(
        e: KeyEvent
      ): Unit = {

        val k =
          e.getKeyCode

        if (
          k == KeyEvent.VK_LEFT ||
          k == KeyEvent.VK_A
        ) {
          playerX -=
            15
        } else if (
          k == KeyEvent.VK_RIGHT ||
          k == KeyEvent.VK_D
        ) {
          playerX +=
            15
        }

        playerX =
          math.max(
            0,
            math.min(
              panel.getWidth - 60,
              playerX
            )
          )
      }
    }
  )

  val carTimer: Timer =
    new Timer(
      25,
      new ActionListener {

        override def actionPerformed(
          e: ActionEvent
        ): Unit = {

          if (running) {

            enemyY +=
              7

            if (
              enemyY >
              panel.getHeight
            ) {

              enemyY =
                -100

              enemyX =
                RANDOM_GEN.nextDouble() *
                math.max(
                  1,
                  panel.getWidth - 60
                )

              score +=
                1
            }

            val hit =
              enemyX + 60 >
              playerX &&
              enemyX <
              playerX + 60 &&
              enemyY + 100 >
              panel.getHeight - 125 &&
              enemyY <
              panel.getHeight

            if (hit) {
              running =
                false
            }

            panel.repaint()
          }
        }
      }
    )

  carTimer.start()

  frame.addWindowListener(
    new java.awt.event.WindowAdapter {

      override def windowClosed(
        e: java.awt.event.WindowEvent
      ): Unit = {
        carTimer.stop()
      }
    }
  )

  frame.add(
    panel
  )

  frame.setVisible(
    true
  )

  panel.requestFocusInWindow()
}

// ============================================================
// GAME 7 - KEYBOARD SPACE
// ============================================================

def playKeyboardSpace(): Unit = {

  val frame =
    gameFrame(
      "SPACE SHOOTER - KEYBOARD"
    )

  var shipX: Double =
    420.0

  var enemyX: Double =
    350.0

  var enemyY: Double =
    60.0

  var laserX: Double =
    -100.0

  var laserY: Double =
    -100.0

  var score: Int =
    0

  var running: Boolean =
    true

  val panel =
    new JPanel {

      override def paintComponent(
        graphics: Graphics
      ): Unit = {

        super.paintComponent(
          graphics
        )

        val g =
          graphics.asInstanceOf[
            Graphics2D
          ]

        g.setColor(
          new Color(5,10,30)
        )

        g.fillRect(
          0,
          0,
          getWidth,
          getHeight
        )

        g.setColor(
          Color.WHITE
        )

        var s: Int =
          0

        while (
          s < 90
        ) {

          g.fillOval(
            (
              s * 83
            ) %
            math.max(
              1,
              getWidth
            ),
            (
              s * 47
            ) %
            math.max(
              1,
              getHeight
            ),
            2,
            2
          )

          s +=
            1
        }

        g.setColor(
          Color.CYAN
        )

        val ship =
          new java.awt.Polygon()

        ship.addPoint(
          shipX.toInt,
          getHeight - 70
        )

        ship.addPoint(
          shipX.toInt + 25,
          getHeight - 130
        )

        ship.addPoint(
          shipX.toInt + 50,
          getHeight - 70
        )

        g.fillPolygon(
          ship
        )

        g.setColor(
          Color.RED
        )

        g.fillOval(
          enemyX.toInt,
          enemyY.toInt,
          45,
          35
        )

        if (
          laserY >
          -40
        ) {

          g.setColor(
            Color.YELLOW
          )

          g.fillRect(
            laserX.toInt,
            laserY.toInt,
            6,
            20
          )
        }

        g.setColor(
          Color.WHITE
        )

        g.drawString(
          "LEFT / RIGHT + SPACE   SCORE: " +
          score,
          15,
          28
        )

        if (!running) {

          g.setFont(
            new Font(
              "Arial",
              Font.BOLD,
              38
            )
          )

          g.drawString(
            "MISSION OVER",
            285,
            300
          )
        }
      }
    }

  panel.setFocusable(
    true
  )

  panel.addKeyListener(
    new KeyAdapter {

      override def keyPressed(
        e: KeyEvent
      ): Unit = {

        val k =
          e.getKeyCode

        if (
          k == KeyEvent.VK_LEFT ||
          k == KeyEvent.VK_A
        ) {
          shipX -=
            12
        } else if (
          k == KeyEvent.VK_RIGHT ||
          k == KeyEvent.VK_D
        ) {
          shipX +=
            12
        } else if (
          k == KeyEvent.VK_SPACE
        ) {

          laserX =
            shipX + 22

          laserY =
            panel.getHeight - 150
        }

        shipX =
          math.max(
            0,
            math.min(
              panel.getWidth - 50,
              shipX
            )
          )
      }
    }
  )

  val spaceTimer: Timer =
    new Timer(
      25,
      new ActionListener {

        override def actionPerformed(
          e: ActionEvent
        ): Unit = {

          if (running) {

            enemyY +=
              2

            laserY -=
              12

            if (
              laserX >= enemyX &&
              laserX <= enemyX + 45 &&
              laserY <= enemyY + 35 &&
              laserY >= enemyY - 20
            ) {

              score +=
                1

              enemyX =
                RANDOM_GEN.nextDouble() *
                math.max(
                  1,
                  panel.getWidth - 45
                )

              enemyY =
                50
            }

            if (
              enemyY >
              panel.getHeight
            ) {
              enemyY =
                50
            }

            panel.repaint()
          }
        }
      }
    )

  spaceTimer.start()

  frame.addWindowListener(
    new java.awt.event.WindowAdapter {

      override def windowClosed(
        e: java.awt.event.WindowEvent
      ): Unit = {
        spaceTimer.stop()
      }
    }
  )

  frame.add(
    panel
  )

  frame.setVisible(
    true
  )

  panel.requestFocusInWindow()
}

// ============================================================
// GAME 8 - TARGET RUSH
// ============================================================

def playTargetRush(): Unit = {

  val frame =
    gameFrame(
      "TARGET RUSH - MOUSE"
    )

  var tx: Double =
    350.0

  var ty: Double =
    250.0

  var score: Int =
    0

  var timeLeft: Int =
    20

  val panel =
    new JPanel {

      override def paintComponent(
        graphics: Graphics
      ): Unit = {

        super.paintComponent(
          graphics
        )

        val g =
          graphics.asInstanceOf[
            Graphics2D
          ]

        g.setColor(
          new Color(20,25,60)
        )

        g.fillRect(
          0,
          0,
          getWidth,
          getHeight
        )

        g.setColor(
          Color.RED
        )

        g.fillOval(
          tx.toInt,
          ty.toInt,
          80,
          80
        )

        g.setColor(
          Color.WHITE
        )

        g.drawOval(
          tx.toInt + 18,
          ty.toInt + 18,
          44,
          44
        )

        g.setFont(
          new Font(
            "Arial",
            Font.BOLD,
            20
          )
        )

        g.drawString(
          "SCORE: " +
          score +
          "   TIME: " +
          timeLeft,
          20,
          30
        )
      }
    }

  panel.addMouseListener(
    new MouseAdapter {

      override def mousePressed(
        e: MouseEvent
      ): Unit = {

        if (
          e.getX >= tx &&
          e.getX <= tx + 80 &&
          e.getY >= ty &&
          e.getY <= ty + 80 &&
          timeLeft > 0
        ) {

          score +=
            1

          tx =
            RANDOM_GEN.nextDouble() *
            math.max(
              1,
              panel.getWidth - 80
            )

          ty =
            70 +
            RANDOM_GEN.nextDouble() *
            math.max(
              1,
              panel.getHeight - 150
            )

          panel.repaint()
        }
      }
    }
  )

  val targetTimer: Timer =
    new Timer(
      1000,
      new ActionListener {

        override def actionPerformed(
          e: ActionEvent
        ): Unit = {

          timeLeft -=
            1

          if (
            timeLeft <=
            0
          ) {

            timeLeft =
              20

            score =
              0
          }

          panel.repaint()
        }
      }
    )

  targetTimer.start()

  frame.addWindowListener(
    new java.awt.event.WindowAdapter {

      override def windowClosed(
        e: java.awt.event.WindowEvent
      ): Unit = {
        targetTimer.stop()
      }
    }
  )

  frame.add(
    panel
  )

  frame.setVisible(
    true
  )
}

// ============================================================
// GAME 9 - BALL CATCH
// ============================================================

def playBallCatch(): Unit = {

  val frame =
    gameFrame(
      "BALL CATCH - MOUSE"
    )

  var basketX: Double =
    350.0

  var ballX: Double =
    200.0

  var ballY: Double =
    -50.0

  var score: Int =
    0

  val panel =
    new JPanel {

      override def paintComponent(
        graphics: Graphics
      ): Unit = {

        super.paintComponent(
          graphics
        )

        val g =
          graphics.asInstanceOf[
            Graphics2D
          ]

        g.setPaint(
          new GradientPaint(
            0,
            0,
            new Color(25,120,220),
            0,
            getHeight,
            new Color(170,235,255)
          )
        )

        g.fillRect(
          0,
          0,
          getWidth,
          getHeight
        )

        g.setColor(
          Color.YELLOW
        )

        g.fillOval(
          ballX.toInt,
          ballY.toInt,
          35,
          35
        )

        g.setColor(
          new Color(150,85,20)
        )

        g.fillRoundRect(
          basketX.toInt,
          getHeight - 80,
          150,
          35,
          15,
          15
        )

        g.setColor(
          Color.WHITE
        )

        g.drawString(
          "SCORE: " + score,
          20,
          30
        )
      }
    }

  panel.addMouseMotionListener(
    new MouseMotionAdapter {

      override def mouseMoved(
        e: MouseEvent
      ): Unit = {

        basketX =
          math.max(
            0,
            math.min(
              panel.getWidth - 150,
              e.getX - 75
            )
          )
      }
    }
  )

  val catchTimer: Timer =
    new Timer(
      30,
      new ActionListener {

        override def actionPerformed(
          e: ActionEvent
        ): Unit = {

          ballY +=
            6

          if (
            ballY >
            panel.getHeight - 110
          ) {

            if (
              ballX + 35 >= basketX &&
              ballX <= basketX + 150
            ) {
              score +=
                1
            }

            ballX =
              RANDOM_GEN.nextDouble() *
              math.max(
                1,
                panel.getWidth - 35
              )

            ballY =
              -40
          }

          panel.repaint()
        }
      }
    )

  catchTimer.start()

  frame.addWindowListener(
    new java.awt.event.WindowAdapter {

      override def windowClosed(
        e: java.awt.event.WindowEvent
      ): Unit = {
        catchTimer.stop()
      }
    }
  )

  frame.add(
    panel
  )

  frame.setVisible(
    true
  )
}

// ============================================================
// GAME 10 - MAZE BALL
// ============================================================

def playMazeBall(): Unit = {

  val frame =
    gameFrame(
      "MAZE BALL - MOUSE"
    )

  var ballX: Double =
    70.0

  var ballY: Double =
    70.0

  val target =
    new java.awt.Rectangle(
      730,
      500,
      60,
      60
    )

  val walls =
    Array(
      new java.awt.Rectangle(
        170,
        90,
        520,
        20
      ),
      new java.awt.Rectangle(
        170,
        90,
        20,
        340
      ),
      new java.awt.Rectangle(
        420,
        220,
        20,
        330
      ),
      new java.awt.Rectangle(
        680,
        90,
        20,
        430
      )
    )

  val panel =
    new JPanel {

      override def paintComponent(
        graphics: Graphics
      ): Unit = {

        super.paintComponent(
          graphics
        )

        val g =
          graphics.asInstanceOf[
            Graphics2D
          ]

        g.setColor(
          Color.WHITE
        )

        g.fillRect(
          0,
          0,
          getWidth,
          getHeight
        )

        g.setColor(
          new Color(75,75,85)
        )

        var i: Int =
          0

        while (
          i < walls.length
        ) {

          g.fillRect(
            walls(i).x,
            walls(i).y,
            walls(i).width,
            walls(i).height
          )

          i +=
            1
        }

        g.setColor(
          Color.GREEN
        )

        g.fillOval(
          target.x,
          target.y,
          target.width,
          target.height
        )

        g.setColor(
          Color.RED
        )

        g.fillOval(
          ballX.toInt,
          ballY.toInt,
          35,
          35
        )

        g.setColor(
          Color.BLACK
        )

        g.drawString(
          "DRAG RED BALL TO GREEN TARGET",
          20,
          30
        )
      }
    }

  panel.addMouseMotionListener(
    new MouseMotionAdapter {

      override def mouseDragged(
        e: MouseEvent
      ): Unit = {

        val nx =
          e.getX - 17

        val ny =
          e.getY - 17

        val probe =
          new java.awt.Rectangle(
            nx,
            ny,
            35,
            35
          )

        var hitWall: Boolean =
          false

        var i: Int =
          0

        while (
          i < walls.length
        ) {

          if (
            walls(i).intersects(
              probe
            )
          ) {
            hitWall =
              true
          }

          i +=
            1
        }

        if (!hitWall) {

          ballX =
            math.max(
              0,
              math.min(
                panel.getWidth - 35,
                nx
              )
            )

          ballY =
            math.max(
              0,
              math.min(
                panel.getHeight - 35,
                ny
              )
            )
        }

        if (
          target.contains(
            ballX.toInt + 17,
            ballY.toInt + 17
          )
        ) {

          JOptionPane.showMessageDialog(
            frame,
            "YOU WIN!"
          )
        }

        panel.repaint()
      }
    }
  )

  frame.add(
    panel
  )

  frame.setVisible(
    true
  )
}

// ============================================================
// GAME 11 - TIC TAC TOE
// ============================================================

def playTicTacToe(): Unit = {

  val frame =
    gameFrame(
      "TIC TAC TOE"
    )

  val board =
    Array.fill[Int](
      9
    )(
      0
    )

  var humanTurn: Boolean =
    true

  var finished: Boolean =
    false

  def win(
    player: Int
  ): Boolean = {

    val lines =
      Array(
        Array(0,1,2),
        Array(3,4,5),
        Array(6,7,8),
        Array(0,3,6),
        Array(1,4,7),
        Array(2,5,8),
        Array(0,4,8),
        Array(2,4,6)
      )

    var result: Boolean =
      false

    var i: Int =
      0

    while (
      i < lines.length
    ) {

      if (
        board(lines(i)(0)) == player &&
        board(lines(i)(1)) == player &&
        board(lines(i)(2)) == player
      ) {
        result =
          true
      }

      i +=
        1
    }

    result
  }

  def full(): Boolean = {

    var result: Boolean =
      true

    var i: Int =
      0

    while (
      i < 9
    ) {

      if (
        board(i) == 0
      ) {
        result =
          false
      }

      i +=
        1
    }

    result
  }

  def computerMove(): Int = {

    var chosen: Int =
      -1

    var i: Int =
      0

    while (
      i < 9 &&
      chosen < 0
    ) {

      if (
        board(i) == 0
      ) {
        chosen =
          i
      }

      i +=
        1
    }

    chosen
  }

  val panel =
    new JPanel {

      override def paintComponent(
        graphics: Graphics
      ): Unit = {

        super.paintComponent(
          graphics
        )

        val g =
          graphics.asInstanceOf[
            Graphics2D
          ]

        g.setColor(
          new Color(
            245,
            248,
            255
          )
        )

        g.fillRect(
          0,
          0,
          getWidth,
          getHeight
        )

        g.setColor(
          Color.BLACK
        )

        g.setStroke(
          new BasicStroke(
            6
          )
        )

        var i: Int =
          1

        while (
          i < 3
        ) {

          g.drawLine(
            i * getWidth / 3,
            0,
            i * getWidth / 3,
            getHeight
          )

          g.drawLine(
            0,
            i * getHeight / 3,
            getWidth,
            i * getHeight / 3
          )

          i +=
            1
        }

        g.setFont(
          new Font(
            "Arial",
            Font.BOLD,
            82
          )
        )

        i =
          0

        while (
          i < 9
        ) {

          if (
            board(i) != 0
          ) {

            g.setColor(
              if (
                board(i) == 1
              )
                Color.BLUE
              else
                Color.RED
            )

            val col =
              i % 3

            val row =
              i / 3

            g.drawString(
              if (
                board(i) == 1
              )
                "X"
              else
                "O",
              col *
              getWidth / 3 +
              65,
              row *
              getHeight / 3 +
              105
            )
          }

          i +=
            1
        }

        g.setColor(
          Color.DARK_GRAY
        )

        g.setFont(
          new Font(
            "Arial",
            Font.BOLD,
            18
          )
        )

        g.drawString(
          if (finished)
            "GAME OVER"
          else if (humanTurn)
            "YOUR TURN"
          else
            "COMPUTER TURN",
          20,
          28
        )
      }
    }

  panel.addMouseListener(
    new MouseAdapter {

      override def mousePressed(
        e: MouseEvent
      ): Unit = {

        if (
          humanTurn &&
          !finished
        ) {

          val cellW =
            math.max(
              1,
              panel.getWidth / 3
            )

          val cellH =
            math.max(
              1,
              panel.getHeight / 3
            )

          val col =
            math.max(
              0,
              math.min(
                2,
                e.getX / cellW
              )
            )

          val row =
            math.max(
              0,
              math.min(
                2,
                e.getY / cellH
              )
            )

          val idx =
            row * 3 + col

          if (
            board(idx) == 0
          ) {

            board(idx) =
              1

            if (
              win(1) ||
              full()
            ) {

              finished =
                true

            } else {

              humanTurn =
                false

              val ai =
                computerMove()

              if (
                ai >= 0
              ) {
                board(ai) =
                  2
              }

              if (
                win(2) ||
                full()
              ) {
                finished =
                  true
              } else {
                humanTurn =
                  true
              }
            }

            panel.repaint()
          }
        }
      }
    }
  )

  frame.add(
    panel
  )

  frame.setVisible(
    true
  )
}

// ============================================================
// GAME 12 - KEYBOARD RUNNER
// ============================================================

def playKeyboardRunner(): Unit = {

  val frame =
    gameFrame(
      "KEYBOARD RUNNER"
    )

  var px: Double =
    100.0

  var py: Double =
    430.0

  var velY: Double =
    0.0

  var score: Int =
    0

  var jumping: Boolean =
    false

  var running: Boolean =
    true

  val coins =
    ArrayBuffer[Point](
      new Point(260,390),
      new Point(470,320),
      new Point(670,390),
      new Point(820,280)
    )

  val enemies =
    ArrayBuffer[Point](
      new Point(530,450),
      new Point(760,450)
    )

  val panel =
    new JPanel {

      override def paintComponent(
        graphics: Graphics
      ): Unit = {

        super.paintComponent(
          graphics
        )

        val g =
          graphics.asInstanceOf[
            Graphics2D
          ]

        g.setPaint(
          new GradientPaint(
            0,
            0,
            new Color(70,180,250),
            0,
            getHeight,
            new Color(210,240,255)
          )
        )

        g.fillRect(
          0,
          0,
          getWidth,
          getHeight
        )

        g.setColor(
          new Color(65,180,75)
        )

        g.fillRect(
          0,
          getHeight - 100,
          getWidth,
          100
        )

        g.setColor(
          Color.BLUE
        )

        g.fillRoundRect(
          px.toInt,
          py.toInt,
          45,
          65,
          10,
          10
        )

        g.setColor(
          Color.YELLOW
        )

        var i: Int =
          0

        while (
          i < coins.length
        ) {

          g.fillOval(
            coins(i).x,
            coins(i).y,
            25,
            25
          )

          i +=
            1
        }

        g.setColor(
          Color.RED
        )

        i =
          0

        while (
          i < enemies.length
        ) {

          g.fillRect(
            enemies(i).x,
            enemies(i).y,
            45,
            45
          )

          i +=
            1
        }

        g.setColor(
          Color.WHITE
        )

        g.drawString(
          "A/D or ARROWS   SPACE = JUMP   SCORE: " +
          score,
          15,
          28
        )

        if (!running) {

          g.setColor(
            Color.BLACK
          )

          g.fillRect(
            0,
            0,
            getWidth,
            getHeight
          )

          g.setColor(
            Color.WHITE
          )

          g.setFont(
            new Font(
              "Arial",
              Font.BOLD,
              42
            )
          )

          g.drawString(
            "GAME OVER",
            300,
            300
          )
        }
      }
    }

  panel.setFocusable(
    true
  )

  panel.addKeyListener(
    new KeyAdapter {

      override def keyPressed(
        e: KeyEvent
      ): Unit = {

        if (!running) {
          return
        }

        val k =
          e.getKeyCode

        if (
          k == KeyEvent.VK_LEFT ||
          k == KeyEvent.VK_A
        ) {

          px -=
            12

        } else if (
          k == KeyEvent.VK_RIGHT ||
          k == KeyEvent.VK_D
        ) {

          px +=
            12

        } else if (
          k == KeyEvent.VK_SPACE ||
          k == KeyEvent.VK_UP
        ) {

          if (!jumping) {

            velY =
              -11

            jumping =
              true
          }
        }

        px =
          math.max(
            0,
            math.min(
              panel.getWidth - 45,
              px
            )
          )
      }
    }
  )

  val runnerTimer: Timer =
    new Timer(
      30,
      new ActionListener {

        override def actionPerformed(
          e: ActionEvent
        ): Unit = {

          if (running) {

            velY +=
              0.55

            py +=
              velY

            val ground =
              panel.getHeight - 165

            if (
              py >= ground
            ) {

              py =
                ground

              velY =
                0

              jumping =
                false
            }

            var i: Int =
              coins.length - 1

            while (
              i >= 0
            ) {

              val c =
                coins(i)

              val got =
                px + 45 > c.x &&
                px < c.x + 25 &&
                py + 65 > c.y &&
                py < c.y + 25

              if (got) {

                coins.remove(i)

                score +=
                  10
              }

              i -=
                1
            }

            i =
              0

            while (
              i < enemies.length
            ) {

              enemies(i).x -=
                3

              if (
                enemies(i).x < -60
              ) {

                enemies(i).x =
                  panel.getWidth +
                  100 +
                  i * 200
              }

              val hit =
                px + 45 > enemies(i).x &&
                px < enemies(i).x + 45 &&
                py + 65 > enemies(i).y &&
                py < enemies(i).y + 45

              if (hit) {
                running =
                  false
              }

              i +=
                1
            }

            panel.repaint()
          }
        }
      }
    )

  runnerTimer.start()

  frame.addWindowListener(
    new java.awt.event.WindowAdapter {

      override def windowClosed(
        e: java.awt.event.WindowEvent
      ): Unit = {
        runnerTimer.stop()
      }
    }
  )

  frame.add(
    panel
  )

  frame.setVisible(
    true
  )

  panel.requestFocusInWindow()
}

// ============================================================
// GAME ROUTER
// ============================================================

def launchGame(
  name: String
): Unit = {

  if (
    name == "SLING BALL"
  ) {
    playSlingBall()

  } else if (
    name == "PONG"
  ) {
    playPong()

  } else if (
    name == "SNAKE"
  ) {
    playSnake()

  } else if (
    name == "BREAKOUT"
  ) {
    playBreakout()

  } else if (
    name == "FLAPPY BIRD"
  ) {
    playFlappy()

  } else if (
    name == "CAR DODGE"
  ) {
    playKeyboardCar()

  } else if (
    name == "SPACE SHOOTER"
  ) {
    playKeyboardSpace()

  } else if (
    name == "TARGET RUSH"
  ) {
    playTargetRush()

  } else if (
    name == "BALL CATCH"
  ) {
    playBallCatch()

  } else if (
    name == "MAZE BALL"
  ) {
    playMazeBall()

  } else if (
    name == "TIC TAC TOE"
  ) {
    playTicTacToe()

  } else {
    playKeyboardRunner()
  }
}

// ============================================================
// GAME STUDIO
// ============================================================

def openGameStudio(): Unit = {

  val frame =
    new JFrame(
      "ULTRA LEGEND GAME STUDIO"
    )

  frame.setDefaultCloseOperation(
    WindowConstants.DISPOSE_ON_CLOSE
  )

  frame.setSize(
    1250,
    780
  )

  frame.setLocationRelativeTo(
    mainFrame
  )

  val model =
    new DefaultListModel[String]()

  var i: Int =
    0

  while (
    i < gameDatabase.length
  ) {

    model.addElement(
      gameDatabase(i).name
    )

    i +=
      1
  }

  val list =
    new JList[String](
      model
    )

  list.setSelectionMode(
    ListSelectionModel.SINGLE_SELECTION
  )

  list.setFont(
    new Font(
      "Arial",
      Font.BOLD,
      14
    )
  )

  val search =
    new JTextField(
      14
    )

  val searchButton =
    button(
      "SEARCH"
    )

  val playButton =
    button(
      "PLAY"
    )

  val codeButton =
    button(
      "GAME CODE"
    )

  val closeButton =
    button(
      "CLOSE"
    )

  val info =
    new JTextArea()

  info.setEditable(
    false
  )

  info.setFont(
    new Font(
      "Monospaced",
      Font.PLAIN,
      13
    )
  )

  info.setText(
    "ULTRA LEGEND GAME STUDIO\n\n" +
    "MOUSE + KEYBOARD GAMES\n\n" +
    "DOUBLE CLICK A GAME TO PLAY.\n" +
    "SELECT GAME AND PRESS GAME CODE.\n"
  )

  val searchPanel =
    new JPanel(
      new FlowLayout(
        FlowLayout.LEFT
      )
    )

  searchPanel.add(
    new JLabel(
      "SEARCH:"
    )
  )

  searchPanel.add(
    search
  )

  searchPanel.add(
    searchButton
  )

  val left =
    new JPanel(
      new BorderLayout()
    )

  left.add(
    new JLabel(
      "GAME LIBRARY",
      SwingConstants.CENTER
    ),
    BorderLayout.NORTH
  )

  left.add(
    searchPanel,
    BorderLayout.CENTER
  )

  left.add(
    new JScrollPane(
      list
    ),
    BorderLayout.SOUTH
  )

  val right =
    new JPanel(
      new BorderLayout()
    )

  right.add(
    new JLabel(
      "GAME INFORMATION",
      SwingConstants.CENTER
    ),
    BorderLayout.NORTH
  )

  right.add(
    new JScrollPane(
      info
    ),
    BorderLayout.CENTER
  )

  val split =
    new JSplitPane(
      JSplitPane.HORIZONTAL_SPLIT,
      left,
      right
    )

  split.setDividerLocation(
    380
  )

  list.addListSelectionListener(
    new javax.swing.event.ListSelectionListener {

      override def valueChanged(
        e: javax.swing.event.ListSelectionEvent
      ): Unit = {

        if (
          !e.getValueIsAdjusting
        ) {

          val selected =
            list.getSelectedValue

          if (
            selected != null
          ) {

            var j: Int =
              0

            while (
              j < gameDatabase.length
            ) {

              if (
                gameDatabase(j).name ==
                selected
              ) {

                info.setText(
                  "GAME\n\n" +
                  gameDatabase(j).name +
                  "\n\nMODE: " +
                  gameDatabase(j).mode +
                  "\n\n" +
                  gameDatabase(j).description
                )
              }

              j +=
                1
            }
          }
        }
      }
    }
  )

  searchButton.addActionListener(
    new ActionListener {

      override def actionPerformed(
        e: ActionEvent
      ): Unit = {

        val q =
          search.getText
            .trim
            .toLowerCase

        model.clear()

        var j: Int =
          0

        while (
          j < gameDatabase.length
        ) {

          if (
            q.isEmpty ||
            gameDatabase(j).name
              .toLowerCase
              .indexOf(q) >= 0
          ) {

            model.addElement(
              gameDatabase(j).name
            )
          }

          j +=
            1
        }
      }
    }
  )

  playButton.addActionListener(
    new ActionListener {

      override def actionPerformed(
        e: ActionEvent
      ): Unit = {

        val selected =
          list.getSelectedValue

        if (
          selected != null
        ) {

          launchGame(
            selected
          )

        } else {

          JOptionPane.showMessageDialog(
            frame,
            "SELECT A GAME FIRST"
          )
        }
      }
    }
  )

  list.addMouseListener(
    new MouseAdapter {

      override def mouseClicked(
        e: MouseEvent
      ): Unit = {

        if (
          e.getClickCount == 2
        ) {

          val selected =
            list.getSelectedValue

          if (
            selected != null
          ) {

            launchGame(
              selected
            )
          }
        }
      }
    }
  )

  codeButton.addActionListener(
    new ActionListener {

      override def actionPerformed(
        e: ActionEvent
      ): Unit = {

        val selected =
          list.getSelectedValue

        if (
          selected != null
        ) {

          generatedAnimationCode =
            "// GAME SOURCE: " +
            selected +
            "\n\n" +
            "cleari()\n" +
            "setAnimationDelay(5)\n\n" +
            "// Launch from ULTRA LEGEND GAME STUDIO."

          val codeFrame =
            new JFrame(
              "GAME CODE"
            )

          codeFrame.setDefaultCloseOperation(
            WindowConstants.DISPOSE_ON_CLOSE
          )

          codeFrame.setSize(
            820,
            580
          )

          codeFrame.setLocationRelativeTo(
            frame
          )

          val area =
            new JTextArea(
              generatedAnimationCode
            )

          area.setFont(
            new Font(
              "Monospaced",
              Font.PLAIN,
              13
            )
          )

          val copyGame =
            button(
              "COPY"
            )

          copyGame.addActionListener(
            new ActionListener {

              override def actionPerformed(
                e: ActionEvent
              ): Unit = {
                copyText(
                  area.getText
                )
              }
            }
          )

          codeFrame.add(
            new JScrollPane(
              area
            ),
            BorderLayout.CENTER
          )

          codeFrame.add(
            copyGame,
            BorderLayout.SOUTH
          )

          codeFrame.setVisible(
            true
          )

        } else {

          JOptionPane.showMessageDialog(
            frame,
            "SELECT A GAME FIRST"
          )
        }
      }
    }
  )

  closeButton.addActionListener(
    new ActionListener {

      override def actionPerformed(
        e: ActionEvent
      ): Unit = {
        frame.dispose()
      }
    }
  )

  val bottom =
    new JPanel(
      new FlowLayout(
        FlowLayout.CENTER
      )
    )

  bottom.add(
    playButton
  )

  bottom.add(
    codeButton
  )

  bottom.add(
    closeButton
  )

  frame.setLayout(
    new BorderLayout()
  )

  frame.add(
    split,
    BorderLayout.CENTER
  )

  frame.add(
    bottom,
    BorderLayout.SOUTH
  )

  frame.setVisible(
    true
  )
}

// ============================================================
// MAIN CANVAS
// ============================================================

canvas =
  new JPanel {

    override def paintComponent(
      graphics: Graphics
    ): Unit = {

      super.paintComponent(
        graphics
      )

      val g =
        graphics.asInstanceOf[
          Graphics2D
        ]

      g.setRenderingHint(
        RenderingHints.KEY_ANTIALIASING,
        RenderingHints.VALUE_ANTIALIAS_ON
      )

      g.setColor(
        Color.WHITE
      )

      g.fillRect(
        0,
        0,
        getWidth,
        getHeight
      )

      if (showGrid) {

        g.setColor(
          new Color(
            0,
            0,
            0,
            20
          )
        )

        var x: Int =
          0

        while (
          x <= CANVAS_W
        ) {

          g.drawLine(
            x,
            0,
            x,
            CANVAS_H
          )

          x +=
            50
        }

        var y: Int =
          0

        while (
          y <= CANVAS_H
        ) {

          g.drawLine(
            0,
            y,
            CANVAS_W,
            y
          )

          y +=
            50
        }
      }

      var i: Int =
        0

      while (
        i < drawItems.length
      ) {

        val item =
          drawItems(i)

        if (
          item.visible &&
          item.points.length >= 2
        ) {

          if (
            item.filled &&
            item.points.length >= 3
          ) {

            val path =
              new Path2D.Double()

            path.moveTo(
              item.points.head.x,
              item.points.head.y
            )

            var j: Int =
              1

            while (
              j < item.points.length
            ) {

              path.lineTo(
                item.points(j).x,
                item.points(j).y
              )

              j +=
                1
            }

            path.closePath()

            g.setColor(
              item.fill
            )

            g.fill(
              path
            )
          }

          g.setColor(
            item.stroke
          )

          g.setStroke(
            new BasicStroke(
              brushWidth,
              BasicStroke.CAP_ROUND,
              BasicStroke.JOIN_ROUND
            )
          )

          var j: Int =
            1

          while (
            j < item.points.length
          ) {

            g.drawLine(
              item.points(j - 1).x,
              item.points(j - 1).y,
              item.points(j).x,
              item.points(j).y
            )

            j +=
              1
          }
        }

        i +=
          1
      }

      if (
        tempPoints.length >= 2
      ) {

        g.setColor(
          strokeColor
        )

        g.setStroke(
          new BasicStroke(
            brushWidth,
            BasicStroke.CAP_ROUND,
            BasicStroke.JOIN_ROUND
          )
        )

        var j: Int =
          1

        while (
          j < tempPoints.length
        ) {

          g.drawLine(
            tempPoints(j - 1).x,
            tempPoints(j - 1).y,
            tempPoints(j).x,
            tempPoints(j).y
          )

          j +=
            1
        }
      }

      if (
        selectedIndex >= 0 &&
        selectedIndex < drawItems.length &&
        activeTool == "SELECT"
      ) {

        val r =
          boundsOf(
            drawItems(
              selectedIndex
            )
          )

        val box =
          new java.awt.Rectangle(
            r.x - 8,
            r.y - 8,
            r.width + 16,
            r.height + 16
          )

        g.setColor(
          new Color(
            0,
            105,
            240
          )
        )

        g.setStroke(
          new BasicStroke(
            2
          )
        )

        g.drawRect(
          box.x,
          box.y,
          box.width,
          box.height
        )

        val hs =
          handles(
            box
          )

        var h: Int =
          0

        while (
          h < hs.length
        ) {

          val hp =
            hs(h)._2

          g.setColor(
            Color.WHITE
          )

          g.fillRect(
            hp.x - 5,
            hp.y - 5,
            10,
            10
          )

          g.setColor(
            new Color(
              0,
              105,
              240
            )
          )

          g.drawRect(
            hp.x - 5,
            hp.y - 5,
            10,
            10
          )

          h +=
            1
        }
      }
    }
  }

canvas.setFocusable(
  true
)

// ============================================================
// CANVAS MOUSE INPUT
// ============================================================

canvas.addMouseListener(
  new MouseAdapter {

    override def mousePressed(
      e: MouseEvent
    ): Unit = {

      val p =
        canvasPoint(e)

      if (
        activeTool == "SELECT"
      ) {

        val hit =
          findObject(p)

        if (
          selectedIndex >= 0 &&
          selectedIndex < drawItems.length
        ) {

          val handle =
            resizeHandle(
              drawItems(
                selectedIndex
              ),
              p
            )

          if (
            handle != ""
          ) {

            if (
              !drawItems(
                selectedIndex
              ).locked
            ) {

              saveHistory()

              resizingNow =
                true

              movingNow =
                false

              resizeHandle =
                handle

              originalPoints =
                drawItems(
                  selectedIndex
                ).points

              lastMousePoint =
                p
            }

          } else if (
            hit == selectedIndex
          ) {

            if (
              !drawItems(
                selectedIndex
              ).locked
            ) {

              saveHistory()

              movingNow =
                true

              resizingNow =
                false

              lastMousePoint =
                p
            }

          } else {

            selectedIndex =
              hit
          }

        } else {

          selectedIndex =
            hit
        }

        repaintCanvas()

      } else if (
        activeTool == "ERASER"
      ) {

        val hit =
          findObject(p)

        if (
          hit >= 0 &&
          !drawItems(hit).locked
        ) {

          saveHistory()

          drawItems.remove(
            hit
          )

          selectedIndex =
            -1

          repaintCanvas()

          setStatus(
            "ERASED"
          )
        }

      } else if (
        activeTool == "BUCKET"
      ) {

        val hit =
          findObject(p)

        if (
          hit >= 0
        ) {

          selectedIndex =
            hit

          fillSelected()

        } else {

          setStatus(
            "CLICK A CLOSED OBJECT"
          )
        }

      } else {

        saveHistory()

        tempPoints.clear()

        tempPoints +=
          p

        drawingNow =
          true

        selectedIndex =
          -1

        setStatus(
          "DRAWING..."
        )
      }
    }

    override def mouseReleased(
      e: MouseEvent
    ): Unit = {

      if (
        activeTool == "SELECT"
      ) {

        movingNow =
          false

        resizingNow =
          false

        repaintCanvas()

      } else if (
        drawingNow
      ) {

        tempPoints +=
          canvasPoint(e)

        if (
          tempPoints.length >= 2
        ) {

          drawItems +=
            DrawItem(
              tempPoints.toVector,
              strokeColor,
              fillColor,
              closed(
                tempPoints.toVector
              ),
              "FREEHAND",
              true,
              false
            )

          selectedIndex =
            drawItems.length - 1
        }

        tempPoints.clear()

        drawingNow =
          false

        repaintCanvas()

        setStatus(
          "DRAWING RECORDED"
        )
      }
    }
  }
)

// ============================================================
// CANVAS DRAG INPUT
// ============================================================

canvas.addMouseMotionListener(
  new MouseMotionAdapter {

    override def mouseDragged(
      e: MouseEvent
    ): Unit = {

      val p =
        canvasPoint(e)

      if (
        activeTool == "SELECT"
      ) {

        if (
          movingNow &&
          selectedIndex >= 0
        ) {

          moveSelected(
            p.x -
            lastMousePoint.x,
            p.y -
            lastMousePoint.y
          )

          lastMousePoint =
            p

          repaintCanvas()

        } else if (
          resizingNow &&
          selectedIndex >= 0
        ) {

          resizeSelected(
            p
          )

          repaintCanvas()
        }

      } else if (
        drawingNow
      ) {

        if (
          tempPoints.isEmpty ||
          distance(
            tempPoints.last,
            p
          ) >= 2.0
        ) {

          tempPoints +=
            p

          repaintCanvas()
        }
      }
    }
  }
)

// ============================================================
// TOOLBAR BUTTONS
// ============================================================

val pencilBtn =
  button(
    "PENCIL"
  )

val selectBtn =
  button(
    "SELECT"
  )

val eraserBtn =
  button(
    "ERASER"
  )

val bucketBtn =
  button(
    "BUCKET"
  )

val undoBtn =
  button(
    "UNDO"
  )

val redoBtn =
  button(
    "REDO"
  )

val clearBtn =
  button(
    "CLEAR"
  )

val drawCodeBtn =
  button(
    "DRAW CODE"
  )

val animationBtn =
  button(
    "ANIMATION"
  )

val animationStudioBtn =
  button(
    "ANIMATION DRAW"
  )

val preview3DBtn =
  button(
    "2D / 3D"
  )

val copyBtn =
  button(
    "COPY"
  )

val saveBtn =
  button(
    "SAVE"
  )

val gamesBtn =
  button(
    "GAMES"
  )

val gridBtn =
  button(
    "GRID"
  )

val snapBtn =
  button(
    "SNAP"
  )

val strokeBtn =
  button(
    "STROKE"
  )

val fillBtn =
  button(
    "FILL"
  )

// ============================================================
// TOOLBAR
// ============================================================

val toolbar =
  new JPanel(
    new FlowLayout(
      FlowLayout.LEFT,
      3,
      3
    )
  )

toolbar.add(
  pencilBtn
)

toolbar.add(
  selectBtn
)

toolbar.add(
  eraserBtn
)

toolbar.add(
  bucketBtn
)

toolbar.add(
  undoBtn
)

toolbar.add(
  redoBtn
)

toolbar.add(
  clearBtn
)

toolbar.add(
  drawCodeBtn
)

toolbar.add(
  animationBtn
)

toolbar.add(
  preview3DBtn
)

toolbar.add(
  copyBtn
)

toolbar.add(
  saveBtn
)

toolbar.add(
  gamesBtn
)

toolbar.add(
  gridBtn
)

toolbar.add(
  snapBtn
)

toolbar.add(
  strokeBtn
)

toolbar.add(
  fillBtn
)

// ============================================================
// OBJECT / SHAPE LIST UI
// ============================================================

objectList =
  new JList[String](
    objectModel
  )

objectList.setFont(
  new Font(
    "Arial",
    Font.BOLD,
    13
  )
)

shapeList =
  new JList[String](
    shapeModel
  )

shapeList.setFont(
  new Font(
    "Arial",
    Font.BOLD,
    13
  )
)

objectSearch =
  new JTextField(
    12
  )

val objectSearchBtn =
  button(
    "SEARCH"
  )

val objectSearchPanel =
  new JPanel(
    new FlowLayout(
      FlowLayout.LEFT
    )
  )

objectSearchPanel.add(
  objectSearch
)

objectSearchPanel.add(
  objectSearchBtn
)

val objectPanel =
  new JPanel(
    new BorderLayout()
  )

objectPanel.add(
  new JLabel(
    "OBJECT LIBRARY",
    SwingConstants.CENTER
  ),
  BorderLayout.NORTH
)

objectPanel.add(
  objectSearchPanel,
  BorderLayout.CENTER
)

objectPanel.add(
  new JScrollPane(
    objectList
  ),
  BorderLayout.SOUTH
)

val shapePanel =
  new JPanel(
    new BorderLayout()
  )

shapePanel.add(
  new JLabel(
    "SHAPE LIBRARY",
    SwingConstants.CENTER
  ),
  BorderLayout.NORTH
)

shapePanel.add(
  new JScrollPane(
    shapeList
  ),
  BorderLayout.CENTER
)

val libraryTabs =
  new JTabbedPane()

libraryTabs.addTab(
  "OBJECTS",
  objectPanel
)

libraryTabs.addTab(
  "SHAPES",
  shapePanel
)

// ============================================================
// SEARCH
// ============================================================

objectSearchBtn.addActionListener(
  new ActionListener {

    override def actionPerformed(
      e: ActionEvent
    ): Unit = {

      val q =
        objectSearch
          .getText
          .trim
          .toLowerCase

      objectModel.clear()

      var i: Int =
        0

      while (
        i < objects.length
      ) {

        if (
          q.isEmpty ||
          objects(i)
            .toLowerCase
            .indexOf(q) >= 0
        ) {

          objectModel.addElement(
            objects(i)
          )
        }

        i +=
          1
      }
    }
  }
)

// ============================================================
// DOUBLE CLICK OBJECT
// ============================================================

objectList.addMouseListener(
  new MouseAdapter {

    override def mouseClicked(
      e: MouseEvent
    ): Unit = {

      if (
        e.getClickCount == 2
      ) {

        val selected =
          objectList.getSelectedValue

        if (
          selected != null
        ) {

          addObject(
            selected
          )
        }
      }
    }
  }
)

// ============================================================
// DOUBLE CLICK SHAPE
// ============================================================

shapeList.addMouseListener(
  new MouseAdapter {

    override def mouseClicked(
      e: MouseEvent
    ): Unit = {

      if (
        e.getClickCount == 2
      ) {

        val selected =
          shapeList.getSelectedValue

        if (
          selected != null
        ) {

          addShape(
            selected
          )
        }
      }
    }
  }
)

// ============================================================
// CODE AREA
// ============================================================

codeArea =
  new JTextArea()

codeArea.setEditable(
  false
)

codeArea.setFont(
  new Font(
    "Monospaced",
    Font.PLAIN,
    13
  )
)

codeArea.setText(
  "// DRAW SOMETHING\n" +
  "// THEN PRESS DRAW CODE"
)

// ============================================================
// PANELS
// ============================================================

val canvasPanel =
  new JPanel(
    new BorderLayout()
  )

canvasPanel.add(
  new JLabel(
    "DRAWING CANVAS",
    SwingConstants.CENTER
  ),
  BorderLayout.NORTH
)

canvasPanel.add(
  canvas,
  BorderLayout.CENTER
)

val codePanel =
  new JPanel(
    new BorderLayout()
  )

codePanel.add(
  new JLabel(
    "GENERATED KOJO / SCALA CODE",
    SwingConstants.CENTER
  ),
  BorderLayout.NORTH
)

codePanel.add(
  new JScrollPane(
    codeArea
  ),
  BorderLayout.CENTER
)

val canvasCodeSplit =
  new JSplitPane(
    JSplitPane.HORIZONTAL_SPLIT,
    canvasPanel,
    codePanel
  )

canvasCodeSplit.setDividerLocation(
  660
)

val mainSplit =
  new JSplitPane(
    JSplitPane.HORIZONTAL_SPLIT,
    libraryTabs,
    canvasCodeSplit
  )

mainSplit.setDividerLocation(
  285
)

// ============================================================
// STATUS
// ============================================================

statusLabel =
  new JLabel(
    "ULTRA LEGEND X9 READY"
  )

statusLabel.setFont(
  new Font(
    "Arial",
    Font.BOLD,
    13
  )
)

// ============================================================
// MAIN FRAME
// ============================================================

mainFrame =
  new JFrame(
    "ULTRA LEGEND DRAW + GAME STUDIO X9"
  )

mainFrame.setDefaultCloseOperation(
  WindowConstants.DISPOSE_ON_CLOSE
)

mainFrame.setSize(
  1650,
  900
)

mainFrame.setMinimumSize(
  new Dimension(
    1100,
    650
  )
)

mainFrame.setLocationRelativeTo(
  null
)

mainFrame.setLayout(
  new BorderLayout(
    4,
    4
  )
)

mainFrame.add(
  toolbar,
  BorderLayout.NORTH
)

mainFrame.add(
  mainSplit,
  BorderLayout.CENTER
)

mainFrame.add(
  statusLabel,
  BorderLayout.SOUTH
)

// ============================================================
// TOOL EVENTS
// ============================================================

pencilBtn.addActionListener(
  new ActionListener {

    override def actionPerformed(
      e: ActionEvent
    ): Unit = {

      activeTool =
        "PENCIL"

      selectedIndex =
        -1

      canvas.setCursor(
        pencilCursor
      )

      setStatus(
        "PENCIL MODE"
      )

      canvas.requestFocusInWindow()
    }
  }
)

selectBtn.addActionListener(
  new ActionListener {

    override def actionPerformed(
      e: ActionEvent
    ): Unit = {

      activeTool =
        "SELECT"

      canvas.setCursor(
        Cursor.getPredefinedCursor(
          Cursor.DEFAULT_CURSOR
        )
      )

      setStatus(
        "SELECT / MOVE / RESIZE"
      )

      repaintCanvas()

      canvas.requestFocusInWindow()
    }
  }
)

eraserBtn.addActionListener(
  new ActionListener {

    override def actionPerformed(
      e: ActionEvent
    ): Unit = {

      activeTool =
        "ERASER"

      selectedIndex =
        -1

      canvas.setCursor(
        pencilCursor
      )

      setStatus(
        "ERASER MODE"
      )

      canvas.requestFocusInWindow()
    }
  }
)

bucketBtn.addActionListener(
  new ActionListener {

    override def actionPerformed(
      e: ActionEvent
    ): Unit = {

      activeTool =
        "BUCKET"

      canvas.setCursor(
        Cursor.getPredefinedCursor(
          Cursor.HAND_CURSOR
        )
      )

      setStatus(
        "BUCKET MODE"
      )

      canvas.requestFocusInWindow()
    }
  }
)

undoBtn.addActionListener(
  new ActionListener {

    override def actionPerformed(
      e: ActionEvent
    ): Unit = {

      undo()
    }
  }
)

redoBtn.addActionListener(
  new ActionListener {

    override def actionPerformed(
      e: ActionEvent
    ): Unit = {

      redo()
    }
  }
)

clearBtn.addActionListener(
  new ActionListener {

    override def actionPerformed(
      e: ActionEvent
    ): Unit = {

      clearAll()
    }
  }
)

drawCodeBtn.addActionListener(
  new ActionListener {

    override def actionPerformed(
      e: ActionEvent
    ): Unit = {

      generateCode()
    }
  }
)

animationBtn.addActionListener(
  new ActionListener {

    override def actionPerformed(
      e: ActionEvent
    ): Unit = {

      // Open the independent Animation Studio window.
      // The regular ANIMATION button is intentionally a window launcher.
      openFrameAnimationStudio()
    }
  }
)

animationStudioBtn.addActionListener(
  new ActionListener {

    override def actionPerformed(
      e: ActionEvent
    ): Unit = {

      openFrameAnimationStudio()
    }
  }
)

preview3DBtn.addActionListener(
  new ActionListener {

    override def actionPerformed(
      e: ActionEvent
    ): Unit = {

      openPreview3D()
    }
  }
)

copyBtn.addActionListener(
  new ActionListener {

    override def actionPerformed(
      e: ActionEvent
    ): Unit = {

      copyText(
        codeArea.getText
      )
    }
  }
)

saveBtn.addActionListener(
  new ActionListener {

    override def actionPerformed(
      e: ActionEvent
    ): Unit = {

      saveText(
        codeArea.getText
      )
    }
  }
)

gamesBtn.addActionListener(
  new ActionListener {

    override def actionPerformed(
      e: ActionEvent
    ): Unit = {

      openGameStudio()
    }
  }
)

gridBtn.addActionListener(
  new ActionListener {

    override def actionPerformed(
      e: ActionEvent
    ): Unit = {

      showGrid =
        !showGrid

      repaintCanvas()

      if (showGrid)
        setStatus("GRID ON")
      else
        setStatus("GRID OFF")
    }
  }
)

snapBtn.addActionListener(
  new ActionListener {

    override def actionPerformed(
      e: ActionEvent
    ): Unit = {

      snapToGrid =
        !snapToGrid

      if (snapToGrid)
        setStatus("SNAP ON")
      else
        setStatus("SNAP OFF")
    }
  }
)

strokeBtn.addActionListener(
  new ActionListener {

    override def actionPerformed(
      e: ActionEvent
    ): Unit = {

      val selected =
        JColorChooser.showDialog(
          mainFrame,
          "CHOOSE STROKE COLOR",
          strokeColor
        )

      if (
        selected != null
      ) {

        strokeColor =
          selected

        setStatus(
          "STROKE COLOR CHANGED"
        )
      }
    }
  }
)

fillBtn.addActionListener(
  new ActionListener {

    override def actionPerformed(
      e: ActionEvent
    ): Unit = {

      val selected =
        JColorChooser.showDialog(
          mainFrame,
          "CHOOSE FILL COLOR",
          fillColor
        )

      if (
        selected != null
      ) {

        fillColor =
          selected

        if (
          selectedIndex >= 0
        ) {
          fillSelected()
        } else {
          setStatus(
            "FILL COLOR CHANGED"
          )
        }
      }
    }
  }
)

// ============================================================
// KEYBOARD SHORTCUTS
// ============================================================

canvas.addKeyListener(
  new KeyAdapter {

    override def keyPressed(
      e: KeyEvent
    ): Unit = {

      val k: Int =
        e.getKeyCode

      if (
        e.isControlDown &&
        k == KeyEvent.VK_Z
      ) {

        undo()

      } else if (
        e.isControlDown &&
        k == KeyEvent.VK_Y
      ) {

        redo()

      } else if (
        e.isControlDown &&
        k == KeyEvent.VK_G
      ) {

        generateCode()

      } else if (
        e.isControlDown &&
        k == KeyEvent.VK_S
      ) {

        saveText(
          codeArea.getText
        )

      } else if (
        e.isControlDown &&
        k == KeyEvent.VK_C
      ) {

        copyText(
          codeArea.getText
        )

      } else if (
        e.isControlDown &&
        k == KeyEvent.VK_D
      ) {

        openGameStudio()

      } else if (
        k == KeyEvent.VK_DELETE
      ) {

        deleteSelected()

      } else if (
        k == KeyEvent.VK_P
      ) {

        activeTool =
          "PENCIL"

        setStatus(
          "PENCIL"
        )

      } else if (
        k == KeyEvent.VK_V
      ) {

        activeTool =
          "SELECT"

        setStatus(
          "SELECT"
        )

      } else if (
        k == KeyEvent.VK_B
      ) {

        activeTool =
          "BUCKET"

        setStatus(
          "BUCKET"
        )

      } else if (
        k == KeyEvent.VK_E
      ) {

        activeTool =
          "ERASER"

        setStatus(
          "ERASER"
        )

      } else if (
        k == KeyEvent.VK_F1
      ) {

        JOptionPane.showMessageDialog(
          mainFrame,
          "ULTRA LEGEND X9\n\n" +
          "P = Pencil\n" +
          "V = Select\n" +
          "B = Bucket\n" +
          "E = Eraser\n" +
          "DELETE = Delete\n\n" +
          "CTRL+Z = Undo\n" +
          "CTRL+Y = Redo\n" +
          "CTRL+G = Draw Code\n" +
          "CTRL+C = Copy\n" +
          "CTRL+S = Save\n" +
          "CTRL+D = Game Studio\n\n" +
          "SELECT + DRAG = MOVE\n" +
          "SELECT + HANDLE = RESIZE",
          "SHORTCUTS",
          JOptionPane.INFORMATION_MESSAGE
        )
      }
    }
  }
)

// ============================================================
// ============================================================
// SMOOTH PENCIL CURSOR + SIMPLE SOUND SUPPORT
// ============================================================

var selectedSoundFile: String = ""
var soundEnabled: Boolean = true


def makePencilCursor(): Cursor = {
  try {
    val img =
      new BufferedImage(
        32,
        32,
        BufferedImage.TYPE_INT_ARGB
      )

    val cg =
      img.createGraphics()

    cg.setRenderingHint(
      RenderingHints.KEY_ANTIALIASING,
      RenderingHints.VALUE_ANTIALIAS_ON
    )

    cg.setColor(
      new Color(246, 205, 128)
    )

    val body =
      new java.awt.Polygon()

    body.addPoint(6, 4)
    body.addPoint(24, 22)
    body.addPoint(19, 27)
    body.addPoint(1, 9)

    cg.fillPolygon(body)

    cg.setColor(
      new Color(70, 45, 30)
    )

    cg.fillPolygon(
      Array(1, 9, 6, 4),
      Array(9, 28, 4, 4),
      4
    )

    cg.setColor(
      new Color(220, 65, 70)
    )

    cg.fillRect(
      20,
      18,
      7,
      7
    )

    cg.dispose()

    val toolkit =
      java.awt.Toolkit.getDefaultToolkit

    toolkit.createCustomCursor(
      img,
      new Point(2, 28),
      "pencil"
    )
  } catch {
    case _: Exception =>
      pencilCursor
  }
}

val pencilCursor =
  makePencilCursor()


def playStudioBeep(): Unit = {
  if (soundEnabled) {
    try {
      Toolkit.getDefaultToolkit.beep()
    } catch {
      case _: Exception =>
    }
  }
}

// ============================================================
// ULTRA INDEPENDENT FRAME ANIMATION STUDIO
// ============================================================

val animationCommandWords = Array(
  "NONE",
  "MOVE LEFT",
  "MOVE RIGHT",
  "MOVE UP",
  "MOVE DOWN",
  "JUMP",
  "BOUNCE",
  "WALK",
  "RUN",
  "FLOAT",
  "FALL",
  "RISE",
  "SHAKE",
  "WIGGLE",
  "WAVE",
  "DANCE",
  "SPIN",
  "ROTATE LEFT",
  "ROTATE RIGHT",
  "FLIP HORIZONTAL",
  "FLIP VERTICAL",
  "SCALE UP",
  "SCALE DOWN",
  "PULSE",
  "SWING",
  "ORBIT",
  "ORBIT REVERSE",
  "CIRCLE MOVE",
  "SPIRAL MOVE",
  "FIGURE EIGHT",
  "ZIGZAG",
  "JELLY",
  "HEARTBEAT",
  "TWINKLE",
  "SPARKLE",
  "VIBRATE",
  "TREMBLE",
  "TILT LEFT",
  "TILT RIGHT",
  "ROCK",
  "SPRING",
  "POP",
  "SLIDE LEFT",
  "SLIDE RIGHT",
  "SLIDE UP",
  "SLIDE DOWN",
  "FLY",
  "SOAR",
  "LAND",
  "TAKE OFF",
  "CAR DRIVE",
  "CAR REVERSE",
  "WHEEL SPIN",
  "BALL ROLL",
  "BALL BOUNCE",
  "CLOUD DRIFT",
  "RAIN FALL",
  "SNOW FALL",
  "FIRE FLICKER",
  "WATER WAVE",
  "TREE SWAY",
  "LEAF FALL",
  "BIRD FLY",
  "BUTTERFLY FLUTTER",
  "FISH SWIM",
  "CLOCK TICK",
  "CANDLE FLICKER",
  "LIGHT FLASH",
  "MAGIC APPEAR",
  "MAGIC DISAPPEAR",
  "TELEPORT",
  "PORTAL",
  "LASER",
  "EXPLODE",
  "PARTICLE BURST",
  "RING PULSE",
  "STAR SPIN",
  "MOON ORBIT",
  "PLANET ORBIT",
  "SUN PULSE",
  "ELECTRIC",
  "NEON PULSE",
  "GLITCH",
  "CYBER MOVE",
  "FUTURE DRIVE",
  "TIME WARP",
  "TIME REWIND",
  "SPEED UP",
  "SLOW MOTION",
  "EASE IN",
  "EASE OUT",
  "EASE IN OUT",
  "SPRING IN",
  "SPRING OUT",
  "ELASTIC",
  "RANDOM SHAKE",
  "RANDOM MOVE",
  "REVERSE",
  "PING PONG",
  "POSE",
  "STEP",
  "CHASE",
  "PATROL",
  "FOLLOW"
)


def animationClone(source: ArrayBuffer[DrawItem]): ArrayBuffer[DrawItem] = {
  cloneItems(source)
}


def animationSnapshot(): Unit = {
  if (animationFrames.isEmpty) {
    animationFrames += animationClone(drawItems)
  } else if (
    animationCurrentFrame >= 0 &&
    animationCurrentFrame < animationFrames.length
  ) {
    animationFrames(animationCurrentFrame) = animationClone(drawItems)
  }
}


def animationLoad(index: Int): Unit = {
  if (animationFrames.nonEmpty) {
    val safe =
      math.max(
        0,
        math.min(
          animationFrames.length - 1,
          index
        )
      )

    drawItems.clear()
    drawItems ++= animationClone(animationFrames(safe))
    animationCurrentFrame = safe
    selectedIndex = -1
    repaintCanvas()
  }
}


def animationNextFrame(): Unit = {
  animationSnapshot()
  animationFrames += ArrayBuffer[DrawItem]()
  animationCurrentFrame = animationFrames.length - 1
  drawItems.clear()
  selectedIndex = -1
}


def animationCopy(): Unit = {
  animationSnapshot()
  animationClipboard.clear()

  if (
    selectedIndex >= 0 &&
    selectedIndex < drawItems.length
  ) {
    animationClipboard +=
      animationClone(
        ArrayBuffer(drawItems(selectedIndex))
      ).head
  } else {
    animationClipboard ++=
      animationClone(drawItems)
  }
}


def animationPaste(): Unit = {
  if (animationClipboard.nonEmpty) {
    val pasted = animationClone(animationClipboard)

    val shifted =
      pasted.map { item =>
        item.copy(
          points =
            item.points.map { p =>
              new Point(
                p.x + 25,
                p.y + 15
              )
            }
        )
      }

    drawItems ++= shifted

    if (drawItems.nonEmpty) {
      selectedIndex = drawItems.length - 1
    }
  }
}


def animationTransform(
  source: ArrayBuffer[DrawItem],
  command: String,
  step: Int
): ArrayBuffer[DrawItem] = {

  val out =
    animationClone(source)

  var minX = 0.0
  var maxX = 0.0
  var minY = 0.0
  var maxY = 0.0
  var pointCount = 0

  var i = 0

  while (i < out.length) {
    var j = 0
    while (j < out(i).points.length) {
      val p = out(i).points(j)

      if (pointCount == 0) {
        minX = p.x.toDouble
        maxX = p.x.toDouble
        minY = p.y.toDouble
        maxY = p.y.toDouble
      } else {
        minX = math.min(minX, p.x.toDouble)
        maxX = math.max(maxX, p.x.toDouble)
        minY = math.min(minY, p.y.toDouble)
        maxY = math.max(maxY, p.y.toDouble)
      }

      pointCount += 1
      j += 1
    }
    i += 1
  }

  if (pointCount == 0) {
    return out
  }

  val cx =
    (minX + maxX) / 2.0

  val cy =
    (minY + maxY) / 2.0

  val t =
    step.toDouble

  val phase =
    t / 12.0

  i = 0

  while (i < out.length) {

    val item =
      out(i)

    val pts =
      item.points.map { p =>

        var nx = p.x.toDouble
        var ny = p.y.toDouble

        if (
          command == "MOVE LEFT" ||
          command == "SLIDE LEFT"
        ) {
          nx -= t * 5.0

        } else if (
          command == "MOVE RIGHT" ||
          command == "SLIDE RIGHT"
        ) {
          nx += t * 5.0

        } else if (
          command == "MOVE UP" ||
          command == "RISE" ||
          command == "SLIDE UP"
        ) {
          ny -= t * 4.0

        } else if (
          command == "MOVE DOWN" ||
          command == "FALL" ||
          command == "SLIDE DOWN"
        ) {
          ny += t * 4.0

        } else if (
          command == "JUMP" ||
          command == "BOUNCE" ||
          command == "SPRING"
        ) {
          ny -=
            math.sin(phase * math.Pi) * 60.0

        } else if (
          command == "WALK" ||
          command == "STEP"
        ) {
          nx +=
            math.sin(t * 0.55) * 10.0
          ny -=
            math.abs(math.sin(t * 0.55)) * 6.0

        } else if (command == "RUN") {
          nx += t * 7.0
          ny += math.sin(t * 0.9) * 7.0

        } else if (
          command == "FLOAT" ||
          command == "SOAR"
        ) {
          ny -= math.sin(t * 0.35) * 18.0

        } else if (
          command == "SCALE UP" ||
          command == "PULSE" ||
          command == "HEARTBEAT"
        ) {
          val k =
            1.0 + math.sin(phase * math.Pi) * 0.12

          nx =
            cx + (nx - cx) * k
          ny =
            cy + (ny - cy) * k

        } else if (command == "SCALE DOWN") {
          val k =
            1.0 - math.sin(phase * math.Pi) * 0.10

          nx =
            cx + (nx - cx) * k
          ny =
            cy + (ny - cy) * k

        } else if (
          command == "ROTATE LEFT" ||
          command == "SPIN" ||
          command == "STAR SPIN"
        ) {
          val a =
            -t * 7.0 * math.Pi / 180.0

          val dx =
            nx - cx

          val dy =
            ny - cy

          val rotatedX =
            dx * math.cos(a) - dy * math.sin(a)

          val rotatedY =
            dx * math.sin(a) + dy * math.cos(a)

          nx =
            cx + rotatedX

          ny =
            cy + rotatedY

        } else if (
          command == "ROTATE RIGHT" ||
          command == "TILT RIGHT"
        ) {
          val a =
            t * 7.0 * math.Pi / 180.0

          val dx =
            nx - cx

          val dy =
            ny - cy

          val rotatedX =
            dx * math.cos(a) - dy * math.sin(a)

          val rotatedY =
            dx * math.sin(a) + dy * math.cos(a)

          nx =
            cx + rotatedX

          ny =
            cy + rotatedY

        } else if (
          command == "FLIP HORIZONTAL"
        ) {
          nx =
            cx - (nx - cx)

        } else if (
          command == "FLIP VERTICAL"
        ) {
          ny =
            cy - (ny - cy)

        } else if (
          command == "WAVE" ||
          command == "WIGGLE" ||
          command == "TREE SWAY" ||
          command == "WATER WAVE"
        ) {
          ny +=
            math.sin(
              p.x.toDouble * 0.05 +
              t * 0.55
            ) * 12.0

        } else if (
          command == "SHAKE" ||
          command == "VIBRATE" ||
          command == "TREMBLE" ||
          command == "RANDOM SHAKE"
        ) {
          nx +=
            math.sin(t * 2.4) * 11.0
          ny +=
            math.cos(t * 2.0) * 8.0

        } else if (
          command == "SWING" ||
          command == "ROCK" ||
          command == "TILT LEFT"
        ) {
          val a =
            math.sin(phase * math.Pi) *
            10.0 * math.Pi / 180.0

          val dx =
            nx - cx

          val dy =
            ny - cy

          val rotatedX =
            dx * math.cos(a) - dy * math.sin(a)

          val rotatedY =
            dx * math.sin(a) + dy * math.cos(a)

          nx =
            cx + rotatedX

          ny =
            cy + rotatedY

        } else if (
          command == "ORBIT" ||
          command == "ORBIT REVERSE" ||
          command == "CIRCLE MOVE" ||
          command == "MOON ORBIT" ||
          command == "PLANET ORBIT"
        ) {
          var orbitSign = 1.0

          if (command == "ORBIT REVERSE") {
            orbitSign = -1.0
          }

          nx +=
            math.cos(t * 0.35 * orbitSign) * 16.0

          ny +=
            math.sin(t * 0.35 * orbitSign) * 10.0

        } else if (
          command == "SPIRAL MOVE"
        ) {
          val radius =
            math.min(60.0, t * 1.6)

          nx +=
            math.cos(t * 0.45) * radius

          ny +=
            math.sin(t * 0.45) * radius

        } else if (
          command == "FIGURE EIGHT"
        ) {
          nx +=
            math.sin(t * 0.35) * 20.0

          ny +=
            math.sin(t * 0.70) * 12.0

        } else if (
          command == "ZIGZAG"
        ) {
          var zig = -14.0

          if (step % 2 == 0) {
            zig = 14.0
          }

          nx += zig
          ny -= 4.0

        } else if (
          command == "TWINKLE" ||
          command == "SPARKLE"
        ) {
          val s =
            math.sin(t * 0.8)

          nx =
            cx + (nx - cx) * (1.0 + s * 0.05)

          ny =
            cy + (ny - cy) * (1.0 + s * 0.05)

        } else if (
          command == "POP" ||
          command == "SPRING IN"
        ) {
          val k =
            0.70 +
            math.sin(phase * math.Pi) * 0.35

          nx =
            cx + (nx - cx) * k

          ny =
            cy + (ny - cy) * k

        } else if (
          command == "SPRING OUT" ||
          command == "ELASTIC"
        ) {
          val k =
            1.0 +
            math.sin(phase * math.Pi * 2.0) * 0.16

          nx =
            cx + (nx - cx) * k

          ny =
            cy + (ny - cy) * k

        } else if (
          command == "JELLY"
        ) {
          nx +=
            math.sin(t * 0.8 + p.y * 0.03) * 8.0
          ny +=
            math.sin(t * 1.0 + p.x * 0.03) * 6.0

        } else if (
          command == "VIBRATE"
        ) {
          nx += math.sin(t * 4.0) * 5.0
          ny += math.cos(t * 4.0) * 5.0

        } else if (
          command == "DANCE" ||
          command == "CYBER MOVE"
        ) {
          nx +=
            math.sin(t * 0.60) * 14.0

          ny -=
            math.abs(math.sin(t * 0.60)) * 10.0

        } else if (
          command == "FLY" ||
          command == "TAKE OFF"
        ) {
          nx += t * 3.0
          ny -= t * 2.0

        } else if (
          command == "LAND"
        ) {
          ny += t * 2.0

        } else if (
          command == "CAR DRIVE" ||
          command == "FUTURE DRIVE"
        ) {
          nx += t * 8.0
          ny += math.sin(t * 0.5) * 2.0

        } else if (
          command == "CAR REVERSE"
        ) {
          nx -= t * 6.0

        } else if (
          command == "WHEEL SPIN" ||
          command == "BALL ROLL"
        ) {
          nx += t * 5.0
          ny += math.sin(t * 0.8) * 2.0

        } else if (
          command == "BALL BOUNCE"
        ) {
          ny -=
            math.abs(math.sin(t * 0.6)) * 45.0

        } else if (
          command == "CLOUD DRIFT"
        ) {
          nx += t * 3.0
          ny += math.sin(t * 0.3) * 5.0

        } else if (
          command == "RAIN FALL" ||
          command == "SNOW FALL" ||
          command == "LEAF FALL"
        ) {
          ny += t * 4.0
          nx += math.sin(t * 0.4) * 10.0

        } else if (
          command == "BIRD FLY" ||
          command == "BUTTERFLY FLUTTER"
        ) {
          nx += t * 4.0
          ny += math.sin(t * 0.7) * 20.0

        } else if (
          command == "FISH SWIM"
        ) {
          nx += t * 3.0
          ny += math.sin(t * 0.55) * 12.0

        } else if (
          command == "CLOCK TICK" ||
          command == "CANDLE FLICKER"
        ) {
          val a =
            math.sin(t * 0.9) * 3.0 * math.Pi / 180.0

          val dx =
            nx - cx

          val dy =
            ny - cy

          nx =
            cx + dx * math.cos(a) - dy * math.sin(a)

          ny =
            cy + dx * math.sin(a) + dy * math.cos(a)

        } else if (
          command == "PULSE" ||
          command == "RING PULSE" ||
          command == "SUN PULSE" ||
          command == "NEON PULSE"
        ) {
          val k =
            1.0 + math.sin(t * 0.7) * 0.08

          nx =
            cx + (nx - cx) * k

          ny =
            cy + (ny - cy) * k

        } else if (
          command == "EASE IN" ||
          command == "EASE IN OUT"
        ) {
          val e =
            (t / 12.0) * (t / 12.0)

          nx += e * 20.0

        } else if (
          command == "EASE OUT"
        ) {
          val q =
            t / 12.0

          val e =
            1.0 - (1.0 - q) * (1.0 - q)

          nx += e * 20.0

        } else if (
          command == "SPEED UP"
        ) {
          nx += t * t * 0.45

        } else if (
          command == "SLOW MOTION"
        ) {
          nx += t * 2.0

        } else if (
          command == "RANDOM MOVE"
        ) {
          nx +=
            math.sin(t * 1.7 + p.x * 0.01) * 8.0
          ny +=
            math.cos(t * 1.3 + p.y * 0.01) * 8.0

        } else if (
          command == "PING PONG"
        ) {
          var direction = 1.0

          if (step > 6) {
            direction = -1.0
          }

          nx +=
            direction * math.min(30.0, math.abs(t - 6.0) * 5.0)

        } else if (
          command == "REVERSE"
        ) {
          nx -= t * 4.0

        } else if (
          command == "TELEPORT"
        ) {
          if (step >= 6) {
            nx += 180.0
          }

        } else if (
          command == "MAGIC APPEAR" ||
          command == "MAGIC DISAPPEAR" ||
          command == "BLINK"
        ) {
          nx +=
            math.sin(t * 1.4) * 2.0

        } else if (
          command == "TIME WARP"
        ) {
          val q =
            math.sin(t * 0.5)

          nx += q * t
          ny -= q * 0.5 * t

        } else if (
          command == "TIME REWIND"
        ) {
          nx -= t * 3.0

        } else if (
          command == "POSE"
        ) {
          nx +=
            math.sin(t * 0.5) * 2.0

        } else if (
          command == "CHASE" ||
          command == "PATROL" ||
          command == "FOLLOW"
        ) {
          nx += t * 3.5
          ny += math.sin(t * 0.5) * 7.0
        }

        new Point(
          nx.toInt,
          ny.toInt
        )
      }.toVector

    out(i) =
      item.copy(
        points = pts
      )

    i += 1
  }

  out
}


// ============================================================
// DRAW ONE FRAME
// ============================================================

def paintFrameScene(
  g: Graphics2D,
  source: ArrayBuffer[DrawItem],
  ghost: ArrayBuffer[DrawItem],
  showGhost: Boolean,
  selected: Int
): Unit = {

  g.setColor(Color.WHITE)
  g.fillRect(
    0,
    0,
    CANVAS_W,
    CANVAS_H
  )

  g.setColor(
    new Color(235, 238, 245)
  )

  var gx = 0

  while (gx <= CANVAS_W) {
    g.drawLine(
      gx,
      0,
      gx,
      CANVAS_H
    )
    gx += 25
  }

  var gy = 0

  while (gy <= CANVAS_H) {
    g.drawLine(
      0,
      gy,
      CANVAS_W,
      gy
    )
    gy += 25
  }

  def drawItemsWithAlpha(
    items: ArrayBuffer[DrawItem],
    alpha: Int
  ): Unit = {

    var i = 0

    while (i < items.length) {

      val item =
        items(i)

      if (
        item.visible &&
        item.points.length >= 2
      ) {

        val oldComposite =
          g.getComposite

        g.setComposite(
          java.awt.AlphaComposite.getInstance(
            java.awt.AlphaComposite.SRC_OVER,
            alpha.toFloat / 255.0f
          )
        )

        if (
          item.filled &&
          item.points.length >= 3
        ) {
          g.setColor(item.fill)

          val xs =
            item.points.map(_.x).toArray

          val ys =
            item.points.map(_.y).toArray

          g.fillPolygon(
            xs,
            ys,
            xs.length
          )
        }

        g.setColor(item.stroke)

        g.setStroke(
          new BasicStroke(
            math.max(
              1.0f,
              brushWidth
            ),
            BasicStroke.CAP_ROUND,
            BasicStroke.JOIN_ROUND
          )
        )

        var j = 1

        while (j < item.points.length) {
          g.drawLine(
            item.points(j - 1).x,
            item.points(j - 1).y,
            item.points(j).x,
            item.points(j).y
          )
          j += 1
        }

        g.setComposite(oldComposite)
      }

      i += 1
    }
  }

  if (showGhost && ghost.nonEmpty) {
    drawItemsWithAlpha(
      ghost,
      55
    )
  }

  drawItemsWithAlpha(
    source,
    255
  )

  if (
    selected >= 0 &&
    selected < source.length
  ) {
    val r =
      boundsOf(
        source(selected)
      )

    g.setColor(
      new Color(
        30,
        120,
        255,
        190
      )
    )

    g.setStroke(
      new BasicStroke(
        2.0f,
        BasicStroke.CAP_ROUND,
        BasicStroke.JOIN_ROUND
      )
    )

    g.drawRect(
      r.x - 6,
      r.y - 6,
      r.width + 12,
      r.height + 12
    )

    val handlesNow =
      handles(r)

    var h = 0

    while (h < handlesNow.length) {
      val hp = handlesNow(h)._2
      g.fillRect(
        hp.x - 4,
        hp.y - 4,
        8,
        8
      )
      h += 1
    }
  }
}


// ============================================================
// GENERATED FRAME ANIMATION CODE
// ============================================================

def generatedAnimationItemsCode(
  items: ArrayBuffer[DrawItem],
  indent: String
): String = {

  val sb =
    new StringBuilder()

  var i = 0

  while (i < items.length) {

    val item =
      items(i)

    if (
      item.visible &&
      item.points.length >= 2
    ) {

      sb.append(
        indent +
        "// " +
        item.name.replace(
          "\"",
          "'"
        ) +
        "\n"
      )

      sb.append(
        indent +
        "val pts" +
        i +
        " = Array(\n"
      )

      var j = 0

      while (j < item.points.length) {

        sb.append(
          indent +
          "  new Point(" +
          item.points(j).x +
          "," +
          item.points(j).y +
          ")"
        )

        if (
          j < item.points.length - 1
        ) {
          sb.append(",")
        }

        sb.append("\n")
        j += 1
      }

      sb.append(
        indent +
        ")\n"
      )

      if (
        item.filled &&
        item.points.length >= 3
      ) {
        sb.append(
          indent +
          "g.setColor(new Color(" +
          item.fill.getRed +
          "," +
          item.fill.getGreen +
          "," +
          item.fill.getBlue +
          "))\n"
        )

        sb.append(
          indent +
          "val fx" +
          i +
          " = pts" +
          i +
          ".map(_.x).toArray\n"
        )

        sb.append(
          indent +
          "val fy" +
          i +
          " = pts" +
          i +
          ".map(_.y).toArray\n"
        )

        sb.append(
          indent +
          "g.fillPolygon(fx" +
          i +
          ", fy" +
          i +
          ", fx" +
          i +
          ".length)\n"
        )
      }

      sb.append(
        indent +
        "g.setColor(new Color(" +
        item.stroke.getRed +
        "," +
        item.stroke.getGreen +
        "," +
        item.stroke.getBlue +
        "))\n"
      )

      sb.append(
        indent +
        "g.setStroke(new BasicStroke(" +
        math.max(1.0f, brushWidth) +
        "f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND))\n"
      )

      sb.append(
        indent +
        "var k" +
        i +
        " = 1\n"
      )

      sb.append(
        indent +
        "while (k" +
        i +
        " < pts" +
        i +
        ".length) {\n"
      )

      sb.append(
        indent +
        "  g.drawLine(pts" +
        i +
        "(k" +
        i +
        " - 1).x, pts" +
        i +
        "(k" +
        i +
        " - 1).y, pts" +
        i +
        "(k" +
        i +
        ").x, pts" +
        i +
        "(k" +
        i +
        ").y)\n"
      )

      sb.append(
        indent +
        "  k" +
        i +
        " += 1\n"
      )

      sb.append(
        indent +
        "}\n"
      )
    }

    i += 1
  }

  sb.toString
}


def generateFrameAnimationStudioCode(): Unit = {

  animationSnapshot()

  val sb =
    new StringBuilder()

  sb.append(
    "import java.awt.BasicStroke\n"
  )
  sb.append(
    "import java.awt.Color\n"
  )
  sb.append(
    "import java.awt.Graphics2D\n"
  )
  sb.append(
    "import java.awt.Point\n"
  )
  sb.append(
    "import java.awt.RenderingHints\n"
  )
  sb.append(
    "import java.awt.event.ActionEvent\n"
  )
  sb.append(
    "import javax.swing.JFrame\n"
  )
  sb.append(
    "import javax.swing.JPanel\n"
  )
  sb.append(
    "import javax.swing.Timer\n"
  )
  sb.append(
    "import javax.swing.WindowConstants\n"
  )
  sb.append(
    "import javax.sound.sampled.AudioSystem\n"
  )
  sb.append(
    "import javax.sound.sampled.Clip\n\n"
  )

  sb.append(
    "val W = " + CANVAS_W + "\n"
  )
  sb.append(
    "val H = " + CANVAS_H + "\n"
  )
  sb.append(
    "var frameNo = 0\n"
  )
  sb.append(
    "val voiceOverFile = \"\"\n"
  )
  sb.append(
    "def playVoice(): Unit = {\n"
  )
  sb.append(
    "  if (voiceOverFile != \"\") {\n"
  )
  sb.append(
    "    try {\n"
  )
  sb.append(
    "      val clip = AudioSystem.getClip\n"
  )
  sb.append(
    "      val ais = AudioSystem.getAudioInputStream(new java.io.File(voiceOverFile))\n"
  )
  sb.append(
    "      clip.open(ais)\n"
  )
  sb.append(
    "      clip.start()\n"
  )
  sb.append(
    "    } catch {\n"
  )
  sb.append(
    "      case _: Exception =>\n"
  )
  sb.append(
    "    }\n"
  )
  sb.append(
    "  }\n"
  )
  sb.append(
    "}\n\n"
  )

  sb.append(
    "val w = new JFrame(\"GENERATED ANIMATION\")\n"
  )
  sb.append(
    "w.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE)\n"
  )
  sb.append(
    "w.setSize(W + 30, H + 70)\n"
  )
  sb.append(
    "w.setLocationRelativeTo(null)\n\n"
  )

  sb.append(
    "val panel = new JPanel {\n"
  )
  sb.append(
    "  override def paintComponent(graphics: java.awt.Graphics): Unit = {\n"
  )
  sb.append(
    "    super.paintComponent(graphics)\n"
  )
  sb.append(
    "    val g = graphics.asInstanceOf[Graphics2D]\n"
  )
  sb.append(
    "    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)\n"
  )
  sb.append(
    "    g.setColor(Color.WHITE)\n"
  )
  sb.append(
    "    g.fillRect(0, 0, W, H)\n"
  )

  var f = 0

  while (f < animationFrames.length) {

    sb.append(
      "    if (frameNo == " +
      f +
      ") {\n"
    )

    sb.append(
      generatedAnimationItemsCode(
        animationFrames(f),
        "      "
      )
    )

    sb.append(
      "    }\n"
    )

    f += 1
  }

  sb.append(
    "    g.setColor(Color.DARK_GRAY)\n"
  )
  sb.append(
    "    g.drawString(\"FRAME \" + (frameNo + 1) + \" / \" + animationFrames.length + \"\", 15, 20)\n"
  )
  sb.append(
    "  }\n"
  )
  sb.append(
    "}\n\n"
  )

  sb.append(
    "val timer = new Timer(" +
    math.max(
      20,
      1000 / animationFps
    ) +
    ", new javax.swing.AbstractAction {\n"
  )

  sb.append(
    "  override def actionPerformed(e: ActionEvent): Unit = {\n"
  )

  sb.append(
    "    frameNo += 1\n"
  )

  sb.append(
    "    if (frameNo >= " +
    animationFrames.length +
    ") {\n"
  )

  sb.append(
    "      frameNo = 0\n"
  )

  sb.append(
    "    }\n"
  )

  sb.append(
    "    panel.repaint()\n"
  )
  sb.append(
    "  }\n"
  )
  sb.append(
    "})\n"
  )

  sb.append(
    "w.add(panel)\n"
  )
  sb.append(
    "w.setVisible(true)\n"
  )
  sb.append(
    "timer.start()\n"
  )

  generatedAnimationCode =
    sb.toString

  if (codeArea != null) {
    codeArea.setText(
      generatedAnimationCode
    )
  }

  setStatus(
    "ANIMATION CODE GENERATED: " +
    animationFrames.length +
    " FRAMES"
  )
}


// ============================================================
// OPEN INDEPENDENT ANIMATION WINDOW
// ============================================================

def openFrameAnimationStudio(): Unit = {

  animationFrames.clear()
  animationClipboard.clear()

  animationCurrentFrame = 0

  animationFrames +=
    animationClone(drawItems)

  val win =
    new JFrame(
      "ULTRA ANIMATION DRAW STUDIO - FRAME BY FRAME"
    )

  win.setDefaultCloseOperation(
    WindowConstants.DISPOSE_ON_CLOSE
  )

  win.setSize(
    1500,
    980
  )

  win.setLocationRelativeTo(
    mainFrame
  )

  win.setLayout(
    new BorderLayout()
  )

  var drawing = false
  var moving = false
  var selecting = false

  var temp =
    ArrayBuffer[Point]()

  var pressPoint =
    new Point(0, 0)

  var originalSelection =
    Vector.empty[Point]

  var previewTimer: Timer =
    null

  var playing = false
  var previewIndex = 0

  var ghostMode = true
  var currentTool = "DRAW"
  var onionAlpha = 55

  val frameLabel =
    new JLabel(
      "FRAME 1 / 1",
      SwingConstants.CENTER
    )

  val modeLabel =
    new JLabel(
      "DRAW MODE"
    )

  val hintLabel =
    new JLabel(
      "DRAW: click-drag   SELECT: drag object   COPY/PASTE: duplicate across frames"
    )

  val editor =
    new JPanel {

      override def paintComponent(
        graphics: Graphics
      ): Unit = {

        super.paintComponent(
          graphics
        )

        val g =
          graphics.asInstanceOf[Graphics2D]

        g.setRenderingHint(
          RenderingHints.KEY_ANTIALIASING,
          RenderingHints.VALUE_ANTIALIAS_ON
        )

        g.setColor(
          new Color(
            218,
            222,
            232
          )
        )

        g.fillRect(
          0,
          0,
          getWidth,
          getHeight
        )

        val ox =
          80

        val oy =
          30

        val old =
          g.getTransform

        g.translate(
          ox,
          oy
        )

        val activeSource =
          if (
            playing &&
            animationFrames.nonEmpty
          ) {
            animationFrames(
              math.max(
                0,
                math.min(
                  animationFrames.length - 1,
                  previewIndex
                )
              )
            )
          } else {
            drawItems
          }

        var ghostSource =
          ArrayBuffer[DrawItem]()

        if (
          ghostMode &&
          !playing &&
          animationCurrentFrame > 0
        ) {
          ghostSource =
            animationFrames(
              animationCurrentFrame - 1
            )
        }

        paintFrameScene(
          g,
          activeSource,
          ghostSource,
          ghostMode && !playing,
          if (playing) -1 else selectedIndex
        )

        if (onionAlpha > 0 && ghostMode && !playing) {
          modeLabel.setText(
            "DRAW MODE  |  ONION SKIN " +
            onionAlpha + "%"
          )
        }

        g.setTransform(old)
      }

      addMouseListener(
        new MouseAdapter {

          override def mousePressed(
            e: MouseEvent
          ): Unit = {

            if (playing) {
              return
            }

            val p =
              new Point(
                math.max(
                  0,
                  math.min(
                    CANVAS_W - 1,
                    e.getX - 80
                  )
                ),
                math.max(
                  0,
                  math.min(
                    CANVAS_H - 1,
                    e.getY - 30
                  )
                )
              )

            if (
              p.x < 0 ||
              p.x >= CANVAS_W ||
              p.y < 0 ||
              p.y >= CANVAS_H
            ) {
              return
            }

            if (currentTool == "SELECT") {

              val hit =
                findObject(p)

              if (hit >= 0) {
                selectedIndex = hit
                moving = true
                selecting = true
                pressPoint = p
                originalSelection =
                  drawItems(hit).points
              } else {
                selectedIndex = -1
              }

            } else {

              drawing = true
              temp.clear()
              temp += p
              selectedIndex = -1
            }

            repaint()
          }

          override def mouseReleased(
            e: MouseEvent
          ): Unit = {

            if (playing) {
              return
            }

            if (moving && selecting) {

              moving = false
              selecting = false

              animationSnapshot()

              setStatus(
                "FRAME " +
                (animationCurrentFrame + 1) +
                " OBJECT MOVED"
              )
            }

            if (
              drawing &&
              temp.length >= 2
            ) {

              val pts =
                temp.toVector

              val item =
                DrawItem(
                  pts,
                  strokeColor,
                  fillColor,
                  closed(pts),
                  "FRAME DRAW",
                  true,
                  false
                )

              drawItems += item

              selectedIndex =
                drawItems.length - 1

              animationSnapshot()
            }

            temp.clear()
            drawing = false
            repaint()
          }
        }
      )

      addMouseMotionListener(
        new MouseMotionAdapter {

          override def mouseDragged(
            e: MouseEvent
          ): Unit = {

            if (playing) {
              return
            }

            val p =
              new Point(
                math.max(
                  0,
                  math.min(
                    CANVAS_W - 1,
                    e.getX - 80
                  )
                ),
                math.max(
                  0,
                  math.min(
                    CANVAS_H - 1,
                    e.getY - 30
                  )
                )
              )

            if (moving && selecting) {

              val dx =
                p.x - pressPoint.x

              val dy =
                p.y - pressPoint.y

              if (
                selectedIndex >= 0 &&
                selectedIndex < drawItems.length
              ) {

                val item =
                  drawItems(selectedIndex)

                drawItems(selectedIndex) =
                  item.copy(
                    points =
                      originalSelection.map { q =>
                        new Point(
                          q.x + dx,
                          q.y + dy
                        )
                      }
                  )
              }

            } else if (drawing) {
              if (temp.isEmpty || distance(temp.last, p) >= 2.0) {
                temp += p
              }
            }

            repaint()
          }
        }
      )
    }

  editor.setCursor(
    pencilCursor
  )

  // ----------------------------------------------------------
  // BUTTONS
  // ----------------------------------------------------------

  val drawB =
    button("DRAW")

  val selectB =
    button("SELECT / MOVE")

  val copyB =
    button("COPY DRAW")

  val pasteB =
    button("PASTE DRAW")

  val prevB =
    button("PREVIOUS")

  val nextB =
    button("NEXT FRAME")

  val clearB =
    button("CLEAR FRAME")

  val deleteB =
    button("DELETE SELECTED")

  val playB =
    button("PLAY")

  val stopB =
    button("STOP")

  val codeB =
    button("GENERATE ANIMATION CODE")

  val doneB =
    button("DONE")

  val strokeB =
    button("DRAW COLOR")

  val fillB =
    button("FILL COLOR")

  val fpsMinusB =
    button("FPS -")

  val fpsPlusB =
    button("FPS +")

  val onionB =
    button("ONION SKIN")

  val opacityMinusB =
    button("GHOST -")

  val opacityPlusB =
    button("GHOST +")

  val fpsLabel =
    new JLabel(
      "FPS: " + animationFps
    )

  val timeline =
    new JPanel(
      new FlowLayout(
        FlowLayout.LEFT,
        3,
        3
      )
    )

  val top =
    new JPanel(
      new FlowLayout(
        FlowLayout.LEFT,
        4,
        4
      )
    )

  top.add(frameLabel)
  top.add(drawB)
  top.add(selectB)
  top.add(copyB)
  top.add(pasteB)
  top.add(prevB)
  top.add(nextB)
  top.add(clearB)
  top.add(deleteB)
  top.add(playB)
  top.add(stopB)
  top.add(codeB)
  top.add(doneB)
  top.add(strokeB)
  top.add(fillB)
  top.add(onionB)
  top.add(opacityMinusB)
  top.add(opacityPlusB)
  top.add(fpsMinusB)
  val soundB =
    button("SOUND ON/OFF")

  val chooseSoundB =
    button("CHOOSE WAV")

  val beepB =
    button("TEST SOUND")

  top.add(fpsPlusB)
  top.add(fpsLabel)
  top.add(soundB)
  top.add(chooseSoundB)
  top.add(beepB)

  val commandField =
    new JTextField(
      18
    )

  val commandCombo =
    new javax.swing.JComboBox[String](
      animationCommandWords
    )

  val autoB =
    button("AUTO ANIMATE")

  val addShapeB =
    button("ADD SHAPE")

  val addObjectB =
    button("ADD OBJECT")

  val shapeCombo =
    new javax.swing.JComboBox[String](
      shapes
    )

  val objectCombo =
    new javax.swing.JComboBox[String](
      objects
    )

  val commandPanel =
    new JPanel(
      new FlowLayout(
        FlowLayout.LEFT,
        4,
        4
      )
    )

  commandPanel.add(
    new JLabel(
      "ANIMATION:")
  )

  commandPanel.add(
    commandCombo
  )

  commandPanel.add(
    commandField
  )

  commandPanel.add(
    autoB
  )

  commandPanel.add(
    new JLabel("SHAPE")
  )

  commandPanel.add(
    shapeCombo
  )

  commandPanel.add(
    addShapeB
  )

  commandPanel.add(
    new JLabel("OBJECT")
  )

  commandPanel.add(
    objectCombo
  )

  commandPanel.add(
    addObjectB
  )

  commandPanel.add(
    hintLabel
  )

  addShapeB.addActionListener(
    new ActionListener {
      override def actionPerformed(
        e: ActionEvent
      ): Unit = {
        val selected =
          shapeCombo.getSelectedItem

        if (selected != null) {
          addShape(
            selected.toString
          )
          animationSnapshot()
          refreshAll()
        }
      }
    }
  )

  addObjectB.addActionListener(
    new ActionListener {
      override def actionPerformed(
        e: ActionEvent
      ): Unit = {
        val selected =
          objectCombo.getSelectedItem

        if (selected != null) {
          addObject(
            selected.toString
          )
          animationSnapshot()
          refreshAll()
        }
      }
    }
  )

  // ----------------------------------------------------------
  // TIMELINE
  // ----------------------------------------------------------

  def rebuildTimeline(): Unit = {

    timeline.removeAll()

    var i = 0

    while (i < animationFrames.length) {

      val frameIndex = i

      val b =
        new JButton(
          "F" + (i + 1)
        )

      b.setFocusable(false)

      b.addActionListener(
        new ActionListener {

          override def actionPerformed(
            e: ActionEvent
          ): Unit = {

            animationSnapshot()
            animationLoad(frameIndex)
            refreshAll()
          }
        }
      )

      timeline.add(b)

      i += 1
    }

    timeline.revalidate()
    timeline.repaint()
  }

  def refreshAll(): Unit = {

    frameLabel.setText(
      "FRAME " +
      (animationCurrentFrame + 1) +
      " / " +
      math.max(
        1,
        animationFrames.length
      )
    )

    fpsLabel.setText(
      "FPS: " +
      animationFps
    )

    rebuildTimeline()
    editor.repaint()
  }

  // ----------------------------------------------------------
  // MODE
  // ----------------------------------------------------------

  drawB.addActionListener(
    new ActionListener {
      override def actionPerformed(
        e: ActionEvent
      ): Unit = {
        currentTool = "DRAW"
        modeLabel.setText(
          "DRAW MODE"
        )
      }
    }
  )

  selectB.addActionListener(
    new ActionListener {
      override def actionPerformed(
        e: ActionEvent
      ): Unit = {
        currentTool = "SELECT"
        modeLabel.setText(
          "SELECT / MOVE MODE"
        )
      }
    }
  )

  // ----------------------------------------------------------
  // COPY / PASTE
  // ----------------------------------------------------------

  copyB.addActionListener(
    new ActionListener {
      override def actionPerformed(
        e: ActionEvent
      ): Unit = {
        animationCopy()
        playStudioBeep()
        modeLabel.setText(
          "COPIED " +
          animationClipboard.length +
          " DRAWINGS"
        )
      }
    }
  )

  pasteB.addActionListener(
    new ActionListener {
      override def actionPerformed(
        e: ActionEvent
      ): Unit = {
        animationPaste()
        playStudioBeep()
        animationSnapshot()
        currentTool = "SELECT"
        modeLabel.setText(
          "PASTED - SELECT / MOVE IT"
        )
        editor.repaint()
      }
    }
  )

  // ----------------------------------------------------------
  // NEXT / PREVIOUS
  // ----------------------------------------------------------

  nextB.addActionListener(
    new ActionListener {
      override def actionPerformed(
        e: ActionEvent
      ): Unit = {
        animationNextFrame()
        playStudioBeep()
        refreshAll()
        modeLabel.setText(
          "NEW FRAME " +
          (animationCurrentFrame + 1) +
          " - DRAW OR PASTE"
        )
      }
    }
  )

  prevB.addActionListener(
    new ActionListener {
      override def actionPerformed(
        e: ActionEvent
      ): Unit = {
        animationSnapshot()

        if (animationCurrentFrame > 0) {
          animationCurrentFrame -= 1
          animationLoad(
            animationCurrentFrame
          )
        }

        refreshAll()
      }
    }
  )

  clearB.addActionListener(
    new ActionListener {
      override def actionPerformed(
        e: ActionEvent
      ): Unit = {
        drawItems.clear()
        selectedIndex = -1
        animationSnapshot()
        editor.repaint()
      }
    }
  )

  deleteB.addActionListener(
    new ActionListener {
      override def actionPerformed(
        e: ActionEvent
      ): Unit = {
        deleteSelected()
        animationSnapshot()
        refreshAll()
      }
    }
  )

  // ----------------------------------------------------------
  // COLORS
  // ----------------------------------------------------------

  strokeB.addActionListener(
    new ActionListener {
      override def actionPerformed(
        e: ActionEvent
      ): Unit = {
        val c =
          JColorChooser.showDialog(
            win,
            "DRAW COLOR",
            strokeColor
          )

        if (c != null) {
          strokeColor = c
          modeLabel.setText(
            "DRAW COLOR RGB(" +
            c.getRed +
            "," +
            c.getGreen +
            "," +
            c.getBlue +
            ")"
          )
        }
      }
    }
  )

  fillB.addActionListener(
    new ActionListener {
      override def actionPerformed(
        e: ActionEvent
      ): Unit = {

        val c =
          JColorChooser.showDialog(
            win,
            "FILL COLOR",
            fillColor
          )

        if (c != null) {
          fillColor = c

          if (
            selectedIndex >= 0 &&
            selectedIndex < drawItems.length
          ) {
            fillSelected()
            animationSnapshot()
          }

          editor.repaint()
        }
      }
    }
  )

  // ----------------------------------------------------------
  // ONION SKIN
  // ----------------------------------------------------------

  onionB.addActionListener(
    new ActionListener {
      override def actionPerformed(
        e: ActionEvent
      ): Unit = {
        ghostMode = !ghostMode
        modeLabel.setText(
          if (ghostMode) {
            "ONION SKIN ON"
          } else {
            "ONION SKIN OFF"
          }
        )
        editor.repaint()
      }
    }
  )

  opacityMinusB.addActionListener(
    new ActionListener {
      override def actionPerformed(
        e: ActionEvent
      ): Unit = {
        onionAlpha =
          math.max(
            10,
            onionAlpha - 10
          )
        editor.repaint()
      }
    }
  )

  opacityPlusB.addActionListener(
    new ActionListener {
      override def actionPerformed(
        e: ActionEvent
      ): Unit = {
        onionAlpha =
          math.min(
            120,
            onionAlpha + 10
          )
        editor.repaint()
      }
    }
  )

  // ----------------------------------------------------------
  // FPS
  // ----------------------------------------------------------

  fpsMinusB.addActionListener(
    new ActionListener {
      override def actionPerformed(
        e: ActionEvent
      ): Unit = {
        animationFps =
          math.max(
            2,
            animationFps - 1
          )
        refreshAll()
      }
    }
  )

  fpsPlusB.addActionListener(
    new ActionListener {
      override def actionPerformed(
        e: ActionEvent
      ): Unit = {
        animationFps =
          math.min(
            30,
            animationFps + 1
          )
        refreshAll()
      }
    }
  )

  // ----------------------------------------------------------
  // PLAY
  // ----------------------------------------------------------

  playB.addActionListener(
    new ActionListener {
      override def actionPerformed(
        e: ActionEvent
      ): Unit = {

        animationSnapshot()

        if (animationFrames.length < 2) {
          modeLabel.setText(
            "MAKE 2 OR MORE FRAMES FIRST"
          )
          return
        }

        if (previewTimer != null) {
          previewTimer.stop()
        }

        playing = true
        previewIndex = 0

        previewTimer =
          new Timer(
            math.max(
              20,
              1000 / animationFps
            ),
            new ActionListener {
              override def actionPerformed(
                e: ActionEvent
              ): Unit = {

                previewIndex += 1

                if (
                  previewIndex >=
                  animationFrames.length
                ) {
                  previewIndex = 0
                }

                editor.repaint()
              }
            }
          )

        previewTimer.start()

        modeLabel.setText(
          "PLAYING ALL FRAMES"
        )
      }
    }
  )

  stopB.addActionListener(
    new ActionListener {
      override def actionPerformed(
        e: ActionEvent
      ): Unit = {

        if (previewTimer != null) {
          previewTimer.stop()
        }

        playing = false
        animationLoad(
          animationCurrentFrame
        )

        modeLabel.setText(
          "PLAY STOPPED"
        )

        editor.repaint()
      }
    }
  )

  // ----------------------------------------------------------
  // AUTO ANIMATE
  // ----------------------------------------------------------

  autoB.addActionListener(
    new ActionListener {
      override def actionPerformed(
        e: ActionEvent
      ): Unit = {

        val typed =
          commandField
            .getText
            .trim
            .toUpperCase

        val selected =
          commandCombo.getSelectedItem

        var command =
          "NONE"

        if (typed.nonEmpty) {
          command = typed
        } else if (selected != null) {
          command =
            selected.toString.toUpperCase
        }

        if (command != "NONE") {

          animationSnapshot()

          var base =
            animationClone(drawItems)

          var step = 1

          while (step <= 12) {

            val next =
              animationTransform(
                base,
                command,
                step
              )

            animationFrames +=
              animationClone(next)

            base = next
            step += 1
          }

          animationCurrentFrame =
            animationFrames.length - 1

          drawItems.clear()
          drawItems ++= animationClone(base)
          selectedIndex = -1

          refreshAll()

          modeLabel.setText(
            "AUTO ANIMATION: " +
            command +
            "  |  12 NEW FRAMES"
          )
        }
      }
    }
  )

  // ----------------------------------------------------------
  // SOUND
  // ----------------------------------------------------------

  soundB.addActionListener(
    new ActionListener {
      override def actionPerformed(
        e: ActionEvent
      ): Unit = {
        soundEnabled = !soundEnabled
        if (soundEnabled) {
          modeLabel.setText("SOUND ON")
        } else {
          modeLabel.setText("SOUND OFF")
        }
      }
    }
  )

  chooseSoundB.addActionListener(
    new ActionListener {
      override def actionPerformed(
        e: ActionEvent
      ): Unit = {
        val chooser =
          new JFileChooser()

        chooser.setDialogTitle(
          "CHOOSE WAV SOUND"
        )

        if (
          chooser.showOpenDialog(win) ==
          JFileChooser.APPROVE_OPTION
        ) {
          selectedSoundFile =
            chooser.getSelectedFile.getAbsolutePath

          modeLabel.setText(
            "SOUND: " +
            chooser.getSelectedFile.getName
          )

          playStudioBeep()
        }
      }
    }
  )

  beepB.addActionListener(
    new ActionListener {
      override def actionPerformed(
        e: ActionEvent
      ): Unit = {
        playStudioBeep()
        modeLabel.setText("TEST SOUND PLAYED")
      }
    }
  )

  // ----------------------------------------------------------
  // CODE / DONE
  // ----------------------------------------------------------

  codeB.addActionListener(
    new ActionListener {
      override def actionPerformed(
        e: ActionEvent
      ): Unit = {

        if (previewTimer != null) {
          previewTimer.stop()
        }

        playing = false

        generateFrameAnimationStudioCode()
        playStudioBeep()

        modeLabel.setText(
          "ANIMATION CODE READY - CHECK CODE PANEL"
        )
      }
    }
  )

  doneB.addActionListener(
    new ActionListener {
      override def actionPerformed(
        e: ActionEvent
      ): Unit = {

        if (previewTimer != null) {
          previewTimer.stop()
        }

        playing = false

        generateFrameAnimationStudioCode()

        modeLabel.setText(
          "DONE - FULL FRAME ANIMATION CODE GENERATED"
        )
      }
    }
  )

  // ----------------------------------------------------------
  // BOTTOM PANEL
  // ----------------------------------------------------------

  val bottom =
    new JPanel(
      new BorderLayout()
    )

  bottom.add(
    modeLabel,
    BorderLayout.NORTH
  )

  bottom.add(
    new JScrollPane(
      timeline
    ),
    BorderLayout.CENTER
  )

  bottom.add(
    commandPanel,
    BorderLayout.SOUTH
  )

  win.add(
    top,
    BorderLayout.NORTH
  )

  win.add(
    new JScrollPane(
      editor
    ),
    BorderLayout.CENTER
  )

  win.add(
    bottom,
    BorderLayout.SOUTH
  )

  win.addWindowListener(
    new java.awt.event.WindowAdapter {
      override def windowClosed(
        e: java.awt.event.WindowEvent
      ): Unit = {

        if (previewTimer != null) {
          previewTimer.stop()
        }

        animationStudioOpen =
          false
      }
    }
  )

  animationStudioOpen =
    true

  rebuildTimeline()
  refreshAll()
  win.setVisible(true)
}




// ============================================================
// ULTRA LAPTOP LAUNCHER
// REALISTIC LAPTOP SHELL + BRAND/COLOR + KEYBOARD + MOUSE
// ============================================================

def launchUltraLaptop(): Unit = {

  // ==========================================================
  // VIRTUAL LAPTOP / WINDOWS-STYLE DESKTOP STATE
  // ==========================================================

  var laptopColor = new Color(48, 52, 60)
  var laptopBrand = "ULTRA X9"
  var powered = false
  var desktopMode = "DESKTOP"
  var cursorX = 430
  var cursorY = 170
  var startOpen = false

  val keyMap =
    new scala.collection.mutable.HashMap[Int, JButton]()

  val laptopFrame =
    new JFrame("" + laptopBrand + " - ULTRA WINDOWS LAPTOP")

  laptopFrame.setDefaultCloseOperation(
    WindowConstants.EXIT_ON_CLOSE
  )

  laptopFrame.setSize(1380, 940)
  laptopFrame.setMinimumSize(new Dimension(1120, 800))
  laptopFrame.setLocationRelativeTo(null)
  laptopFrame.setLayout(new BorderLayout(8, 8))

  // ==========================================================
  // TOP CONTROL BAR
  // ==========================================================

  val brandField =
    new JTextField("ULTRA X9", 14)

  val colorButton =
    button("LAPTOP COLOR")

  val powerButton =
    button("POWER ON")

  val resetButton =
    button("RESET")

  val topStatus =
    new JLabel("POWER OFF  |  Configure your laptop first")

  topStatus.setFont(new Font("Arial", Font.BOLD, 13))

  val topBar =
    new JPanel(new FlowLayout(FlowLayout.CENTER, 8, 6))

  topBar.setBackground(new Color(20, 23, 30))
  topBar.add(new JLabel("BRAND"))
  topBar.add(brandField)
  topBar.add(colorButton)
  topBar.add(powerButton)
  topBar.add(resetButton)
  topBar.add(topStatus)

  // ==========================================================
  // VIRTUAL SCREEN
  // ==========================================================

  val screen =
    new JPanel {
      setLayout(null)
      setFocusable(true)
      setBackground(Color.BLACK)

      override def paintComponent(graphics: Graphics): Unit = {
        super.paintComponent(graphics)

        val g = graphics.asInstanceOf[Graphics2D]
        g.setRenderingHint(
          RenderingHints.KEY_ANTIALIASING,
          RenderingHints.VALUE_ANTIALIAS_ON
        )

        val screenX = 24
        val screenY = 22
        val screenW = getWidth - 48
        val screenH = getHeight - 64

        g.setColor(new Color(10, 12, 17))
        g.fillRoundRect(0, 0, getWidth, getHeight, 30, 30)

        if (!powered) {
          g.setColor(new Color(24, 28, 38))
          g.fillRoundRect(screenX, screenY, screenW, screenH, 22, 22)

          g.setColor(laptopColor)
          g.fillOval(getWidth / 2 - 8, 8, 16, 16)

          g.setColor(Color.WHITE)
          g.setFont(new Font("Arial", Font.BOLD, 42))
          val title = laptopBrand
          g.drawString(title, getWidth / 2 - 160, getHeight / 2 - 36)

          g.setFont(new Font("Arial", Font.PLAIN, 18))
          g.setColor(new Color(190, 198, 215))
          g.drawString(
            "Press POWER ON to start your Windows-style laptop",
            getWidth / 2 - 250,
            getHeight / 2 + 8
          )

          g.setColor(laptopColor.brighter())
          g.setFont(new Font("Arial", Font.BOLD, 16))
          g.drawString("BRAND: " + laptopBrand, getWidth / 2 - 95, getHeight / 2 + 50)

        } else {
          val topColor = laptopColor.brighter()
          val bottomColor = new Color(11, 20, 46)

          g.setPaint(
            new GradientPaint(
              0,
              screenY,
              topColor,
              0,
              screenY + screenH,
              bottomColor
            )
          )
          g.fillRoundRect(screenX, screenY, screenW, screenH, 22, 22)

          // Wallpaper bubbles / light
          g.setColor(new Color(255, 255, 255, 28))
          g.fillOval(screenX + screenW - 320, screenY + 50, 250, 250)
          g.setColor(new Color(255, 255, 255, 20))
          g.fillOval(screenX + 70, screenY + 90, 180, 180)

          // Desktop title
          g.setColor(Color.WHITE)
          g.setFont(new Font("Arial", Font.BOLD, 21))
          g.drawString(laptopBrand, screenX + 24, screenY + 34)

          g.setFont(new Font("Arial", Font.PLAIN, 12))
          g.drawString(
            "ULTRA WINDOWS DESKTOP",
            screenX + 24,
            screenY + 52
          )

          // Taskbar
          val taskY = screenY + screenH - 48
          g.setColor(new Color(8, 11, 20, 225))
          g.fillRect(screenX + 5, taskY, screenW - 10, 42)

          g.setColor(Color.WHITE)
          g.setFont(new Font("Arial", Font.BOLD, 13))
          g.drawString("START", screenX + 18, taskY + 26)
          g.drawString("?", screenX + 82, taskY + 26)
          g.drawString("?", screenX + 116, taskY + 26)

          g.setFont(new Font("Arial", Font.PLAIN, 12))
          g.drawString(
            desktopMode,
            screenX + 155,
            taskY + 26
          )

          // Clock
          val now = new java.text.SimpleDateFormat("HH:mm:ss").format(new java.util.Date())
          g.drawString(now, screenX + screenW - 84, taskY + 25)

          // Custom cursor
          val cx = math.max(screenX + 3, math.min(screenX + screenW - 25, cursorX))
          val cy = math.max(screenY + 3, math.min(screenY + screenH - 40, cursorY))

          val arrow = new Path2D.Double()
          arrow.moveTo(cx, cy)
          arrow.lineTo(cx + 2, cy + 27)
          arrow.lineTo(cx + 10, cy + 20)
          arrow.lineTo(cx + 19, cy + 33)
          arrow.lineTo(cx + 25, cy + 29)
          arrow.lineTo(cx + 16, cy + 17)
          arrow.lineTo(cx + 27, cy + 17)
          arrow.closePath()

          g.setColor(Color.BLACK)
          g.fill(arrow)
          g.translate(1, 1)
          g.setColor(Color.WHITE)
          g.fill(arrow)
          g.translate(-1, -1)
        }
      }
    }

  // ==========================================================
  // DESKTOP APP HELPER
  // ==========================================================

  def iconButton(title: String, x: Int, y: Int): JButton = {
    val b = button(title)
    b.setSize(150, 56)
    b.setLocation(x, y)
    b.setFont(new Font("Arial", Font.BOLD, 12))
    b.setFocusable(false)
    b
  }

  val paintApp = iconButton("PAINT STUDIO", 90, 110)
  val animationApp = iconButton("ANIMATION", 260, 110)
  val gameApp = iconButton("GAME STUDIO", 430, 110)
  val codeApp = iconButton("CODE DESK", 600, 110)
  val previewApp = iconButton("2D / 3D", 770, 110)
  val notesApp = iconButton("NOTEPAD", 940, 110)

  val calcApp = iconButton("CALCULATOR", 90, 185)
  val filesApp = iconButton("FILE EXPLORER", 260, 185)
  val browserApp = iconButton("WEB BROWSER", 430, 185)
  val settingsApp = iconButton("SETTINGS", 600, 185)
  val clockApp = iconButton("CLOCK", 770, 185)
  val recycleApp = iconButton("RECYCLE BIN", 940, 185)

  val mediaApp = iconButton("MEDIA CENTER", 90, 260)
  val paint3dApp = iconButton("3D DRAW", 260, 260)
  val terminalApp = iconButton("TERMINAL", 430, 260)
  val calendarApp = iconButton("CALENDAR", 600, 260)
  val helpApp = iconButton("HELP", 770, 260)
  val aboutApp = iconButton("ABOUT", 940, 260)

  val desktopApps = Array(
    paintApp,
    animationApp,
    gameApp,
    codeApp,
    previewApp,
    notesApp,
    calcApp,
    filesApp,
    browserApp,
    settingsApp,
    clockApp,
    recycleApp,
    mediaApp,
    paint3dApp,
    terminalApp,
    calendarApp,
    helpApp,
    aboutApp
  )

  var i0 = 0
  while (i0 < desktopApps.length) {
    desktopApps(i0).setVisible(false)
    screen.add(desktopApps(i0))
    i0 += 1
  }

  // Start panel (overlay-like Swing panel on screen)
  val startPanel = new JPanel()
  startPanel.setLayout(new FlowLayout(FlowLayout.LEFT, 8, 8))
  startPanel.setBounds(20, 330, 470, 190)
  startPanel.setBackground(new Color(16, 20, 31, 245))

  val startTitle = new JLabel("  ULTRA START MENU  ")
  startTitle.setForeground(Color.WHITE)
  startTitle.setFont(new Font("Arial", Font.BOLD, 16))
  startPanel.add(startTitle)

  val startCalc = button("Calculator")
  val startNotes = button("Notepad")
  val startFiles = button("Explorer")
  val startSettings = button("Settings")
  val startPaint = button("Paint")
  val startGame = button("Games")

  startPanel.add(startCalc)
  startPanel.add(startNotes)
  startPanel.add(startFiles)
  startPanel.add(startSettings)
  startPanel.add(startPaint)
  startPanel.add(startGame)
  startPanel.setVisible(false)
  screen.add(startPanel)

  def showDesktopIcons(show: Boolean): Unit = {
    var ix = 0
    while (ix < desktopApps.length) {
      desktopApps(ix).setVisible(show)
      ix += 1
    }
  }

  // ==========================================================
  // GENERIC WINDOW BUILDER
  // ==========================================================

  def appWindow(title: String, width: Int, height: Int): JFrame = {
    val win = new JFrame(laptopBrand + " - " + title)
    win.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE)
    win.setSize(width, height)
    win.setLocationRelativeTo(laptopFrame)
    win.setLayout(new BorderLayout())
    win
  }

  // ==========================================================
  // NOTEPAD - REAL EDITING + OPEN + SAVE + NEW
  // ==========================================================

  def openNotepad(): Unit = {
    val win = appWindow("NOTEPAD", 850, 600)

    val text = new JTextArea()
    text.setFont(new Font("Monospaced", Font.PLAIN, 16))
    text.setLineWrap(true)
    text.setWrapStyleWord(true)

    val top = new JPanel(new FlowLayout(FlowLayout.LEFT, 6, 6))
    val newB = button("NEW")
    val openB = button("OPEN")
    val saveB = button("SAVE AS")
    val info = new JLabel("Ready")

    top.add(newB)
    top.add(openB)
    top.add(saveB)
    top.add(info)

    newB.addActionListener(new ActionListener {
      override def actionPerformed(e: ActionEvent): Unit = {
        text.setText("")
        info.setText("New document")
      }
    })

    openB.addActionListener(new ActionListener {
      override def actionPerformed(e: ActionEvent): Unit = {
        val chooser = new JFileChooser()
        if (chooser.showOpenDialog(win) == JFileChooser.APPROVE_OPTION) {
          val source = scala.io.Source.fromFile(chooser.getSelectedFile, "UTF-8")
          try {
            text.setText(source.mkString)
          } finally {
            source.close()
          }
          info.setText("Opened: " + chooser.getSelectedFile.getName)
        }
      }
    })

    saveB.addActionListener(new ActionListener {
      override def actionPerformed(e: ActionEvent): Unit = {
        val chooser = new JFileChooser()
        if (chooser.showSaveDialog(win) == JFileChooser.APPROVE_OPTION) {
          val out = new java.io.PrintWriter(chooser.getSelectedFile, "UTF-8")
          try {
            out.print(text.getText)
          } finally {
            out.close()
          }
          info.setText("Saved: " + chooser.getSelectedFile.getName)
        }
      }
    })

    win.add(top, BorderLayout.NORTH)
    win.add(new JScrollPane(text), BorderLayout.CENTER)
    win.setVisible(true)
    text.requestFocusInWindow()
  }

  // ==========================================================
  // CALCULATOR - REAL BUTTON INPUT + SAFE BASIC EXPRESSION MATH
  // ==========================================================

  def openCalculator(): Unit = {
    val win = appWindow("CALCULATOR", 400, 530)

    val display = new JTextField("0")
    display.setHorizontalAlignment(SwingConstants.RIGHT)
    display.setFont(new Font("Arial", Font.BOLD, 28))
    display.setEditable(false)

    val grid = new JPanel(new java.awt.GridLayout(5, 4, 6, 6))
    val values = Array(
      "7","8","9","/",
      "4","5","6","*",
      "1","2","3","-",
      "0",".","=","+",
      "C","(",")","%"
    )

    var current = ""

    def setDisplay(s: String): Unit = {
      display.setText(if (s.isEmpty) "0" else s)
    }

    def calculateSimple(expr: String): Double = {
      val clean = expr.replace(" ", "")

      def numbersAndOps: Vector[String] = {
        val out = ArrayBuffer[String]()
        var token = ""
        var j = 0
        while (j < clean.length) {
          val c = clean.charAt(j)
          if ((c >= '0' && c <= '9') || c == '.') {
            token += c
          } else {
            if (token.nonEmpty) out += token
            token = ""
            out += c.toString
          }
          j += 1
        }
        if (token.nonEmpty) out += token
        out.toVector
      }

      val tokens = numbersAndOps
      val nums = ArrayBuffer[Double]()
      val ops = ArrayBuffer[String]()
      var k = 0
      while (k < tokens.length) {
        if (tokens(k) != "+" && tokens(k) != "-" && tokens(k) != "*" && tokens(k) != "/") {
          nums += tokens(k).toDouble
        } else {
          ops += tokens(k)
        }
        k += 1
      }

      if (nums.isEmpty) 0.0
      else {
        var result = nums(0)
        var n = 1
        var o = 0
        while (n < nums.length && o < ops.length) {
          val op = ops(o)
          if (op == "+") result += nums(n)
          else if (op == "-") result -= nums(n)
          else if (op == "*") result *= nums(n)
          else if (op == "/") {
            if (nums(n) == 0.0) throw new ArithmeticException("Cannot divide by zero")
            result /= nums(n)
          }
          n += 1
          o += 1
        }
        result
      }
    }

    var ci = 0
    while (ci < values.length) {
      val label = values(ci)
      val b = button(label)
      b.setFont(new Font("Arial", Font.BOLD, 17))
      b.addActionListener(new ActionListener {
        override def actionPerformed(e: ActionEvent): Unit = {
          if (label == "C") {
            current = ""
            setDisplay(current)
          } else if (label == "=") {
            try {
              val result = calculateSimple(current)
              current = if (result % 1.0 == 0.0) result.toLong.toString else result.toString
              setDisplay(current)
            } catch {
              case _: Throwable =>
                current = ""
                setDisplay("ERROR")
            }
          } else if (label == "%") {
            try {
              val result = calculateSimple(current) / 100.0
              current = result.toString
              setDisplay(current)
            } catch {
              case _: Throwable =>
                setDisplay("ERROR")
            }
          } else {
            if (display.getText == "ERROR") current = ""
            current += label
            setDisplay(current)
          }
        }
      })
      grid.add(b)
      ci += 1
    }

    win.add(display, BorderLayout.NORTH)
    win.add(grid, BorderLayout.CENTER)
    win.setVisible(true)
  }

  // ==========================================================
  // FILE EXPLORER - REAL COMPUTER DIRECTORY VIEW
  // ==========================================================

  def openExplorer(): Unit = {
    val win = appWindow("FILE EXPLORER", 900, 600)

    val pathField = new JTextField(System.getProperty("user.home"))
    val listModel = new DefaultListModel[String]()
    val list = new JList[String](listModel)
    list.setFont(new Font("Monospaced", Font.PLAIN, 14))

    val openB = button("OPEN")
    val upB = button("UP")
    val refreshB = button("REFRESH")
    val chooseB = button("CHOOSE FOLDER")
    val status = new JLabel("Ready")

    def loadPath(path: String): Unit = {
      try {
        val file = new java.io.File(path)
        if (file.isDirectory) {
          pathField.setText(file.getCanonicalPath)
          listModel.clear()
          val children = file.listFiles()
          if (children != null) {
            val sorted = children.sortBy { f => (!f.isDirectory, f.getName.toLowerCase) }
            var li = 0
            while (li < sorted.length) {
              val child = sorted(li)
              val prefix = if (child.isDirectory) "[DIR] " else "[FILE] "
              listModel.addElement(prefix + child.getName)
              li += 1
            }
          }
          status.setText("Loaded " + file.getCanonicalPath)
        }
      } catch {
        case ex: Throwable =>
          status.setText("Explorer error: " + ex.getMessage)
      }
    }

    openB.addActionListener(new ActionListener {
      override def actionPerformed(e: ActionEvent): Unit = loadPath(pathField.getText.trim)
    })

    refreshB.addActionListener(new ActionListener {
      override def actionPerformed(e: ActionEvent): Unit = loadPath(pathField.getText.trim)
    })

    upB.addActionListener(new ActionListener {
      override def actionPerformed(e: ActionEvent): Unit = {
        val f = new java.io.File(pathField.getText.trim)
        if (f.getParent != null) loadPath(f.getParent)
      }
    })

    chooseB.addActionListener(new ActionListener {
      override def actionPerformed(e: ActionEvent): Unit = {
        val chooser = new JFileChooser(pathField.getText.trim)
        chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY)
        if (chooser.showOpenDialog(win) == JFileChooser.APPROVE_OPTION) {
          loadPath(chooser.getSelectedFile.getAbsolutePath)
        }
      }
    })

    val top = new JPanel(new BorderLayout(6, 6))
    val buttons = new JPanel(new FlowLayout(FlowLayout.LEFT, 5, 4))
    buttons.add(openB)
    buttons.add(upB)
    buttons.add(refreshB)
    buttons.add(chooseB)
    top.add(pathField, BorderLayout.CENTER)
    top.add(buttons, BorderLayout.SOUTH)

    win.add(top, BorderLayout.NORTH)
    win.add(new JScrollPane(list), BorderLayout.CENTER)
    win.add(status, BorderLayout.SOUTH)
    win.setVisible(true)
    loadPath(System.getProperty("user.home"))
  }

  // ==========================================================
  // WEB BROWSER - REAL SYSTEM BROWSER OPEN + URL ENTRY
  // ==========================================================

  def openBrowser(): Unit = {
    val win = appWindow("WEB BROWSER", 850, 560)
    val address = new JTextField("https://www.google.com")
    val goB = button("GO")
    val backB = button("HOME")
    val systemB = button("OPEN IN SYSTEM BROWSER")
    val message = new JTextArea()
    message.setEditable(false)
    message.setFont(new Font("Arial", Font.PLAIN, 16))
    message.setLineWrap(true)
    message.setWrapStyleWord(true)
    message.setText(
      "ULTRA WEB BROWSER\n\n" +
      "Type a web address above. GO opens it in the computer's default browser.\n\n" +
      "Example: https://www.google.com\nExample: https://www.wikipedia.org"
    )

    def browse(): Unit = {
      try {
        var u = address.getText.trim
        if (!u.contains("://")) u = "https://" + u
        java.awt.Desktop.getDesktop.browse(new java.net.URI(u))
        message.setText("Opened in the system browser:\n\n" + u)
      } catch {
        case ex: Throwable => message.setText("Could not open URL:\n\n" + ex.getMessage)
      }
    }

    goB.addActionListener(new ActionListener {
      override def actionPerformed(e: ActionEvent): Unit = browse()
    })

    systemB.addActionListener(new ActionListener {
      override def actionPerformed(e: ActionEvent): Unit = browse()
    })

    backB.addActionListener(new ActionListener {
      override def actionPerformed(e: ActionEvent): Unit = {
        address.setText("https://www.google.com")
      }
    })

    val top = new JPanel(new BorderLayout(6, 6))
    val controls = new JPanel(new FlowLayout(FlowLayout.LEFT, 5, 4))
    controls.add(goB)
    controls.add(backB)
    controls.add(systemB)
    top.add(address, BorderLayout.CENTER)
    top.add(controls, BorderLayout.SOUTH)

    win.add(top, BorderLayout.NORTH)
    win.add(new JScrollPane(message), BorderLayout.CENTER)
    win.setVisible(true)
  }

  // ==========================================================
  // SETTINGS - LIVE THEME / BRAND SETTINGS
  // ==========================================================

  def openSettings(): Unit = {
    val win = appWindow("SETTINGS", 600, 430)
    val panel = new JPanel(new java.awt.GridLayout(0, 1, 8, 8))

    val brand = new JTextField(laptopBrand)
    val colorInfo = new JLabel(
      "Current RGB: " + laptopColor.getRed + ", " + laptopColor.getGreen + ", " + laptopColor.getBlue
    )
    val choose = button("CHANGE THEME COLOR")
    val applyBrand = button("APPLY BRAND")
    val resetTheme = button("RESET THEME")
    val keyboardInfo = new JLabel("Keyboard mirror: ON | Mouse cursor: ON")

    applyBrand.addActionListener(new ActionListener {
      override def actionPerformed(e: ActionEvent): Unit = {
        val t = brand.getText.trim
        laptopBrand = if (t.isEmpty) "ULTRA X9" else t
        laptopFrame.setTitle(laptopBrand + " - ULTRA WINDOWS LAPTOP")
        topStatus.setText("BRAND UPDATED: " + laptopBrand)
        screen.repaint()
      }
    })

    choose.addActionListener(new ActionListener {
      override def actionPerformed(e: ActionEvent): Unit = {
        val c = JColorChooser.showDialog(win, "CHOOSE WINDOWS THEME COLOR", laptopColor)
        if (c != null) {
          laptopColor = c
          colorInfo.setText(
            "Current RGB: " + laptopColor.getRed + ", " + laptopColor.getGreen + ", " + laptopColor.getBlue
          )
          screen.repaint()
        }
      }
    })

    resetTheme.addActionListener(new ActionListener {
      override def actionPerformed(e: ActionEvent): Unit = {
        laptopColor = new Color(48, 52, 60)
        brand.setText("ULTRA X9")
        laptopBrand = "ULTRA X9"
        colorInfo.setText("Current RGB: 48, 52, 60")
        screen.repaint()
      }
    })

    panel.add(new JLabel("WINDOWS STYLE SETTINGS"))
    panel.add(new JLabel("Laptop Brand"))
    panel.add(brand)
    panel.add(applyBrand)
    panel.add(choose)
    panel.add(colorInfo)
    panel.add(resetTheme)
    panel.add(keyboardInfo)

    win.add(panel, BorderLayout.CENTER)
    win.setVisible(true)
  }

  // ==========================================================
  // CLOCK
  // ==========================================================

  def openClock(): Unit = {
    val win = appWindow("CLOCK", 420, 260)
    val label = new JLabel("00:00:00", SwingConstants.CENTER)
    label.setFont(new Font("Arial", Font.BOLD, 44))

    val dateLabel = new JLabel("", SwingConstants.CENTER)
    dateLabel.setFont(new Font("Arial", Font.PLAIN, 17))

    val timer = new Timer(500, new ActionListener {
      override def actionPerformed(e: ActionEvent): Unit = {
        label.setText(new java.text.SimpleDateFormat("HH:mm:ss").format(new java.util.Date()))
        dateLabel.setText(new java.text.SimpleDateFormat("EEEE, dd MMMM yyyy").format(new java.util.Date()))
      }
    })
    timer.start()

    win.add(label, BorderLayout.CENTER)
    win.add(dateLabel, BorderLayout.SOUTH)
    win.addWindowListener(new java.awt.event.WindowAdapter {
      override def windowClosed(e: java.awt.event.WindowEvent): Unit = timer.stop()
    })
    win.setVisible(true)
  }

  // ==========================================================
  // RECYCLE BIN
  // ==========================================================

  def openRecycle(): Unit = {
    val win = appWindow("RECYCLE BIN", 620, 430)
    val text = new JTextArea()
    text.setEditable(false)
    text.setFont(new Font("Monospaced", Font.PLAIN, 15))
    text.setText(
      "RECYCLE BIN\n\n" +
      "This virtual laptop does not move your real files into a fake bin.\n" +
      "Use File Explorer to work with your real folders.\n\n" +
      "STATUS: EMPTY"
    )
    win.add(new JScrollPane(text), BorderLayout.CENTER)
    win.setVisible(true)
  }

  // ==========================================================
  // MEDIA CENTER
  // ==========================================================

  def openMedia(): Unit = {
    val win = appWindow("MEDIA CENTER", 720, 480)
    val chooser = new JFileChooser()
    val choose = button("CHOOSE MEDIA FILE")
    val open = button("OPEN FILE")
    val field = new JTextField()
    val info = new JTextArea()
    info.setEditable(false)
    info.setText("Choose an audio/video/image file. OPEN FILE uses the computer's default application.")

    choose.addActionListener(new ActionListener {
      override def actionPerformed(e: ActionEvent): Unit = {
        if (chooser.showOpenDialog(win) == JFileChooser.APPROVE_OPTION) {
          field.setText(chooser.getSelectedFile.getAbsolutePath)
        }
      }
    })

    open.addActionListener(new ActionListener {
      override def actionPerformed(e: ActionEvent): Unit = {
        try {
          val f = new java.io.File(field.getText.trim)
          if (f.exists()) java.awt.Desktop.getDesktop.open(f)
        } catch {
          case ex: Throwable => info.setText("Cannot open file: " + ex.getMessage)
        }
      }
    })

    val controls = new JPanel(new BorderLayout(5, 5))
    controls.add(field, BorderLayout.CENTER)
    val buttons = new JPanel(new FlowLayout(FlowLayout.LEFT, 5, 5))
    buttons.add(choose)
    buttons.add(open)
    controls.add(buttons, BorderLayout.SOUTH)

    win.add(controls, BorderLayout.NORTH)
    win.add(new JScrollPane(info), BorderLayout.CENTER)
    win.setVisible(true)
  }

  // ==========================================================
  // TERMINAL - SAFE DEMO TERMINAL / COMMAND VIEWER
  // ==========================================================

  def openTerminal(): Unit = {
    val win = appWindow("TERMINAL", 760, 520)
    val area = new JTextArea()
    area.setFont(new Font("Monospaced", Font.PLAIN, 15))
    area.setBackground(Color.BLACK)
    area.setForeground(new Color(110, 240, 130))
    area.setText(
      "ULTRA TERMINAL\n" +
      "Type simple commands: help, dir, time, clear, about\n\n> "
    )

    val input = new JTextField()
    val run = button("RUN")

    def command(): Unit = {
      val cmd = input.getText.trim.toLowerCase
      if (cmd == "help") {
        area.append("\nhelp  dir  time  clear  about")
      } else if (cmd == "dir") {
        val f = new java.io.File(System.getProperty("user.home"))
        val files = f.listFiles()
        area.append("\nHOME:\n")
        if (files != null) {
          var ti = 0
          while (ti < files.length) {
            area.append(files(ti).getName + "\n")
            ti += 1
          }
        }
      } else if (cmd == "time") {
        area.append("\n" + new java.util.Date().toString)
      } else if (cmd == "about") {
        area.append("\nULTRA WINDOWS LAPTOP")
      } else if (cmd == "clear") {
        area.setText("ULTRA TERMINAL\n> ")
      } else {
        area.append("\nUnknown command")
      }
      area.append("\n> ")
      input.setText("")
    }

    run.addActionListener(new ActionListener {
      override def actionPerformed(e: ActionEvent): Unit = command()
    })

    input.addActionListener(new ActionListener {
      override def actionPerformed(e: ActionEvent): Unit = command()
    })

    val bottom = new JPanel(new BorderLayout(5, 5))
    bottom.add(input, BorderLayout.CENTER)
    bottom.add(run, BorderLayout.EAST)

    win.add(new JScrollPane(area), BorderLayout.CENTER)
    win.add(bottom, BorderLayout.SOUTH)
    win.setVisible(true)
    input.requestFocusInWindow()
  }

  // ==========================================================
  // CALENDAR
  // ==========================================================

  def openCalendar(): Unit = {
    val win = appWindow("CALENDAR", 520, 420)
    val area = new JTextArea()
    area.setEditable(false)
    area.setFont(new Font("Monospaced", Font.PLAIN, 17))
    area.setText(new java.text.SimpleDateFormat("MMMM yyyy\n\nEEE   Sun Mon Tue Wed Thu Fri Sat\n").format(new java.util.Date()))

    val today = java.util.Calendar.getInstance()
    val year = today.get(java.util.Calendar.YEAR)
    val month = today.get(java.util.Calendar.MONTH)

    val cal = java.util.Calendar.getInstance()
    cal.set(year, month, 1)
    val first = cal.get(java.util.Calendar.DAY_OF_WEEK) - 1
    val max = cal.getActualMaximum(java.util.Calendar.DAY_OF_MONTH)

    var ci = 0
    while (ci < first) {
      area.append("    ")
      ci += 1
    }

    var day = 1
    while (day <= max) {
      area.append(String.format("%3d ", Integer.valueOf(day)))
      if ((day + first) % 7 == 0) area.append("\n")
      day += 1
    }

    win.add(new JScrollPane(area), BorderLayout.CENTER)
    win.setVisible(true)
  }

  // ==========================================================
  // HELP / ABOUT / PREVIEW / 3D
  // ==========================================================

  def openInfoWindow(title: String, text: String): Unit = {
    val win = appWindow(title, 700, 480)
    val area = new JTextArea(text)
    area.setEditable(false)
    area.setFont(new Font("Arial", Font.PLAIN, 15))
    area.setLineWrap(true)
    area.setWrapStyleWord(true)
    win.add(new JScrollPane(area), BorderLayout.CENTER)
    win.setVisible(true)
  }

  // ==========================================================
  // APP ACTIONS
  // ==========================================================

  paintApp.addActionListener(new ActionListener {
    override def actionPerformed(e: ActionEvent): Unit = {
      if (mainFrame != null) {
        mainFrame.setLocationRelativeTo(laptopFrame)
        mainFrame.setVisible(true)
        canvas.requestFocusInWindow()
      }
      desktopMode = "PAINT"
      topStatus.setText("PAINT STUDIO OPEN")
      screen.repaint()
    }
  })

  animationApp.addActionListener(new ActionListener {
    override def actionPerformed(e: ActionEvent): Unit = {
      openFrameAnimationStudio()
      desktopMode = "ANIMATION"
      topStatus.setText("ANIMATION STUDIO OPEN")
      screen.repaint()
    }
  })

  gameApp.addActionListener(new ActionListener {
    override def actionPerformed(e: ActionEvent): Unit = {
      openGameStudio()
      desktopMode = "GAME STUDIO"
      topStatus.setText("GAME STUDIO OPEN")
      screen.repaint()
    }
  })

  codeApp.addActionListener(new ActionListener {
    override def actionPerformed(e: ActionEvent): Unit = {
      if (mainFrame != null) {
        mainFrame.setLocationRelativeTo(laptopFrame)
        mainFrame.setVisible(true)
        if (codeArea != null) codeArea.requestFocusInWindow()
      }
      desktopMode = "CODE DESK"
      topStatus.setText("CODE DESK OPEN")
      screen.repaint()
    }
  })

  previewApp.addActionListener(new ActionListener {
    override def actionPerformed(e: ActionEvent): Unit = {
      openPreview3D()
      desktopMode = "2D / 3D"
      screen.repaint()
    }
  })

  notesApp.addActionListener(new ActionListener {
    override def actionPerformed(e: ActionEvent): Unit = openNotepad()
  })

  calcApp.addActionListener(new ActionListener {
    override def actionPerformed(e: ActionEvent): Unit = openCalculator()
  })

  filesApp.addActionListener(new ActionListener {
    override def actionPerformed(e: ActionEvent): Unit = openExplorer()
  })

  browserApp.addActionListener(new ActionListener {
    override def actionPerformed(e: ActionEvent): Unit = openBrowser()
  })

  settingsApp.addActionListener(new ActionListener {
    override def actionPerformed(e: ActionEvent): Unit = openSettings()
  })

  clockApp.addActionListener(new ActionListener {
    override def actionPerformed(e: ActionEvent): Unit = openClock()
  })

  recycleApp.addActionListener(new ActionListener {
    override def actionPerformed(e: ActionEvent): Unit = openRecycle()
  })

  mediaApp.addActionListener(new ActionListener {
    override def actionPerformed(e: ActionEvent): Unit = openMedia()
  })

  paint3dApp.addActionListener(new ActionListener {
    override def actionPerformed(e: ActionEvent): Unit = openInfoWindow(
      "3D DRAW",
      "ULTRA 3D DRAW\n\n" +
      "This module opens your existing Preview/shape studio so you can work with 2D/3D-style objects."
    )
  })

  terminalApp.addActionListener(new ActionListener {
    override def actionPerformed(e: ActionEvent): Unit = openTerminal()
  })

  calendarApp.addActionListener(new ActionListener {
    override def actionPerformed(e: ActionEvent): Unit = openCalendar()
  })

  helpApp.addActionListener(new ActionListener {
    override def actionPerformed(e: ActionEvent): Unit = openInfoWindow(
      "HELP",
      "ULTRA WINDOWS LAPTOP HELP\n\n" +
      "1. Set the brand.\n" +
      "2. Select a laptop/theme color.\n" +
      "3. Press POWER ON.\n" +
      "4. Click any desktop app.\n" +
      "5. Your real keyboard keys are mirrored on the virtual keyboard.\n" +
      "6. Move the real mouse over the screen to move the on-screen cursor.\n\n" +
      "Paint, Animation and Game Studio connect to the existing studios in this KOJO project."
    )
  })

  aboutApp.addActionListener(new ActionListener {
    override def actionPerformed(e: ActionEvent): Unit = openInfoWindow(
      "ABOUT",
      "ULTRA WINDOWS LAPTOP\n\n" +
      "A Windows-style laptop simulation built in Scala/Swing for KOJO.\n" +
      "It provides a desktop shell, taskbar, Start menu, virtual keyboard, mouse mirror and functional utility apps."
    )
  })

  // Start menu shortcuts
  startCalc.addActionListener(new ActionListener {
    override def actionPerformed(e: ActionEvent): Unit = {
      startPanel.setVisible(false)
      openCalculator()
    }
  })

  startNotes.addActionListener(new ActionListener {
    override def actionPerformed(e: ActionEvent): Unit = {
      startPanel.setVisible(false)
      openNotepad()
    }
  })

  startFiles.addActionListener(new ActionListener {
    override def actionPerformed(e: ActionEvent): Unit = {
      startPanel.setVisible(false)
      openExplorer()
    }
  })

  startSettings.addActionListener(new ActionListener {
    override def actionPerformed(e: ActionEvent): Unit = {
      startPanel.setVisible(false)
      openSettings()
    }
  })

  startPaint.addActionListener(new ActionListener {
    override def actionPerformed(e: ActionEvent): Unit = {
      startPanel.setVisible(false)
      if (mainFrame != null) mainFrame.setVisible(true)
    }
  })

  startGame.addActionListener(new ActionListener {
    override def actionPerformed(e: ActionEvent): Unit = {
      startPanel.setVisible(false)
      openGameStudio()
    }
  })

  // ==========================================================
  // LAPTOP BASE REFERENCE
  // Declared before listeners to avoid Scala forward-reference errors.
  // It is fully built near the end of the setup section.
  // ==========================================================
  var base: JPanel = null

  // ==========================================================
  // COLOR BUTTON
  // ==========================================================

  colorButton.addActionListener(new ActionListener {
    override def actionPerformed(e: ActionEvent): Unit = {
      val selected = JColorChooser.showDialog(
        laptopFrame,
        "CHOOSE LAPTOP / WINDOWS COLOR",
        laptopColor
      )
      if (selected != null) {
        laptopColor = selected
        screen.repaint()
        topBar.setBackground(laptopColor.darker())
        base.setBackground(laptopColor.darker())
        topStatus.setText(
          "THEME: RGB " +
          laptopColor.getRed + "," +
          laptopColor.getGreen + "," +
          laptopColor.getBlue
        )
      }
    }
  })

  brandField.addKeyListener(new KeyAdapter {
    override def keyReleased(e: KeyEvent): Unit = {
      val t = brandField.getText.trim
      laptopBrand = if (t.isEmpty) "ULTRA X9" else t
      laptopFrame.setTitle(laptopBrand + " - ULTRA WINDOWS LAPTOP")
      screen.repaint()
    }
  })

  // ==========================================================
  // POWER BUTTON
  // ==========================================================

  powerButton.addActionListener(new ActionListener {
    override def actionPerformed(e: ActionEvent): Unit = {
      powered = !powered

      if (powered) {
        val b = brandField.getText.trim
        laptopBrand = if (b.isEmpty) "ULTRA X9" else b
        laptopFrame.setTitle(laptopBrand + " - ULTRA WINDOWS LAPTOP")
        powerButton.setText("POWER OFF")
        brandField.setEnabled(false)
        resetButton.setEnabled(true)
        showDesktopIcons(true)
        topStatus.setText("POWER ON | DESKTOP READY | " + laptopBrand)
      } else {
        powerButton.setText("POWER ON")
        brandField.setEnabled(true)
        showDesktopIcons(false)
        startPanel.setVisible(false)
        topStatus.setText("POWER OFF | Press POWER ON")
      }

      screen.repaint()
      screen.requestFocusInWindow()
    }
  })

  resetButton.addActionListener(new ActionListener {
    override def actionPerformed(e: ActionEvent): Unit = {
      powered = false
      laptopBrand = "ULTRA X9"
      laptopColor = new Color(48, 52, 60)
      brandField.setText("ULTRA X9")
      brandField.setEnabled(true)
      powerButton.setText("POWER ON")
      showDesktopIcons(false)
      startPanel.setVisible(false)
      topStatus.setText("RESET COMPLETE | POWER OFF")
      screen.repaint()
    }
  })

  // ==========================================================
  // START CLICK AREA + MOUSE CURSOR MIRROR
  // ==========================================================

  screen.addMouseListener(new MouseAdapter {
    override def mousePressed(e: MouseEvent): Unit = {
      cursorX = e.getX
      cursorY = e.getY

      if (powered) {
        val sy = screen.getHeight - 64
        if (e.getY >= sy) {
          if (e.getX >= 20 && e.getX <= 115) {
            startOpen = !startOpen
            startPanel.setVisible(startOpen)
          } else if (e.getX >= 115 && e.getX <= 205) {
            openCalculator()
          }
        }
      }
      screen.repaint()
    }
  })

  screen.addMouseMotionListener(new MouseMotionAdapter {
    override def mouseMoved(e: MouseEvent): Unit = {
      cursorX = e.getX
      cursorY = e.getY
      screen.repaint()
    }

    override def mouseDragged(e: MouseEvent): Unit = {
      cursorX = e.getX
      cursorY = e.getY
      screen.repaint()
    }
  })

  // ==========================================================
  // VIRTUAL KEYBOARD
  // ==========================================================

  def baseKeyColor(): Color = {
    new Color(
      math.min(255, laptopColor.getRed + 24),
      math.min(255, laptopColor.getGreen + 24),
      math.min(255, laptopColor.getBlue + 24)
    )
  }

  def addKey(panel: JPanel, label: String, code: Int, width: Int): JButton = {
    val b = button(label)
    b.setPreferredSize(new Dimension(width, 34))
    b.setBackground(baseKeyColor())
    b.setForeground(Color.WHITE)
    b.setFocusable(false)

    b.addActionListener(new ActionListener {
      override def actionPerformed(e: ActionEvent): Unit = {
        b.setBackground(new Color(255, 190, 55))
        topStatus.setText("KEYBOARD: " + label)
        val t = new Timer(120, new ActionListener {
          override def actionPerformed(e2: ActionEvent): Unit = b.setBackground(baseKeyColor())
        })
        t.setRepeats(false)
        t.start()
        screen.requestFocusInWindow()
      }
    })

    panel.add(b)
    keyMap(code) = b
    b
  }

  def row(labels: Array[String], codes: Array[Int]): JPanel = {
    val p = new JPanel(new FlowLayout(FlowLayout.CENTER, 3, 2))
    p.setOpaque(false)
    var ri = 0
    while (ri < labels.length) {
      val width =
        if (labels(ri) == "SPACE") 260
        else if (labels(ri).length >= 6) 78
        else 45
      addKey(p, labels(ri), codes(ri), width)
      ri += 1
    }
    p
  }

  val keyboardPanel = new JPanel(new java.awt.GridLayout(6, 1, 4, 3))
  keyboardPanel.setBackground(new Color(25, 28, 34))

  keyboardPanel.add(row(
    Array("ESC","F1","F2","F3","F4","F5","F6","F7","F8","F9","F10","F11","F12"),
    Array(KeyEvent.VK_ESCAPE,KeyEvent.VK_F1,KeyEvent.VK_F2,KeyEvent.VK_F3,KeyEvent.VK_F4,KeyEvent.VK_F5,KeyEvent.VK_F6,KeyEvent.VK_F7,KeyEvent.VK_F8,KeyEvent.VK_F9,KeyEvent.VK_F10,KeyEvent.VK_F11,KeyEvent.VK_F12)
  ))

  keyboardPanel.add(row(
    Array("1","2","3","4","5","6","7","8","9","0","-","=","BACKSPACE"),
    Array(KeyEvent.VK_1,KeyEvent.VK_2,KeyEvent.VK_3,KeyEvent.VK_4,KeyEvent.VK_5,KeyEvent.VK_6,KeyEvent.VK_7,KeyEvent.VK_8,KeyEvent.VK_9,KeyEvent.VK_0,KeyEvent.VK_MINUS,KeyEvent.VK_EQUALS,KeyEvent.VK_BACK_SPACE)
  ))

  keyboardPanel.add(row(
    Array("TAB","Q","W","E","R","T","Y","U","I","O","P","[","]","\\"),
    Array(KeyEvent.VK_TAB,KeyEvent.VK_Q,KeyEvent.VK_W,KeyEvent.VK_E,KeyEvent.VK_R,KeyEvent.VK_T,KeyEvent.VK_Y,KeyEvent.VK_U,KeyEvent.VK_I,KeyEvent.VK_O,KeyEvent.VK_P,KeyEvent.VK_OPEN_BRACKET,KeyEvent.VK_CLOSE_BRACKET,KeyEvent.VK_BACK_SLASH)
  ))

  keyboardPanel.add(row(
    Array("CAPS","A","S","D","F","G","H","J","K","L",";","'","ENTER"),
    Array(KeyEvent.VK_CAPS_LOCK,KeyEvent.VK_A,KeyEvent.VK_S,KeyEvent.VK_D,KeyEvent.VK_F,KeyEvent.VK_G,KeyEvent.VK_H,KeyEvent.VK_J,KeyEvent.VK_K,KeyEvent.VK_L,KeyEvent.VK_SEMICOLON,KeyEvent.VK_QUOTE,KeyEvent.VK_ENTER)
  ))

  keyboardPanel.add(row(
    Array("SHIFT","Z","X","C","V","B","N","M",",",".","/","SHIFT"),
    Array(KeyEvent.VK_SHIFT,KeyEvent.VK_Z,KeyEvent.VK_X,KeyEvent.VK_C,KeyEvent.VK_V,KeyEvent.VK_B,KeyEvent.VK_N,KeyEvent.VK_M,KeyEvent.VK_COMMA,KeyEvent.VK_PERIOD,KeyEvent.VK_SLASH,KeyEvent.VK_SHIFT)
  ))

  keyboardPanel.add(row(
    Array("CTRL","ALT","SPACE","ALT","CTRL","INSERT","DELETE","HOME","END","LEFT","UP","DOWN","RIGHT"),
    Array(KeyEvent.VK_CONTROL,KeyEvent.VK_ALT,KeyEvent.VK_SPACE,KeyEvent.VK_ALT,KeyEvent.VK_CONTROL,KeyEvent.VK_INSERT,KeyEvent.VK_DELETE,KeyEvent.VK_HOME,KeyEvent.VK_END,KeyEvent.VK_LEFT,KeyEvent.VK_UP,KeyEvent.VK_DOWN,KeyEvent.VK_RIGHT)
  ))

  val keyboardDispatcher = new java.awt.KeyEventDispatcher {
    override def dispatchKeyEvent(e: KeyEvent): Boolean = {
      val target = keyMap.get(e.getKeyCode)
      if (e.getID == KeyEvent.KEY_PRESSED) {
        target.foreach { b => b.setBackground(new Color(255, 190, 55)) }
        if (powered) {
          topStatus.setText("REAL KEYBOARD: " + KeyEvent.getKeyText(e.getKeyCode))
        }
      } else if (e.getID == KeyEvent.KEY_RELEASED) {
        target.foreach { b => b.setBackground(baseKeyColor()) }
      }
      false
    }
  }

  java.awt.KeyboardFocusManager
    .getCurrentKeyboardFocusManager
    .addKeyEventDispatcher(keyboardDispatcher)

  // ==========================================================
  // LAPTOP BASE
  // ==========================================================

  val keyboardLabel = new JLabel(
    "VIRTUAL KEYBOARD  |  REAL KEYBOARD MIRROR: ON  |  MOUSE CURSOR: ON",
    SwingConstants.CENTER
  )
  keyboardLabel.setFont(new Font("Arial", Font.BOLD, 12))
  keyboardLabel.setForeground(Color.WHITE)

  base = new JPanel(new BorderLayout())
  base.setBackground(new Color(31, 34, 41))
  base.add(keyboardLabel, BorderLayout.NORTH)
  base.add(keyboardPanel, BorderLayout.CENTER)

  laptopFrame.add(topBar, BorderLayout.NORTH)
  laptopFrame.add(screen, BorderLayout.CENTER)
  laptopFrame.add(base, BorderLayout.SOUTH)

  laptopFrame.addWindowListener(new java.awt.event.WindowAdapter {
    override def windowClosed(e: java.awt.event.WindowEvent): Unit = {
      java.awt.KeyboardFocusManager
        .getCurrentKeyboardFocusManager
        .removeKeyEventDispatcher(keyboardDispatcher)
    }
  })

  laptopFrame.setVisible(true)
  screen.requestFocusInWindow()
}

// ============================================================
// FINAL START
// ============================================================

SwingUtilities.invokeLater(
  new Runnable {
    override def run(): Unit = {
      launchUltraLaptop()
    }
  }
)