Code Sketch
yoiiiii11
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.GridLayout
import java.awt.Point
import java.awt.Polygon
import java.awt.RenderingHints
import java.awt.event.ActionEvent
import java.awt.event.ActionListener
import java.awt.event.KeyAdapter
import java.awt.event.KeyEvent
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import java.awt.event.MouseMotionAdapter
import java.awt.geom.AffineTransform
import java.awt.image.BufferedImage
import javax.swing.BorderFactory
import javax.swing.DefaultListModel
import javax.swing.JButton
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.JSlider
import javax.swing.JTextArea
import javax.swing.JTextField
import javax.swing.SwingUtilities
import javax.swing.Timer
import javax.swing.WindowConstants
import scala.collection.mutable.ArrayBuffer
// ============================================================
// ULTRA LEGEND DRAW -> EXACT KOJO -> 3D ANIMATION STUDIO X
// ============================================================
//
// 1. DRAW ANYTHING
// 2. EVERY STROKE IS RECORDED
// 3. EXACT POINTS ARE KEPT
// 4. GENERATE KOJO CODE
// 5. OBJECT LIBRARY
// 6. SHAPE LIBRARY
// 7. 3D EXTRUDED PREVIEW
// 8. 3D ROTATION ANIMATION
// 9. DRAWING ANIMATION
// 10. UNDO / REDO
// 11. GRID / SNAP
// 12. COPY CODE
// 13. SAVE CODE
// 14. MANY FUTURE OBJECTS
//
// ============================================================
// ============================================================
// CANVAS SIZE
// ============================================================
val CW = 1000
val CH = 650
val CX = CW / 2
val CY = CH / 2
// ============================================================
// DATA
// ============================================================
case class DrawStroke(
points: Vector[Point]
)
case class LibraryShape(
name: String,
kind: String
)
// ============================================================
// STATE
// ============================================================
var strokes =
ArrayBuffer[DrawStroke]()
var undoHistory =
ArrayBuffer[
ArrayBuffer[DrawStroke]
]()
var redoHistory =
ArrayBuffer[
ArrayBuffer[DrawStroke]
]()
var activeStroke =
ArrayBuffer[Point]()
var drawing =
false
var eraser =
false
var gridOn =
true
var snapOn =
false
var brushSize =
4
var currentColor =
new Color(
20,
50,
120
)
var generatedCode =
""
var animationStroke =
0
var animationPoint =
1
var animationPlaying =
false
var animationTimer: Timer =
null
var animationData =
ArrayBuffer[DrawStroke]()
// ============================================================
// UI
// ============================================================
var frame: JFrame =
null
var canvas: JPanel =
null
var codeArea: JTextArea =
null
var status: JLabel =
null
var searchField: JTextField =
null
var objectModel =
new DefaultListModel[String]()
var objectList: JList[String] =
null
var speedSlider: JSlider =
null
// ============================================================
// BUTTON
// ============================================================
def makeButton(
text: String
): JButton = {
val b =
new JButton(text)
b.setFocusable(false)
b.setFont(
new Font(
"Arial",
Font.BOLD,
11
)
)
b
}
// ============================================================
// STATUS
// ============================================================
def statusText(
text: String
): Unit = {
if (
status != null
) {
status.setText(
text
)
}
}
// ============================================================
// REPAINT
// ============================================================
def repaintCanvas(): Unit = {
if (
canvas != null
) {
canvas.repaint()
}
}
// ============================================================
// COPY STROKES
// ============================================================
def copyStrokes(
source: ArrayBuffer[DrawStroke]
): ArrayBuffer[DrawStroke] = {
val result =
ArrayBuffer[DrawStroke]()
var i = 0
while (
i < source.length
) {
val pts =
source(i).points.map(
p =>
new Point(
p.x,
p.y
)
)
result +=
DrawStroke(
pts
)
i += 1
}
result
}
// ============================================================
// HISTORY
// ============================================================
def remember(): Unit = {
undoHistory +=
copyStrokes(
strokes
)
redoHistory.clear()
while (
undoHistory.length > 35
) {
undoHistory.remove(
0
)
}
}
def doUndo(): Unit = {
if (
undoHistory.nonEmpty
) {
redoHistory +=
copyStrokes(
strokes
)
val previous =
undoHistory(
undoHistory.length - 1
)
undoHistory.remove(
undoHistory.length - 1
)
strokes.clear()
strokes ++=
copyStrokes(
previous
)
repaintCanvas()
statusText(
"UNDO"
)
} else {
statusText(
"NOTHING TO UNDO"
)
}
}
def doRedo(): Unit = {
if (
redoHistory.nonEmpty
) {
undoHistory +=
copyStrokes(
strokes
)
val next =
redoHistory(
redoHistory.length - 1
)
redoHistory.remove(
redoHistory.length - 1
)
strokes.clear()
strokes ++=
copyStrokes(
next
)
repaintCanvas()
statusText(
"REDO"
)
} else {
statusText(
"NOTHING TO REDO"
)
}
}
// ============================================================
// SNAP
// ============================================================
def snap(
value: Int
): Int = {
if (
snapOn
) {
(value / 10) * 10
} else {
value
}
}
// ============================================================
// MOUSE POINT
// ============================================================
def mousePoint(
e: MouseEvent
): Point = {
val x =
math.max(
0,
math.min(
CW - 1,
e.getX
)
)
val y =
math.max(
0,
math.min(
CH - 1,
e.getY
)
)
new Point(
snap(x),
snap(y)
)
}
// ============================================================
// GRID
// ============================================================
def drawGrid(
g2: Graphics2D
): Unit = {
if (
!gridOn
) {
return
}
g2.setColor(
new Color(
0,
0,
0,
23
)
)
var x = 0
while (
x <= CW
) {
g2.drawLine(
x,
0,
x,
CH
)
x += 50
}
var y = 0
while (
y <= CH
) {
g2.drawLine(
0,
y,
CW,
y
)
y += 50
}
g2.setColor(
new Color(
0,
0,
0,
55
)
)
g2.drawLine(
CX,
0,
CX,
CH
)
g2.drawLine(
0,
CY,
CW,
CY
)
}
// ============================================================
// DRAW STROKE
// ============================================================
def paintStroke(
g2: Graphics2D,
points: Seq[Point]
): Unit = {
if (
points.length == 1
) {
val p =
points.head
g2.fillOval(
p.x - brushSize / 2,
p.y - brushSize / 2,
brushSize,
brushSize
)
} else {
var i = 1
while (
i < points.length
) {
val a =
points(i - 1)
val b =
points(i)
g2.drawLine(
a.x,
a.y,
b.x,
b.y
)
i += 1
}
}
}
// ============================================================
// CANVAS
// ============================================================
canvas =
new JPanel {
override def paintComponent(
graphics: Graphics
): Unit = {
super.paintComponent(
graphics
)
val g2 =
graphics
.asInstanceOf[Graphics2D]
g2.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON
)
g2.setColor(
new Color(
250,
252,
255
)
)
g2.fillRect(
0,
0,
getWidth,
getHeight
)
drawGrid(
g2
)
g2.setStroke(
new BasicStroke(
brushSize.toFloat,
BasicStroke.CAP_ROUND,
BasicStroke.JOIN_ROUND
)
)
g2.setColor(
currentColor
)
var i = 0
while (
i < strokes.length
) {
paintStroke(
g2,
strokes(i).points
)
i += 1
}
if (
activeStroke.nonEmpty
) {
if (
eraser
) {
g2.setColor(
Color.WHITE
)
} else {
g2.setColor(
currentColor
)
}
paintStroke(
g2,
activeStroke.toVector
)
}
}
}
// ============================================================
// GEOMETRY
// ============================================================
def distance(
a: Point,
b: Point
): Double = {
math.hypot(
b.x - a.x,
b.y - a.y
)
}
def heading(
a: Point,
b: Point
): Double = {
math.toDegrees(
math.atan2(
b.y - a.y,
b.x - a.x
)
)
}
def normalize(
angle: Double
): Double = {
var a =
angle
while (
a > 180
) {
a -= 360
}
while (
a < -180
) {
a += 360
}
a
}
// ============================================================
// RDP SIMPLIFIER
// ============================================================
def lineDistance(
p: Point,
a: Point,
b: Point
): Double = {
val dx =
b.x - a.x
val dy =
b.y - a.y
if (
dx == 0 &&
dy == 0
) {
distance(
p,
a
)
} else {
val t =
(
(p.x - a.x) * dx +
(p.y - a.y) * dy
).toDouble /
(
dx * dx +
dy * dy
)
val tt =
math.max(
0.0,
math.min(
1.0,
t
)
)
val px =
a.x +
tt * dx
val py =
a.y +
tt * dy
math.hypot(
p.x - px,
p.y - py
)
}
}
def simplify(
pts: Seq[Point],
tolerance: Double
): Vector[Point] = {
if (
pts.length <= 2
) {
pts.toVector
} else {
var maxDist = 0.0
var index = 0
var i = 1
while (
i < pts.length - 1
) {
val d =
lineDistance(
pts(i),
pts.head,
pts.last
)
if (
d > maxDist
) {
maxDist =
d
index =
i
}
i += 1
}
if (
maxDist > tolerance
) {
val left =
simplify(
pts.take(
index + 1
),
tolerance
)
val right =
simplify(
pts.drop(
index
),
tolerance
)
(
left.dropRight(1) ++
right
).toVector
} else {
Vector(
pts.head,
pts.last
)
}
}
}
// ============================================================
// DETECT CLOSED
// ============================================================
def closed(
pts: Seq[Point]
): Boolean = {
pts.length >= 4 &&
distance(
pts.head,
pts.last
) < 45
}
// ============================================================
// DETECT CIRCLE
// ============================================================
def isCircle(
pts: Seq[Point]
): Boolean = {
if (
pts.length < 18
) {
false
} else if (
!closed(pts)
) {
false
} else {
val center =
averagePoint(
pts
)
var total = 0.0
var i = 0
while (
i < pts.length
) {
total +=
distance(
center,
pts(i)
)
i += 1
}
val radius =
total /
pts.length
var error = 0.0
i = 0
while (
i < pts.length
) {
error +=
math.abs(
distance(
center,
pts(i)
) -
radius
)
i += 1
}
val avgError =
error /
pts.length
radius > 25 &&
avgError / radius < 0.23
}
}
// ============================================================
// AVERAGE POINT
// ============================================================
def averagePoint(
pts: Seq[Point]
): Point = {
var sx = 0.0
var sy = 0.0
var i = 0
while (
i < pts.length
) {
sx += pts(i).x
sy += pts(i).y
i += 1
}
new Point(
(sx / pts.length).toInt,
(sy / pts.length).toInt
)
}
// ============================================================
// DETECT POLYGON
// ============================================================
def polygon(
pts: Seq[Point]
): Vector[Point] = {
simplify(
pts,
18
)
}
// ============================================================
// KOJO COORDINATES
// ============================================================
def kx(
x: Int
): Int = {
x - CX
}
def ky(
y: Int
): Int = {
CY - y
}
// ============================================================
// NUMBER
// ============================================================
def num(
d: Double
): String = {
if (
math.abs(
d - d.round
) < 0.05
) {
d.round.toString
} else {
"%.2f".format(d)
}
}
// ============================================================
// EXACT PATH CODE
// ============================================================
// setPosition + lineTo is deliberately used for arbitrary
// freehand strokes so that the recorded geometry is preserved.
// ============================================================
def exactPathCode(
points: Seq[Point],
number: Int
): String = {
val p =
simplify(
points,
2
)
if (
p.isEmpty
) {
""
} else {
val sb =
new StringBuilder()
sb.append(
"// EXACT STROKE " +
number +
"\n"
)
sb.append(
"penUp()\n"
)
sb.append(
"setPosition(" +
kx(p.head.x) +
", " +
ky(p.head.y) +
")\n"
)
sb.append(
"penDown()\n"
)
var i = 1
while (
i < p.length
) {
sb.append(
"lineTo(" +
kx(p(i).x) +
", " +
ky(p(i).y) +
")\n"
)
i += 1
}
sb.toString
}
}
// ============================================================
// SMART SQUARE CODE
// ============================================================
def squareCode(
p: Vector[Point]
): String = {
if (
p.length == 5 &&
closed(p)
) {
val a =
distance(
p(0),
p(1)
)
val b =
distance(
p(1),
p(2)
)
if (
b > 0 &&
math.max(a,b) /
math.min(a,b) < 1.25
) {
val angle =
heading(
p(0),
p(1)
)
"penUp()\n" +
"setPosition(" +
kx(p(0).x) +
", " +
ky(p(0).y) +
")\n" +
"setHeading(" +
num(angle) +
")\n" +
"penDown()\n" +
"repeat(4) {\n" +
" forward(" +
num(a) +
")\n" +
" right(90)\n" +
"}\n"
} else {
exactPathCode(
p,
0
)
}
} else {
exactPathCode(
p,
0
)
}
}
// ============================================================
// SMART RECTANGLE CODE
// ============================================================
def rectangleCode(
p: Vector[Point]
): String = {
if (
p.length == 5 &&
closed(p)
) {
val a =
distance(
p(0),
p(1)
)
val b =
distance(
p(1),
p(2)
)
if (
a > 20 &&
b > 20
) {
val ratio =
math.max(a,b) /
math.min(a,b)
if (
ratio >= 1.25
) {
val angle =
heading(
p(0),
p(1)
)
"penUp()\n" +
"setPosition(" +
kx(p(0).x) +
", " +
ky(p(0).y) +
")\n" +
"setHeading(" +
num(angle) +
")\n" +
"penDown()\n" +
"forward(" +
num(a) +
")\n" +
"right(90)\n" +
"forward(" +
num(b) +
")\n" +
"right(90)\n" +
"forward(" +
num(a) +
")\n" +
"right(90)\n" +
"forward(" +
num(b) +
")\n" +
"right(90)\n"
} else {
squareCode(
p
)
}
} else {
exactPathCode(
p,
0
)
}
} else {
exactPathCode(
p,
0
)
}
}
// ============================================================
// CIRCLE CODE
// ============================================================
def circleCode(
pts: Seq[Point]
): String = {
val c =
averagePoint(
pts
)
val radius =
distance(
c,
pts.head
)
val circumference =
2.0 *
math.Pi *
radius
val step =
circumference /
360.0
"penUp()\n" +
"setPosition(" +
kx(pts.head.x) +
", " +
ky(pts.head.y) +
")\n" +
"setHeading(0)\n" +
"penDown()\n" +
"repeat(360) {\n" +
" forward(" +
num(step) +
")\n" +
" right(1)\n" +
"}\n"
}
// ============================================================
// SMART STROKE
// ============================================================
def generateStroke(
points: Seq[Point],
number: Int
): String = {
val simple =
polygon(
points
)
if (
simple.isEmpty
) {
""
} else if (
isCircle(points)
) {
"// CIRCLE DETECTED - STROKE " +
number +
"\n" +
circleCode(
points
)
} else if (
simple.length == 5 &&
closed(simple)
) {
val a =
distance(
simple(0),
simple(1)
)
val b =
distance(
simple(1),
simple(2)
)
val ratio =
if (
math.min(a,b) > 0
)
math.max(a,b) /
math.min(a,b)
else
99.0
if (
ratio < 1.25
) {
"// SQUARE DETECTED - STROKE " +
number +
"\n" +
squareCode(
simple
)
} else {
"// RECTANGLE DETECTED - STROKE " +
number +
"\n" +
rectangleCode(
simple
)
}
} else {
exactPathCode(
points,
number
)
}
}
// ============================================================
// GENERATE WHOLE DRAWING
// ============================================================
def generateKojoCode(): Unit = {
if (
strokes.isEmpty
) {
codeArea.setText(
"// NOTHING DRAWN YET\n" +
"// Draw on the left canvas."
)
statusText(
"DRAW SOMETHING FIRST"
)
} else {
val sb =
new StringBuilder()
sb.append(
"// ==================================================\n"
)
sb.append(
"// ULTRA LEGEND EXACT CANVAS -> KOJO\n"
)
sb.append(
"// Generated from recorded geometry\n"
)
sb.append(
"// ==================================================\n\n"
)
sb.append(
"cleari()\n"
)
sb.append(
"setAnimationDelay(5)\n"
)
sb.append(
"setPenColor(blue)\n"
)
sb.append(
"setPenThickness(" +
brushSize +
")\n\n"
)
var i = 0
while (
i < strokes.length
) {
sb.append(
generateStroke(
strokes(i).points,
i + 1
)
)
sb.append(
"\n"
)
i += 1
}
sb.append(
"\n// END OF GENERATED DRAWING\n"
)
generatedCode =
sb.toString
codeArea.setText(
generatedCode
)
codeArea.setCaretPosition(
0
)
statusText(
"EXACT KOJO CODE GENERATED"
)
}
}
// ============================================================
// SAVE CODE
// ============================================================
def saveCode(): Unit = {
val chooser =
new javax.swing.JFileChooser()
chooser.setDialogTitle(
"SAVE KOJO CODE"
)
if (
chooser.showSaveDialog(frame) ==
javax.swing.JFileChooser.APPROVE_OPTION
) {
try {
var file =
chooser.getSelectedFile
if (
!file.getName
.toLowerCase
.endsWith(".scala")
) {
file =
new java.io.File(
file.getAbsolutePath +
".scala"
)
}
val writer =
new java.io.PrintWriter(
file
)
writer.write(
codeArea.getText
)
writer.close()
statusText(
"CODE SAVED"
)
} catch {
case _: Exception =>
statusText(
"SAVE ERROR"
)
}
}
}
// ============================================================
// COPY CODE
// ============================================================
def copyCode(): Unit = {
if (
generatedCode.trim.isEmpty
) {
generateKojoCode()
}
try {
val clipboard =
java.awt.Toolkit
.getDefaultToolkit
.getSystemClipboard
clipboard.setContents(
new java.awt.datatransfer.StringSelection(
codeArea.getText
),
null
)
statusText(
"KOJO CODE COPIED"
)
} catch {
case _: Exception =>
statusText(
"COPY FAILED"
)
}
}
// ============================================================
// CLEAR
// ============================================================
def clearDrawing(): Unit = {
remember()
strokes.clear()
activeStroke.clear()
generatedCode =
""
codeArea.setText(
"// Canvas cleared."
)
repaintCanvas()
statusText(
"CANVAS CLEARED"
)
}
// ============================================================
// OBJECT LIBRARY
// ============================================================
val objects =
Array(
"CAR",
"SPORTS CAR",
"RACING CAR",
"TRUCK",
"BUS",
"TRAIN",
"AIRPLANE",
"ROCKET",
"HELICOPTER",
"BICYCLE",
"MOTORBIKE",
"SCOOTER",
"BOAT",
"SHIP",
"SUBMARINE",
"HOUSE",
"VILLA",
"CASTLE",
"PALACE",
"TOWER",
"SCHOOL",
"SHOP",
"HOSPITAL",
"LIGHTHOUSE",
"BRIDGE",
"CITY",
"SKYSCRAPER",
"TREE",
"PALM TREE",
"FLOWER",
"CACTUS",
"MUSHROOM",
"MOUNTAIN",
"VOLCANO",
"ISLAND",
"CLOUD",
"RAINBOW",
"SUN",
"MOON",
"PLANET",
"EARTH",
"GALAXY",
"BOY",
"GIRL",
"MAN",
"WOMAN",
"PERSON",
"ROBOT",
"ASTRONAUT",
"SCIENTIST",
"KING",
"QUEEN",
"SUPERHERO",
"CAT",
"DOG",
"BIRD",
"FISH",
"SHARK",
"WHALE",
"DOLPHIN",
"LION",
"TIGER",
"ELEPHANT",
"HORSE",
"RABBIT",
"BEAR",
"PANDA",
"MONKEY",
"FOX",
"DEER",
"COW",
"GOAT",
"SHEEP",
"CHICKEN",
"DUCK",
"BUTTERFLY",
"BEE",
"SNAKE",
"CROCODILE",
"TURTLE",
"APPLE",
"BANANA",
"ORANGE",
"MANGO",
"WATERMELON",
"PIZZA",
"BURGER",
"CAKE",
"ICE CREAM",
"DONUT",
"GUITAR",
"PIANO",
"DRUM",
"MICROPHONE",
"SPEAKER",
"CAMERA",
"COMPUTER",
"PHONE",
"BOOK",
"PENCIL",
"CLOCK",
"KEY",
"LAMP",
"CHAIR",
"TABLE",
"BACKPACK",
"STAR",
"HEART",
"DIAMOND",
"CROWN",
"TROPHY",
"MEDAL",
"GIFT",
"BALLOON",
"UMBRELLA",
"MAGIC WAND",
"FOOTBALL",
"BASKETBALL",
"TENNIS BALL",
"CRICKET BAT",
"CRICKET BALL",
"CYBER CITY",
"AI ROBOT",
"FUTURE CAR",
"DRONE",
"SPACE STATION",
"TIME MACHINE",
"HOLOGRAM",
"NEON TOWER",
"FUTURE HOUSE"
)
// ============================================================
// SHAPE LIBRARY
// ============================================================
val shapes =
Array(
"LINE",
"SQUARE",
"RECTANGLE",
"ROUNDED RECTANGLE",
"CIRCLE",
"ELLIPSE",
"TRIANGLE",
"DIAMOND",
"PENTAGON",
"HEXAGON",
"OCTAGON",
"STAR 5",
"STAR 6",
"STAR 8",
"HEART",
"ARROW RIGHT",
"ARROW LEFT",
"ARROW UP",
"ARROW DOWN",
"CHEVRON",
"PLUS",
"CROSS",
"X",
"LIGHTNING",
"CLOUD",
"SUN",
"MOON",
"CRESCENT",
"RING",
"ARC",
"SEMICIRCLE",
"WAVE",
"SPIRAL",
"BURST",
"HOUSE",
"TREE",
"MOUNTAIN",
"FLOWER",
"SNOWFLAKE",
"SMILEY"
)
def populateObjects(): Unit = {
objectModel.clear()
var i = 0
while (
i < objects.length
) {
objectModel.addElement(
objects(i)
)
i += 1
}
}
// ============================================================
// OBJECT HASH
// ============================================================
def objectSeed(
name: String
): Int = {
math.abs(
name.hashCode
)
}
// ============================================================
// OBJECT MODEL GEOMETRY
// ============================================================
// The same deterministic geometry is used both for the canvas
// object and generated Kojo code.
// ============================================================
def objectPoints(
name: String
): Vector[Point] = {
val seed =
objectSeed(
name
)
val kind =
seed % 7
if (
name == "STAR"
) {
starPoints(
5,
250
)
} else if (
name == "HEART"
) {
heartPoints()
} else if (
name == "HOUSE" ||
name == "VILLA" ||
name == "CASTLE" ||
name == "PALACE"
) {
Vector(
new Point(260,500),
new Point(260,290),
new Point(500,90),
new Point(740,290),
new Point(740,500),
new Point(260,500)
)
} else if (
name == "TREE" ||
name == "PALM TREE"
) {
Vector(
new Point(455,570),
new Point(545,570),
new Point(545,330),
new Point(670,250),
new Point(600,160),
new Point(500,210),
new Point(400,160),
new Point(330,250),
new Point(455,330),
new Point(455,570)
)
} else if (
name == "CAR" ||
name == "SPORTS CAR" ||
name == "RACING CAR"
) {
Vector(
new Point(200,470),
new Point(280,370),
new Point(390,370),
new Point(470,260),
new Point(650,260),
new Point(760,370),
new Point(850,370),
new Point(910,470),
new Point(200,470)
)
} else if (
name == "ROCKET"
) {
Vector(
new Point(430,500),
new Point(450,210),
new Point(500,100),
new Point(550,210),
new Point(570,500),
new Point(500,570),
new Point(430,500)
)
} else if (
name == "FISH" ||
name == "SHARK" ||
name == "WHALE" ||
name == "DOLPHIN"
) {
Vector(
new Point(180,350),
new Point(350,230),
new Point(650,230),
new Point(820,350),
new Point(650,470),
new Point(350,470),
new Point(180,350)
)
} else {
val r =
180 + seed % 90
if (
kind == 0
) {
Vector(
new Point(
CX - r,
CY - r
),
new Point(
CX + r,
CY - r
),
new Point(
CX + r,
CY + r
),
new Point(
CX - r,
CY + r
),
new Point(
CX - r,
CY - r
)
)
} else if (
kind == 1
) {
starPoints(
6,
r
)
} else if (
kind == 2
) {
polygonPoints(
5,
r
)
} else if (
kind == 3
) {
polygonPoints(
6,
r
)
} else if (
kind == 4
) {
polygonPoints(
8,
r
)
} else if (
kind == 5
) {
Vector(
new Point(
CX,
CY - r
),
new Point(
CX + r,
CY
),
new Point(
CX,
CY + r
),
new Point(
CX - r,
CY
),
new Point(
CX,
CY - r
)
)
} else {
starPoints(
5,
r
)
}
}
}
// ============================================================
// POLYGON POINTS
// ============================================================
def polygonPoints(
sides: Int,
radius: Int
): Vector[Point] = {
val out =
ArrayBuffer[Point]()
var i = 0
while (
i <= sides
) {
val angle =
-math.Pi / 2 +
i *
2.0 *
math.Pi /
sides
out +=
new Point(
CX +
(math.cos(angle) * radius).toInt,
CY +
(math.sin(angle) * radius).toInt
)
i += 1
}
out.toVector
}
// ============================================================
// STAR
// ============================================================
def starPoints(
points: Int,
radius: Int
): Vector[Point] = {
val out =
ArrayBuffer[Point]()
var i = 0
while (
i <= points * 2
) {
val angle =
-math.Pi / 2 +
i *
math.Pi /
points
val r =
if (
i % 2 == 0
)
radius
else
(radius * 0.45).toInt
out +=
new Point(
CX +
(math.cos(angle) * r).toInt,
CY +
(math.sin(angle) * r).toInt
)
i += 1
}
out.toVector
}
// ============================================================
// HEART
// ============================================================
def heartPoints(): Vector[Point] = {
val out =
ArrayBuffer[Point]()
var i = 0
while (
i <= 180
) {
val t =
i *
math.Pi /
180.0
val x =
16 *
math.pow(
math.sin(t),
3
)
val y =
13 *
math.cos(t) -
5 *
math.cos(
2 * t
) -
2 *
math.cos(
3 * t
) -
math.cos(
4 * t
)
out +=
new Point(
CX +
(x * 13).toInt,
CY -
(y * 13).toInt
)
i += 1
}
out.toVector
}
// ============================================================
// DRAW OBJECT ON CANVAS
// ============================================================
def drawObjectPreview(
g2: Graphics2D,
name: String
): Unit = {
val pts =
objectPoints(
name
)
g2.setColor(
new Color(
25,
105,
210
)
)
g2.setStroke(
new BasicStroke(
5,
BasicStroke.CAP_ROUND,
BasicStroke.JOIN_ROUND
)
)
var i = 1
while (
i < pts.length
) {
g2.drawLine(
pts(i - 1).x,
pts(i - 1).y,
pts(i).x,
pts(i).y
)
i += 1
}
g2.setFont(
new Font(
"Arial",
Font.BOLD,
24
)
)
val labelWidth =
g2.getFontMetrics
.stringWidth(
name
)
g2.drawString(
name,
CX -
labelWidth / 2,
620
)
}
// ============================================================
// ADD OBJECT TO EXACT STROKES
// ============================================================
def addObject(
name: String
): Unit = {
remember()
val pts =
objectPoints(
name
)
strokes +=
DrawStroke(
pts
)
repaintCanvas()
statusText(
"OBJECT ADDED: " +
name
)
}
// ============================================================
// OBJECT KOJO CODE
// ============================================================
def objectCode(
name: String,
number: Int
): String = {
val pts =
objectPoints(
name
)
exactPathCode(
pts,
number
)
}
// ============================================================
// SEARCH
// ============================================================
def searchObjects(): Unit = {
val query =
searchField
.getText
.trim
.toLowerCase
objectModel.clear()
var i = 0
while (
i < objects.length
) {
if (
query.isEmpty ||
objects(i)
.toLowerCase
.contains(
query
)
) {
objectModel.addElement(
objects(i)
)
}
i += 1
}
statusText(
"SEARCH COMPLETE"
)
}
// ============================================================
// 3D PROJECTOR
// ============================================================
def project3D(
x: Double,
y: Double,
z: Double,
angle: Double,
scale: Double,
ox: Int,
oy: Int
): Point = {
val rad =
angle *
math.Pi /
180.0
val rx =
x *
math.cos(rad) -
z *
math.sin(rad)
val rz =
x *
math.sin(rad) +
z *
math.cos(rad)
val px =
ox +
(
rx * scale
).toInt
val py =
oy +
(
y * scale -
rz * scale * 0.55
).toInt
new Point(
px,
py
)
}
// ============================================================
// 3D MODEL RENDER
// ============================================================
def draw3DModel(
g2: Graphics2D,
pts: Seq[Point],
angle: Double,
depth: Double,
scale: Double,
ox: Int,
oy: Int,
reveal: Double
): Unit = {
if (
pts.length < 2
) {
return
}
val front =
ArrayBuffer[Point]()
var i = 0
while (
i < pts.length
) {
val px =
pts(i).x -
CX
val py =
pts(i).y -
CY
front +=
project3D(
px,
py,
0,
angle,
scale,
ox,
oy
)
i += 1
}
val back =
ArrayBuffer[Point]()
i = 0
while (
i < pts.length
) {
val px =
pts(i).x -
CX
val py =
pts(i).y -
CY
back +=
project3D(
px,
py,
depth,
angle,
scale,
ox,
oy
)
i += 1
}
val maxFront =
math.max(
1,
(
front.length *
reveal
).toInt
)
val maxBack =
math.max(
1,
(
back.length *
reveal
).toInt
)
g2.setStroke(
new BasicStroke(
3,
BasicStroke.CAP_ROUND,
BasicStroke.JOIN_ROUND
)
)
g2.setColor(
new Color(
80,
130,
220
)
)
i = 1
while (
i < maxFront &&
i < front.length
) {
g2.drawLine(
front(i - 1).x,
front(i - 1).y,
front(i).x,
front(i).y
)
i += 1
}
g2.setColor(
new Color(
120,
160,
230
)
)
i = 1
while (
i < maxBack &&
i < back.length
) {
g2.drawLine(
back(i - 1).x,
back(i - 1).y,
back(i).x,
back(i).y
)
i += 1
}
val maxConnect =
math.min(
maxFront,
maxBack
)
g2.setColor(
new Color(
40,
80,
160
)
)
i = 0
while (
i < maxConnect
) {
g2.drawLine(
front(i).x,
front(i).y,
back(i).x,
back(i).y
)
i += 1
}
}
// ============================================================
// 3D PREVIEW
// ============================================================
def start3DPreview(): Unit = {
if (
strokes.isEmpty
) {
statusText(
"DRAW OR ADD AN OBJECT FIRST"
)
} else {
val preview =
new JFrame(
"TRUE-TIME 3D STYLE MODEL PREVIEW"
)
preview.setDefaultCloseOperation(
WindowConstants.DISPOSE_ON_CLOSE
)
preview.setSize(
1050,
720
)
preview.setLocationRelativeTo(
frame
)
var angle =
0.0
var reveal =
0.0
var playing =
true
val previewPanel =
new JPanel {
override def paintComponent(
graphics: Graphics
): Unit = {
super.paintComponent(
graphics
)
val g2 =
graphics
.asInstanceOf[Graphics2D]
g2.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON
)
val w =
getWidth
val h =
getHeight
g2.setPaint(
new GradientPaint(
0,
0,
new Color(
10,
20,
45
),
0,
h,
new Color(
55,
20,
95
)
)
)
g2.fillRect(
0,
0,
w,
h
)
val all =
ArrayBuffer[Point]()
var s = 0
while (
s < animationData.length
) {
var p = 0
while (
p < animationData(s).points.length
) {
all +=
animationData(s).points(p)
p += 1
}
s += 1
}
var minX = 0
var maxX = CW
var minY = 0
var maxY = CH
if (
all.nonEmpty
) {
minX =
all.map(
_.x
).min
maxX =
all.map(
_.x
).max
minY =
all.map(
_.y
).min
maxY =
all.map(
_.y
).max
}
val width =
math.max(
100,
maxX - minX
)
val height =
math.max(
100,
maxY - minY
)
val scale =
math.min(
(
w * 0.65
) / width,
(
h * 0.65
) / height
)
val ox =
w / 2
val oy =
h / 2
s = 0
while (
s < animationData.length
) {
draw3DModel(
g2,
animationData(s).points,
angle +
s * 12,
80 + s * 8,
scale,
ox,
oy,
reveal
)
s += 1
}
g2.setColor(
new Color(
255,
255,
255,
210
)
)
g2.setFont(
new Font(
"Arial",
Font.BOLD,
18
)
)
g2.drawString(
"3D MODEL PREVIEW",
25,
35
)
}
}
val timer =
new Timer(
30,
new ActionListener {
override def actionPerformed(
e: ActionEvent
): Unit = {
if (
playing
) {
angle += 2.0
reveal += 0.025
if (
reveal > 1.0
) {
reveal =
1.0
}
previewPanel.repaint()
}
}
}
)
val playButton =
makeButton(
"PLAY / PAUSE"
)
val restartButton =
makeButton(
"RESTART 3D"
)
val closeButton =
makeButton(
"CLOSE"
)
playButton.addActionListener(
new ActionListener {
override def actionPerformed(
e: ActionEvent
): Unit = {
playing =
!playing
}
}
)
restartButton.addActionListener(
new ActionListener {
override def actionPerformed(
e: ActionEvent
): Unit = {
angle =
0.0
reveal =
0.0
previewPanel.repaint()
}
}
)
closeButton.addActionListener(
new ActionListener {
override def actionPerformed(
e: ActionEvent
): Unit = {
timer.stop()
preview.dispose()
}
}
)
val controls =
new JPanel(
new FlowLayout(
FlowLayout.CENTER
)
)
controls.add(
playButton
)
controls.add(
restartButton
)
controls.add(
closeButton
)
preview.setLayout(
new BorderLayout()
)
preview.add(
previewPanel,
BorderLayout.CENTER
)
preview.add(
controls,
BorderLayout.SOUTH
)
preview.setVisible(
true
)
animationData =
copyStrokes(
strokes
)
timer.start()
}
}
// ============================================================
// DRAWING ANIMATION
// ============================================================
def startDrawingAnimation(): Unit = {
if (
strokes.isEmpty
) {
statusText(
"DRAW SOMETHING FIRST"
)
} else {
val preview =
new JFrame(
"EXACT DRAWING ANIMATION"
)
preview.setDefaultCloseOperation(
WindowConstants.DISPOSE_ON_CLOSE
)
preview.setSize(
1050,
720
)
preview.setLocationRelativeTo(
frame
)
val data =
copyStrokes(
strokes
)
var currentStrokeIndex =
0
var currentPointIndex =
1
var running =
true
val panel =
new JPanel {
override def paintComponent(
graphics: Graphics
): Unit = {
super.paintComponent(
graphics
)
val g2 =
graphics
.asInstanceOf[Graphics2D]
g2.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON
)
val scale =
math.min(
(
getWidth -
40
).toDouble /
CW,
(
getHeight -
40
).toDouble /
CH
)
val ox =
(
getWidth -
CW * scale
) / 2.0
val oy =
(
getHeight -
CH * scale
) / 2.0
val old =
g2.getTransform
g2.translate(
ox,
oy
)
g2.scale(
scale,
scale
)
g2.setColor(
Color.WHITE
)
g2.fillRect(
0,
0,
CW,
CH
)
g2.setColor(
currentColor
)
g2.setStroke(
new BasicStroke(
brushSize.toFloat,
BasicStroke.CAP_ROUND,
BasicStroke.JOIN_ROUND
)
)
var s = 0
while (
s < currentStrokeIndex
) {
paintStroke(
g2,
data(s).points
)
s += 1
}
if (
currentStrokeIndex <
data.length
) {
paintStroke(
g2,
data(
currentStrokeIndex
).points.take(
math.min(
currentPointIndex,
data(
currentStrokeIndex
).points.length
)
)
)
}
g2.setTransform(
old
)
}
}
val timer =
new Timer(
math.max(
5,
speedSlider.getValue
),
new ActionListener {
override def actionPerformed(
e: ActionEvent
): Unit = {
if (
running
) {
if (
currentStrokeIndex <
data.length
) {
currentPointIndex += 3
if (
currentPointIndex >=
data(
currentStrokeIndex
).points.length
) {
currentStrokeIndex += 1
currentPointIndex = 1
}
panel.repaint()
} else {
running =
false
}
}
}
}
)
val play =
makeButton(
"PLAY / PAUSE"
)
val reset =
makeButton(
"RESET"
)
val close =
makeButton(
"CLOSE"
)
play.addActionListener(
new ActionListener {
override def actionPerformed(
e: ActionEvent
): Unit = {
running =
!running
}
}
)
reset.addActionListener(
new ActionListener {
override def actionPerformed(
e: ActionEvent
): Unit = {
currentStrokeIndex =
0
currentPointIndex =
1
running =
true
panel.repaint()
}
}
)
close.addActionListener(
new ActionListener {
override def actionPerformed(
e: ActionEvent
): Unit = {
timer.stop()
preview.dispose()
}
}
)
val controls =
new JPanel(
new FlowLayout()
)
controls.add(
play
)
controls.add(
reset
)
controls.add(
close
)
preview.setLayout(
new BorderLayout()
)
preview.add(
panel,
BorderLayout.CENTER
)
preview.add(
controls,
BorderLayout.SOUTH
)
preview.setVisible(
true
)
timer.start()
}
}
// ============================================================
// BUTTONS
// ============================================================
val pencilButton =
makeButton(
"PENCIL"
)
val eraserButton =
makeButton(
"ERASER"
)
val undoButton =
makeButton(
"UNDO"
)
val redoButton =
makeButton(
"REDO"
)
val clearButton =
makeButton(
"CLEAR"
)
val generateButton =
makeButton(
"GENERATE CODE"
)
val copyButton =
makeButton(
"COPY CODE"
)
val saveCodeButton =
makeButton(
"SAVE CODE"
)
val animationButton =
makeButton(
"2D ANIMATION"
)
val model3DButton =
makeButton(
"3D MODEL"
)
val gridButton =
makeButton(
"GRID"
)
val snapButton =
makeButton(
"SNAP"
)
val colorButton =
makeButton(
"COLOR"
)
// ============================================================
// SEARCH
// ============================================================
searchField =
new JTextField(
12
)
val searchButton =
makeButton(
"SEARCH"
)
// ============================================================
// OBJECT LIST
// ============================================================
objectList =
new JList[String](
objectModel
)
objectList.setFont(
new Font(
"Arial",
Font.BOLD,
13
)
)
populateObjects()
// ============================================================
// CODE AREA
// ============================================================
codeArea =
new JTextArea()
codeArea.setEditable(
false
)
codeArea.setFont(
new Font(
"Monospaced",
Font.PLAIN,
13
)
)
codeArea.setText(
"// DRAW SOMETHING ON THE CANVAS\n" +
"// THEN PRESS GENERATE CODE"
)
// ============================================================
// STATUS
// ============================================================
status =
new JLabel(
"READY"
)
status.setFont(
new Font(
"Arial",
Font.BOLD,
13
)
)
// ============================================================
// SPEED
// ============================================================
speedSlider =
new JSlider(
5,
100,
25
)
speedSlider.setPreferredSize(
new Dimension(
140,
35
)
)
// ============================================================
// TOP TOOLBAR
// ============================================================
val toolbar =
new JPanel(
new FlowLayout(
FlowLayout.LEFT,
4,
4
)
)
toolbar.add(
pencilButton
)
toolbar.add(
eraserButton
)
toolbar.add(
undoButton
)
toolbar.add(
redoButton
)
toolbar.add(
clearButton
)
toolbar.add(
generateButton
)
toolbar.add(
copyButton
)
toolbar.add(
saveCodeButton
)
toolbar.add(
animationButton
)
toolbar.add(
model3DButton
)
toolbar.add(
gridButton
)
toolbar.add(
snapButton
)
toolbar.add(
colorButton
)
toolbar.add(
new JLabel(
"SPEED"
)
)
toolbar.add(
speedSlider
)
// ============================================================
// LEFT PANEL
// ============================================================
val leftPanel =
new JPanel(
new BorderLayout(
5,
5
)
)
leftPanel.setBorder(
BorderFactory.createEmptyBorder(
5,
5,
5,
5
)
)
val leftTitle =
new JLabel(
"OBJECT LIBRARY"
)
leftTitle.setFont(
new Font(
"Arial",
Font.BOLD,
18
)
)
val searchPanel =
new JPanel(
new FlowLayout(
FlowLayout.LEFT,
2,
2
)
)
searchPanel.add(
searchField
)
searchPanel.add(
searchButton
)
val libraryTop =
new JPanel(
new BorderLayout()
)
libraryTop.add(
leftTitle,
BorderLayout.NORTH
)
libraryTop.add(
searchPanel,
BorderLayout.SOUTH
)
leftPanel.add(
libraryTop,
BorderLayout.NORTH
)
leftPanel.add(
new JScrollPane(
objectList
),
BorderLayout.CENTER
)
// ============================================================
// RIGHT PANEL
// ============================================================
val rightPanel =
new JPanel(
new BorderLayout(
5,
5
)
)
rightPanel.setBorder(
BorderFactory.createEmptyBorder(
5,
5,
5,
5
)
)
val rightTitle =
new JLabel(
"EXACT KOJO / SCALA CODE"
)
rightTitle.setFont(
new Font(
"Arial",
Font.BOLD,
18
)
)
rightPanel.add(
rightTitle,
BorderLayout.NORTH
)
rightPanel.add(
new JScrollPane(
codeArea
),
BorderLayout.CENTER
)
// ============================================================
// MAIN FRAME
// ============================================================
frame =
new JFrame(
"ULTRA LEGEND DRAW -> EXACT KOJO -> 3D STUDIO X"
)
frame.setDefaultCloseOperation(
WindowConstants.EXIT_ON_CLOSE
)
frame.setSize(
1650,
900
)
frame.setLocationRelativeTo(
null
)
frame.setLayout(
new BorderLayout(
4,
4
)
)
frame.add(
toolbar,
BorderLayout.NORTH
)
val centerPanel =
new JPanel(
new GridLayout(
1,
3
)
)
val canvasContainer =
new JPanel(
new BorderLayout()
)
canvasContainer.add(
new JLabel(
"CANVAS"
),
BorderLayout.NORTH
)
canvasContainer.add(
canvas,
BorderLayout.CENTER
)
centerPanel.add(
leftPanel
)
centerPanel.add(
canvasContainer
)
centerPanel.add(
rightPanel
)
frame.add(
centerPanel,
BorderLayout.CENTER
)
frame.add(
status,
BorderLayout.SOUTH
)
// ============================================================
// PENCIL
// ============================================================
pencilButton.addActionListener(
new ActionListener {
override def actionPerformed(
e: ActionEvent
): Unit = {
eraser =
false
canvas.requestFocusInWindow()
statusText(
"PENCIL MODE"
)
}
}
)
// ============================================================
// ERASER
// ============================================================
eraserButton.addActionListener(
new ActionListener {
override def actionPerformed(
e: ActionEvent
): Unit = {
eraser =
true
canvas.requestFocusInWindow()
statusText(
"ERASER MODE"
)
}
}
)
// ============================================================
// UNDO
// ============================================================
undoButton.addActionListener(
new ActionListener {
override def actionPerformed(
e: ActionEvent
): Unit = {
doUndo()
canvas.requestFocusInWindow()
}
}
)
// ============================================================
// REDO
// ============================================================
redoButton.addActionListener(
new ActionListener {
override def actionPerformed(
e: ActionEvent
): Unit = {
doRedo()
canvas.requestFocusInWindow()
}
}
)
// ============================================================
// CLEAR
// ============================================================
clearButton.addActionListener(
new ActionListener {
override def actionPerformed(
e: ActionEvent
): Unit = {
clearDrawing()
canvas.requestFocusInWindow()
}
}
)
// ============================================================
// GRID
// ============================================================
gridButton.addActionListener(
new ActionListener {
override def actionPerformed(
e: ActionEvent
): Unit = {
gridOn =
!gridOn
repaintCanvas()
statusText(
if (
gridOn
)
"GRID ON"
else
"GRID OFF"
)
}
}
)
// ============================================================
// SNAP
// ============================================================
snapButton.addActionListener(
new ActionListener {
override def actionPerformed(
e: ActionEvent
): Unit = {
snapOn =
!snapOn
statusText(
if (
snapOn
)
"SNAP ON"
else
"SNAP OFF"
)
}
}
)
// ============================================================
// COLOR
// ============================================================
colorButton.addActionListener(
new ActionListener {
override def actionPerformed(
e: ActionEvent
): Unit = {
val selected =
javax.swing.JColorChooser.showDialog(
frame,
"CHOOSE DRAW COLOR",
currentColor
)
if (
selected != null
) {
currentColor =
selected
repaintCanvas()
statusText(
"COLOR CHANGED"
)
}
}
}
)
// ============================================================
// GENERATE
// ============================================================
generateButton.addActionListener(
new ActionListener {
override def actionPerformed(
e: ActionEvent
): Unit = {
generateKojoCode()
canvas.requestFocusInWindow()
}
}
)
// ============================================================
// COPY
// ============================================================
copyButton.addActionListener(
new ActionListener {
override def actionPerformed(
e: ActionEvent
): Unit = {
copyCode()
}
}
)
// ============================================================
// SAVE CODE
// ============================================================
saveCodeButton.addActionListener(
new ActionListener {
override def actionPerformed(
e: ActionEvent
): Unit = {
saveCode()
}
}
)
// ============================================================
// 2D ANIMATION
// ============================================================
animationButton.addActionListener(
new ActionListener {
override def actionPerformed(
e: ActionEvent
): Unit = {
startDrawingAnimation()
}
}
)
// ============================================================
// 3D MODEL
// ============================================================
model3DButton.addActionListener(
new ActionListener {
override def actionPerformed(
e: ActionEvent
): Unit = {
start3DPreview()
}
}
)
// ============================================================
// SEARCH
// ============================================================
searchButton.addActionListener(
new ActionListener {
override def actionPerformed(
e: ActionEvent
): Unit = {
searchObjects()
}
}
)
searchField.addActionListener(
new ActionListener {
override def actionPerformed(
e: ActionEvent
): Unit = {
searchObjects()
}
}
)
// ============================================================
// OBJECT DOUBLE CLICK
// ============================================================
objectList.addMouseListener(
new MouseAdapter {
override def mouseClicked(
e: MouseEvent
): Unit = {
if (
e.getClickCount == 2
) {
val value =
objectList.getSelectedValue
if (
value != null
) {
addObject(
value
)
}
}
}
}
)
// ============================================================
// MOUSE PRESS
// ============================================================
canvas.addMouseListener(
new MouseAdapter {
override def mousePressed(
e: MouseEvent
): Unit = {
if (
e.getButton ==
MouseEvent.BUTTON1
) {
remember()
activeStroke.clear()
activeStroke +=
mousePoint(e)
drawing =
true
statusText(
"DRAWING..."
)
}
}
override def mouseReleased(
e: MouseEvent
): Unit = {
if (
drawing
) {
activeStroke +=
mousePoint(e)
if (
eraser
) {
val hit =
activeStroke.last
val remaining =
ArrayBuffer[DrawStroke]()
var i = 0
while (
i < strokes.length
) {
val s =
strokes(i)
var remove =
false
var j = 0
while (
j < s.points.length &&
!remove
) {
if (
distance(
hit,
s.points(j)
) < 30
) {
remove =
true
}
j += 1
}
if (
!remove
) {
remaining +=
s
}
i += 1
}
strokes.clear()
strokes ++=
remaining
} else {
if (
activeStroke.length >= 2
) {
strokes +=
DrawStroke(
activeStroke.toVector
)
}
}
activeStroke.clear()
drawing =
false
repaintCanvas()
statusText(
"STROKE SAVED"
)
}
}
}
)
// ============================================================
// MOUSE DRAG
// ============================================================
canvas.addMouseMotionListener(
new MouseMotionAdapter {
override def mouseDragged(
e: MouseEvent
): Unit = {
if (
drawing
) {
val p =
mousePoint(e)
if (
activeStroke.isEmpty ||
distance(
activeStroke.last,
p
) >= 2
) {
activeStroke +=
p
}
repaintCanvas()
}
}
}
)
// ============================================================
// KEYBOARD SHORTCUTS
// ============================================================
canvas.addKeyListener(
new KeyAdapter {
override def keyPressed(
e: KeyEvent
): Unit = {
val key =
e.getKeyCode
if (
e.isControlDown &&
key == KeyEvent.VK_Z
) {
doUndo()
} else if (
e.isControlDown &&
key == KeyEvent.VK_Y
) {
doRedo()
} else if (
e.isControlDown &&
key == KeyEvent.VK_G
) {
generateKojoCode()
} else if (
e.isControlDown &&
key == KeyEvent.VK_C
) {
copyCode()
} else if (
e.isControlDown &&
key == KeyEvent.VK_S
) {
saveCode()
} else if (
e.isControlDown &&
key == KeyEvent.VK_A
) {
startDrawingAnimation()
} else if (
key == KeyEvent.VK_F1
) {
JOptionPane.showMessageDialog(
frame,
"ULTRA LEGEND X SHORTCUTS\n\n" +
"CTRL + Z = UNDO\n" +
"CTRL + Y = REDO\n" +
"CTRL + G = GENERATE KOJO CODE\n" +
"CTRL + C = COPY CODE\n" +
"CTRL + S = SAVE CODE\n" +
"CTRL + A = 2D ANIMATION\n" +
"DELETE = CLEAR\n" +
"E = ERASER / PENCIL\n" +
"F1 = THIS HELP\n\n" +
"DOUBLE CLICK OBJECT = ADD OBJECT\n" +
"3D MODEL BUTTON = 3D PREVIEW\n\n" +
"IMPORTANT:\n" +
"Canvas strokes are stored as geometry.\n" +
"The generator reproduces the stored path.",
"SECRET CONTROL CENTER",
JOptionPane.INFORMATION_MESSAGE
)
} else if (
key == KeyEvent.VK_DELETE
) {
clearDrawing()
} else if (
key == KeyEvent.VK_E
) {
eraser =
!eraser
statusText(
if (
eraser
)
"ERASER ON"
else
"PENCIL ON"
)
}
}
}
)
// ============================================================
// FOCUS
// ============================================================
canvas.setFocusable(
true
)
canvas.setCursor(
Cursor.getPredefinedCursor(
Cursor.CROSSHAIR_CURSOR
)
)
// ============================================================
// START APPLICATION
// ============================================================
SwingUtilities.invokeLater(
new Runnable {
override def run(): Unit = {
frame.setVisible(
true
)
canvas.requestFocusInWindow()
statusText(
"ULTRA LEGEND X READY"
)
}
}
)