Code Sketch
yooooo1
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.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 javax.swing.BorderFactory
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.JSlider
import javax.swing.JSplitPane
import javax.swing.JTabbedPane
import javax.swing.JTextArea
import javax.swing.JTextField
import javax.swing.ListSelectionModel
import javax.swing.SwingUtilities
import javax.swing.Timer
import javax.swing.WindowConstants
import scala.collection.mutable.ArrayBuffer
// ============================================================
// ULTRA LEGEND DRAW LAB X
// CANVAS -> EXACT KOJO CODE -> 2D ANIMATION -> 3D PREVIEW
// ============================================================
// ============================================================
// CONSTANTS
// ============================================================
val CANVAS_W = 1000
val CANVAS_H = 650
val CENTER_X = CANVAS_W / 2
val CENTER_Y = CANVAS_H / 2
// ============================================================
// DRAWING DATA
// ============================================================
case class DrawStroke(
points: Vector[Point]
)
// ============================================================
// APPLICATION STATE
// ============================================================
var strokes =
ArrayBuffer[DrawStroke]()
var undoHistory =
ArrayBuffer[
ArrayBuffer[DrawStroke]
]()
var redoHistory =
ArrayBuffer[
ArrayBuffer[DrawStroke]
]()
var activeStroke =
ArrayBuffer[Point]()
var drawingNow =
false
var eraserOn =
false
var gridOn =
true
var snapOn =
false
var brushSize =
5.0f
var currentColor =
new Color(
20,
70,
180
)
var generatedCode =
""
var animationSpeed =
25
// ============================================================
// UI REFERENCES
// ============================================================
var frame: JFrame =
null
var canvas: JPanel =
null
var codeArea: JTextArea =
null
var status: JLabel =
null
var objectList: JList[String] =
null
var shapeList: JList[String] =
null
var searchField: JTextField =
null
var speedSlider: JSlider =
null
// ============================================================
// MODELS
// ============================================================
val objectModel =
new DefaultListModel[String]()
val shapeModel =
new DefaultListModel[String]()
// ============================================================
// BUTTON REFERENCES
// ============================================================
var pencilButton: JButton =
null
var eraserButton: JButton =
null
var undoButton: JButton =
null
var redoButton: JButton =
null
var clearButton: JButton =
null
var generateButton: JButton =
null
var copyButton: JButton =
null
var saveButton: JButton =
null
var animationButton: JButton =
null
var model3DButton: JButton =
null
var gridButton: JButton =
null
var snapButton: JButton =
null
var colorButton: JButton =
null
// ============================================================
// BUTTON HELPER
// ============================================================
def makeButton(
text: String
): JButton = {
val b =
new JButton(
text
)
b.setFocusable(
false
)
b.setFont(
new Font(
"Arial",
Font.BOLD,
11
)
)
b
}
// ============================================================
// STATUS HELPER
// ============================================================
def setStatus(
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 > 40
) {
undoHistory.remove(
0
)
}
}
def doUndo(): Unit = {
if (
undoHistory.nonEmpty
) {
redoHistory +=
copyStrokes(
strokes
)
val old =
undoHistory(
undoHistory.length - 1
)
undoHistory.remove(
undoHistory.length - 1
)
strokes.clear()
strokes ++=
copyStrokes(
old
)
repaintCanvas()
setStatus(
"UNDO COMPLETE"
)
} else {
setStatus(
"NOTHING TO UNDO"
)
}
}
def doRedo(): Unit = {
if (
redoHistory.nonEmpty
) {
undoHistory +=
copyStrokes(
strokes
)
val newer =
redoHistory(
redoHistory.length - 1
)
redoHistory.remove(
redoHistory.length - 1
)
strokes.clear()
strokes ++=
copyStrokes(
newer
)
repaintCanvas()
setStatus(
"REDO COMPLETE"
)
} else {
setStatus(
"NOTHING TO REDO"
)
}
}
// ============================================================
// SNAP
// ============================================================
def snapCoordinate(
value: Int
): Int = {
if (
snapOn
) {
(value / 10) * 10
} else {
value
}
}
// ============================================================
// MOUSE POINT
// ============================================================
def mousePoint(
e: MouseEvent
): Point = {
val x =
math.max(
0,
math.min(
CANVAS_W - 1,
e.getX
)
)
val y =
math.max(
0,
math.min(
CANVAS_H - 1,
e.getY
)
)
new Point(
snapCoordinate(x),
snapCoordinate(y)
)
}
// ============================================================
// GRID
// ============================================================
def drawGrid(
g2: Graphics2D
): Unit = {
if (
gridOn
) {
g2.setColor(
new Color(
0,
0,
0,
25
)
)
var x = 0
while (
x <= CANVAS_W
) {
g2.drawLine(
x,
0,
x,
CANVAS_H
)
x += 50
}
var y = 0
while (
y <= CANVAS_H
) {
g2.drawLine(
0,
y,
CANVAS_W,
y
)
y += 50
}
g2.setColor(
new Color(
0,
100,
220,
55
)
)
g2.drawLine(
CENTER_X,
0,
CENTER_X,
CANVAS_H
)
g2.drawLine(
0,
CENTER_Y,
CANVAS_W,
CENTER_Y
)
}
}
// ============================================================
// PAINT STROKE
// ============================================================
def paintStroke(
g2: Graphics2D,
pts: Seq[Point]
): Unit = {
if (
pts.length == 1
) {
val p =
pts.head
val size =
math.max(
1,
brushSize.toInt
)
g2.fillOval(
p.x - size / 2,
p.y - size / 2,
size,
size
)
} else {
var i = 1
while (
i < pts.length
) {
val a =
pts(i - 1)
val b =
pts(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.setRenderingHint(
RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY
)
g2.setColor(
Color.WHITE
)
g2.fillRect(
0,
0,
getWidth,
getHeight
)
drawGrid(
g2
)
g2.setStroke(
new BasicStroke(
brushSize,
BasicStroke.CAP_ROUND,
BasicStroke.JOIN_ROUND
)
)
if (
eraserOn
) {
g2.setColor(
Color.WHITE
)
} else {
g2.setColor(
currentColor
)
}
var i = 0
while (
i < strokes.length
) {
paintStroke(
g2,
strokes(i).points
)
i += 1
}
if (
activeStroke.nonEmpty
) {
paintStroke(
g2,
activeStroke.toVector
)
}
}
}
canvas.setFocusable(
true
)
canvas.setCursor(
Cursor.getPredefinedCursor(
Cursor.CROSSHAIR_CURSOR
)
)
// ============================================================
// GEOMETRY HELPERS
// ============================================================
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 normalizeAngle(
angle: Double
): Double = {
var a =
angle
while (
a > 180
) {
a -= 360
}
while (
a < -180
) {
a += 360
}
a
}
def centerOf(
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
)
}
// ============================================================
// LINE DISTANCE
// ============================================================
def pointLineDistance(
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 tc =
math.max(
0.0,
math.min(
1.0,
t
)
)
val px =
a.x +
tc * dx
val py =
a.y +
tc * dy
math.hypot(
p.x - px,
p.y - py
)
}
}
// ============================================================
// SIMPLIFY
// ============================================================
def simplify(
pts: Seq[Point],
tolerance: Double
): Vector[Point] = {
if (
pts.length <= 2
) {
pts.toVector
} else {
var maxDistance =
0.0
var splitIndex =
0
var i = 1
while (
i < pts.length - 1
) {
val d =
pointLineDistance(
pts(i),
pts.head,
pts.last
)
if (
d > maxDistance
) {
maxDistance =
d
splitIndex =
i
}
i += 1
}
if (
maxDistance > tolerance
) {
val left =
simplify(
pts.take(
splitIndex + 1
),
tolerance
)
val right =
simplify(
pts.drop(
splitIndex
),
tolerance
)
(
left.dropRight(1) ++
right
).toVector
} else {
Vector(
pts.head,
pts.last
)
}
}
}
// ============================================================
// CLOSED
// ============================================================
def isClosed(
pts: Seq[Point]
): Boolean = {
pts.length >= 4 &&
distance(
pts.head,
pts.last
) <= 45
}
// ============================================================
// CIRCLE DETECTION
// ============================================================
def isCircle(
pts: Seq[Point]
): Boolean = {
if (
pts.length < 20
) {
false
} else if (
!isClosed(pts)
) {
false
} else {
val c =
centerOf(
pts
)
var total =
0.0
var i = 0
while (
i < pts.length
) {
total +=
distance(
c,
pts(i)
)
i += 1
}
val radius =
total /
pts.length
if (
radius < 25
) {
false
} else {
var error =
0.0
i = 0
while (
i < pts.length
) {
error +=
math.abs(
distance(
c,
pts(i)
) -
radius
)
i += 1
}
val meanError =
error /
pts.length
meanError /
radius < 0.24
}
}
}
// ============================================================
// NUMBER
// ============================================================
def fmt(
n: Double
): String = {
if (
math.abs(
n -
n.round
) < 0.04
) {
n.round.toString
} else {
"%.2f".format(
n
)
}
}
// ============================================================
// KOJO COORDINATES
// ============================================================
def kx(
x: Int
): Int = {
x - CENTER_X
}
def ky(
y: Int
): Int = {
CENTER_Y - y
}
// ============================================================
// EXACT PATH CODE
// ============================================================
def exactPathCode(
pts: Seq[Point],
number: Int
): String = {
val simple =
simplify(
pts,
2
)
if (
simple.isEmpty
) {
""
} else {
val sb =
new StringBuilder()
sb.append(
"// EXACT STROKE " +
number +
"\n"
)
sb.append(
"penUp()\n"
)
sb.append(
"setPosition(" +
kx(
simple.head.x
) +
", " +
ky(
simple.head.y
) +
")\n"
)
sb.append(
"penDown()\n"
)
var i = 1
while (
i < simple.length
) {
sb.append(
"lineTo(" +
kx(
simple(i).x
) +
", " +
ky(
simple(i).y
) +
")\n"
)
i += 1
}
sb.toString
}
}
// ============================================================
// SQUARE CODE
// ============================================================
def squareCode(
p: Vector[Point]
): String = {
val side =
distance(
p(0),
p(1)
)
val angle =
heading(
p(0),
p(1)
)
"penUp()\n" +
"setPosition(" +
kx(
p(0).x
) +
", " +
ky(
p(0).y
) +
")\n" +
"setHeading(" +
fmt(angle) +
")\n" +
"penDown()\n" +
"repeat(4) {\n" +
" forward(" +
fmt(side) +
")\n" +
" right(90)\n" +
"}\n"
}
// ============================================================
// RECTANGLE CODE
// ============================================================
def rectangleCode(
p: Vector[Point]
): String = {
val a =
distance(
p(0),
p(1)
)
val b =
distance(
p(1),
p(2)
)
val angle =
heading(
p(0),
p(1)
)
"penUp()\n" +
"setPosition(" +
kx(
p(0).x
) +
", " +
ky(
p(0).y
) +
")\n" +
"setHeading(" +
fmt(angle) +
")\n" +
"penDown()\n" +
"forward(" +
fmt(a) +
")\n" +
"right(90)\n" +
"forward(" +
fmt(b) +
")\n" +
"right(90)\n" +
"forward(" +
fmt(a) +
")\n" +
"right(90)\n" +
"forward(" +
fmt(b) +
")\n" +
"right(90)\n"
}
// ============================================================
// CIRCLE CODE
// ============================================================
def circleCode(
pts: Seq[Point]
): String = {
val c =
centerOf(
pts
)
val radius =
distance(
c,
pts.head
)
val step =
2.0 *
math.Pi *
radius /
360.0
"penUp()\n" +
"setPosition(" +
kx(
pts.head.x
) +
", " +
ky(
pts.head.y
) +
")\n" +
"setHeading(0)\n" +
"penDown()\n" +
"repeat(360) {\n" +
" forward(" +
fmt(step) +
")\n" +
" right(1)\n" +
"}\n"
}
// ============================================================
// SMART STROKE
// ============================================================
def smartStrokeCode(
pts: Seq[Point],
number: Int
): String = {
val p =
simplify(
pts,
18
)
if (
p.isEmpty
) {
""
} else if (
isCircle(pts)
) {
"// CIRCLE DETECTED - STROKE " +
number +
"\n" +
circleCode(
pts
)
} else if (
p.length == 5 &&
isClosed(p)
) {
val a =
distance(
p(0),
p(1)
)
val b =
distance(
p(1),
p(2)
)
if (
a > 1 &&
b > 1
) {
val ratio =
math.max(
a,
b
) /
math.min(
a,
b
)
if (
ratio < 1.25
) {
"// SQUARE DETECTED - STROKE " +
number +
"\n" +
squareCode(
p
)
} else {
"// RECTANGLE DETECTED - STROKE " +
number +
"\n" +
rectangleCode(
p
)
}
} else {
exactPathCode(
pts,
number
)
}
} else {
exactPathCode(
pts,
number
)
}
}
// ============================================================
// COMPLETE KOJO CODE
// ============================================================
def generateKojoCode(): Unit = {
if (
strokes.isEmpty
) {
codeArea.setText(
"// NOTHING DRAWN YET\n" +
"// Draw something on the canvas first."
)
setStatus(
"DRAW SOMETHING FIRST"
)
} else {
val sb =
new StringBuilder()
sb.append(
"// ==================================================\n"
)
sb.append(
"// ULTRA LEGEND EXACT CANVAS -> KOJO\n"
)
sb.append(
"// ORIGINAL 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.toInt +
")\n\n"
)
var i = 0
while (
i < strokes.length
) {
sb.append(
smartStrokeCode(
strokes(i).points,
i + 1
)
)
sb.append(
"\n"
)
i += 1
}
sb.append(
"// ==================================================\n"
)
sb.append(
"// END OF DRAWING\n"
)
sb.append(
"// ==================================================\n"
)
generatedCode =
sb.toString
codeArea.setText(
generatedCode
)
codeArea.setCaretPosition(
0
)
setStatus(
"EXACT KOJO CODE GENERATED"
)
}
}
// ============================================================
// 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
)
setStatus(
"CODE COPIED"
)
} catch {
case _: Exception =>
setStatus(
"COPY FAILED"
)
}
}
// ============================================================
// SAVE CODE
// ============================================================
def saveCode(): Unit = {
if (
generatedCode.trim.isEmpty
) {
generateKojoCode()
}
val chooser =
new JFileChooser()
chooser.setDialogTitle(
"SAVE KOJO CODE"
)
val result =
chooser.showSaveDialog(
frame
)
if (
result ==
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()
setStatus(
"CODE SAVED"
)
} catch {
case _: Exception =>
setStatus(
"SAVE ERROR"
)
}
}
}
// ============================================================
// CLEAR DRAWING
// ============================================================
def clearDrawing(): Unit = {
remember()
strokes.clear()
activeStroke.clear()
generatedCode =
""
codeArea.setText(
"// CANVAS CLEARED\n" +
"// Draw again and press GENERATE CODE."
)
repaintCanvas()
setStatus(
"CANVAS CLEARED"
)
}
// ============================================================
// SHAPE GEOMETRY
// ============================================================
def polygonPoints(
sides: Int,
radius: Int,
startAngle: Double
): Vector[Point] = {
val out =
ArrayBuffer[Point]()
var i = 0
while (
i <= sides
) {
val a =
startAngle +
i *
2.0 *
math.Pi /
sides
out +=
new Point(
CENTER_X +
(
math.cos(a) *
radius
).toInt,
CENTER_Y +
(
math.sin(a) *
radius
).toInt
)
i += 1
}
out.toVector
}
def starPoints(
points: Int,
radius: Int
): Vector[Point] = {
val out =
ArrayBuffer[Point]()
var i = 0
while (
i <= points * 2
) {
val a =
-math.Pi / 2.0 +
i *
math.Pi /
points
val r =
if (
i % 2 == 0
)
radius
else
(
radius *
0.45
).toInt
out +=
new Point(
CENTER_X +
(
math.cos(a) *
r
).toInt,
CENTER_Y +
(
math.sin(a) *
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(
CENTER_X +
(
x * 13
).toInt,
CENTER_Y -
(
y * 13
).toInt
)
i += 1
}
out.toVector
}
// ============================================================
// ALL SHAPE GEOMETRY
// ============================================================
def shapeGeometry(
name: String
): Vector[Point] = {
val n =
name.toUpperCase
if (
n == "LINE"
) {
Vector(
new Point(150,180),
new Point(850,500)
)
} else if (
n == "SQUARE"
) {
Vector(
new Point(300,200),
new Point(700,200),
new Point(700,600),
new Point(300,600),
new Point(300,200)
)
} else if (
n == "RECTANGLE"
) {
Vector(
new Point(170,230),
new Point(830,230),
new Point(830,480),
new Point(170,480),
new Point(170,230)
)
} else if (
n == "ROUNDED RECTANGLE"
) {
Vector(
new Point(230,200),
new Point(770,200),
new Point(770,500),
new Point(230,500),
new Point(230,200)
)
} else if (
n == "CIRCLE"
) {
val out =
ArrayBuffer[Point]()
var i = 0
while (
i <= 360
) {
val a =
i *
math.Pi /
180.0
out +=
new Point(
CENTER_X +
(
math.cos(a) *
220
).toInt,
CENTER_Y +
(
math.sin(a) *
220
).toInt
)
i += 2
}
out.toVector
} else if (
n == "ELLIPSE"
) {
val out =
ArrayBuffer[Point]()
var i = 0
while (
i <= 360
) {
val a =
i *
math.Pi /
180.0
out +=
new Point(
CENTER_X +
(
math.cos(a) *
300
).toInt,
CENTER_Y +
(
math.sin(a) *
160
).toInt
)
i += 2
}
out.toVector
} else if (
n == "TRIANGLE"
) {
polygonPoints(
3,
250,
-math.Pi / 2
)
} else if (
n == "DIAMOND"
) {
Vector(
new Point(
CENTER_X,
110
),
new Point(
820,
CENTER_Y
),
new Point(
CENTER_X,
540
),
new Point(
180,
CENTER_Y
),
new Point(
CENTER_X,
110
)
)
} else if (
n == "PENTAGON"
) {
polygonPoints(
5,
250,
-math.Pi / 2
)
} else if (
n == "HEXAGON"
) {
polygonPoints(
6,
240,
0
)
} else if (
n == "HEPTAGON"
) {
polygonPoints(
7,
240,
-math.Pi / 2
)
} else if (
n == "OCTAGON"
) {
polygonPoints(
8,
240,
math.Pi / 8
)
} else if (
n == "STAR 5"
) {
starPoints(
5,
260
)
} else if (
n == "STAR 6"
) {
starPoints(
6,
255
)
} else if (
n == "STAR 8"
) {
starPoints(
8,
250
)
} else if (
n == "HEART"
) {
heartPoints()
} else if (
n == "ARROW RIGHT"
) {
Vector(
new Point(150,260),
new Point(600,260),
new Point(600,160),
new Point(850,325),
new Point(600,490),
new Point(600,390),
new Point(150,390),
new Point(150,260)
)
} else if (
n == "ARROW LEFT"
) {
Vector(
new Point(850,260),
new Point(400,260),
new Point(400,160),
new Point(150,325),
new Point(400,490),
new Point(400,390),
new Point(850,390),
new Point(850,260)
)
} else if (
n == "ARROW UP"
) {
Vector(
new Point(430,530),
new Point(430,300),
new Point(280,300),
new Point(500,90),
new Point(720,300),
new Point(570,300),
new Point(570,530),
new Point(430,530)
)
} else if (
n == "ARROW DOWN"
) {
Vector(
new Point(430,120),
new Point(430,350),
new Point(280,350),
new Point(500,560),
new Point(720,350),
new Point(570,350),
new Point(570,120),
new Point(430,120)
)
} else if (
n == "CHEVRON"
) {
Vector(
new Point(250,200),
new Point(500,420),
new Point(750,200),
new Point(750,320),
new Point(500,540),
new Point(250,320),
new Point(250,200)
)
} else if (
n == "PLUS"
) {
Vector(
new Point(430,120),
new Point(570,120),
new Point(570,250),
new Point(700,250),
new Point(700,400),
new Point(570,400),
new Point(570,530),
new Point(430,530),
new Point(430,400),
new Point(300,400),
new Point(300,250),
new Point(430,250),
new Point(430,120)
)
} else if (
n == "CROSS"
) {
Vector(
new Point(270,170),
new Point(730,480)
) ++
Vector(
new Point(730,170),
new Point(270,480)
)
} else if (
n == "X"
) {
Vector(
new Point(240,140),
new Point(760,500)
) ++
Vector(
new Point(760,140),
new Point(240,500)
)
} else if (
n == "LIGHTNING"
) {
Vector(
new Point(570,70),
new Point(320,320),
new Point(470,320),
new Point(410,570),
new Point(700,250),
new Point(540,250),
new Point(570,70)
)
} else if (
n == "CLOUD"
) {
Vector(
new Point(230,440),
new Point(270,320),
new Point(380,250),
new Point(480,270),
new Point(560,180),
new Point(690,220),
new Point(770,320),
new Point(830,360),
new Point(800,470),
new Point(230,470),
new Point(230,440)
)
} else if (
n == "SUN"
) {
starPoints(
16,
245
)
} else if (
n == "MOON"
) {
Vector(
new Point(420,140),
new Point(620,150),
new Point(760,300),
new Point(620,500),
new Point(420,500),
new Point(520,410),
new Point(570,300),
new Point(520,210),
new Point(420,140)
)
} else if (
n == "CRESCENT"
) {
Vector(
new Point(470,130),
new Point(620,170),
new Point(720,300),
new Point(620,470),
new Point(470,510),
new Point(550,420),
new Point(590,300),
new Point(550,200),
new Point(470,130)
)
} else if (
n == "RING"
) {
val out =
ArrayBuffer[Point]()
var i = 0
while (
i <= 360
) {
val a =
i *
math.Pi /
180.0
out +=
new Point(
CENTER_X +
(
math.cos(a) *
220
).toInt,
CENTER_Y +
(
math.sin(a) *
220
).toInt
)
i += 3
}
out.toVector
} else if (
n == "ARC"
) {
val out =
ArrayBuffer[Point]()
var i = 0
while (
i <= 240
) {
val a =
(
-120 +
i
) *
math.Pi /
180.0
out +=
new Point(
CENTER_X +
(
math.cos(a) *
260
).toInt,
CENTER_Y +
(
math.sin(a) *
180
).toInt
)
i += 3
}
out.toVector
} else if (
n == "SEMICIRCLE"
) {
val out =
ArrayBuffer[Point]()
var i = 0
while (
i <= 180
) {
val a =
i *
math.Pi /
180.0
out +=
new Point(
CENTER_X +
(
math.cos(a) *
250
).toInt,
CENTER_Y +
(
math.sin(a) *
180
).toInt
)
i += 3
}
out.toVector
} else if (
n == "WAVE"
) {
val out =
ArrayBuffer[Point]()
var x = 80
while (
x <= 920
) {
val y =
CENTER_Y +
(
math.sin(
(
x - 80
) *
0.04
) *
100
).toInt
out +=
new Point(
x,
y
)
x += 5
}
out.toVector
} else if (
n == "SPIRAL"
) {
val out =
ArrayBuffer[Point]()
var a =
0.0
var r =
5.0
while (
a <
math.Pi * 8
) {
out +=
new Point(
CENTER_X +
(
math.cos(a) *
r
).toInt,
CENTER_Y +
(
math.sin(a) *
r
).toInt
)
a += 0.10
r += 1.7
}
out.toVector
} else if (
n == "BURST"
) {
starPoints(
20,
260
)
} else if (
n == "SNOWFLAKE"
) {
Vector(
new Point(
CENTER_X,
90
),
new Point(
CENTER_X,
560
),
new Point(
260,
170
),
new Point(
740,
480
),
new Point(
260,
480
),
new Point(
740,
170
)
)
} else if (
n == "SMILEY"
) {
val out =
ArrayBuffer[Point]()
var i = 0
while (
i <= 360
) {
val a =
i *
math.Pi /
180.0
out +=
new Point(
CENTER_X +
(
math.cos(a) *
230
).toInt,
CENTER_Y +
(
math.sin(a) *
230
).toInt
)
i += 3
}
out.toVector
} else {
polygonPoints(
6,
230,
0
)
}
}
// ============================================================
// OBJECT GEOMETRY
// ============================================================
def objectGeometry(
name: String
): Vector[Point] = {
val n =
name.toUpperCase
if (
n.indexOf("CAR") >= 0
) {
Vector(
new Point(150,470),
new Point(220,370),
new Point(380,360),
new Point(470,250),
new Point(670,250),
new Point(780,360),
new Point(880,370),
new Point(940,470),
new Point(150,470)
)
} else if (
n == "HOUSE" ||
n == "VILLA" ||
n == "PALACE" ||
n == "CASTLE"
) {
Vector(
new Point(250,540),
new Point(250,300),
new Point(500,90),
new Point(750,300),
new Point(750,540),
new Point(250,540)
)
} else if (
n == "TREE" ||
n == "PALM TREE"
) {
Vector(
new Point(455,570),
new Point(545,570),
new Point(545,340),
new Point(680,270),
new Point(600,160),
new Point(500,220),
new Point(400,160),
new Point(320,270),
new Point(455,340),
new Point(455,570)
)
} else if (
n == "ROCKET"
) {
Vector(
new Point(430,520),
new Point(445,220),
new Point(500,80),
new Point(555,220),
new Point(570,520),
new Point(500,590),
new Point(430,520)
)
} else if (
n == "ROBOT" ||
n == "AI ROBOT"
) {
Vector(
new Point(380,540),
new Point(380,300),
new Point(350,300),
new Point(350,150),
new Point(650,150),
new Point(650,300),
new Point(620,300),
new Point(620,540),
new Point(380,540)
)
} else if (
n == "FISH" ||
n == "SHARK" ||
n == "WHALE" ||
n == "DOLPHIN"
) {
Vector(
new Point(170,330),
new Point(330,220),
new Point(640,220),
new Point(820,330),
new Point(640,440),
new Point(330,440),
new Point(170,330)
)
} else if (
n == "BIRD"
) {
Vector(
new Point(250,340),
new Point(400,230),
new Point(500,300),
new Point(620,190),
new Point(760,320),
new Point(620,390),
new Point(470,390),
new Point(350,450),
new Point(250,340)
)
} else if (
n == "FLOWER"
) {
Vector(
new Point(500,570),
new Point(500,320),
new Point(400,230),
new Point(500,140),
new Point(600,230),
new Point(500,320),
new Point(500,570)
)
} else if (
n == "MOUNTAIN"
) {
Vector(
new Point(70,540),
new Point(300,220),
new Point(430,400),
new Point(620,100),
new Point(930,540),
new Point(70,540)
)
} else if (
n == "STAR"
) {
starPoints(
5,
260
)
} else if (
n == "HEART"
) {
heartPoints()
} else if (
n == "PLANET" ||
n == "EARTH" ||
n == "SATURN"
) {
val out =
ArrayBuffer[Point]()
var i = 0
while (
i <= 360
) {
val a =
i *
math.Pi /
180.0
out +=
new Point(
CENTER_X +
(
math.cos(a) *
220
).toInt,
CENTER_Y +
(
math.sin(a) *
220
).toInt
)
i += 3
}
out.toVector
} else if (
n == "CROWN"
) {
Vector(
new Point(260,480),
new Point(220,170),
new Point(390,300),
new Point(500,120),
new Point(610,300),
new Point(780,170),
new Point(740,480),
new Point(260,480)
)
} else {
val hash =
math.abs(
n.hashCode
)
val kind =
hash % 5
if (
kind == 0
) {
shapeGeometry(
"HEXAGON"
)
} else if (
kind == 1
) {
shapeGeometry(
"STAR 6"
)
} else if (
kind == 2
) {
shapeGeometry(
"DIAMOND"
)
} else if (
kind == 3
) {
shapeGeometry(
"PENTAGON"
)
} else {
shapeGeometry(
"OCTAGON"
)
}
}
}
// ============================================================
// ADD GEOMETRY
// ============================================================
def addGeometry(
pts: Vector[Point],
name: String
): Unit = {
remember()
strokes +=
DrawStroke(
pts
)
repaintCanvas()
setStatus(
"ADDED: " +
name
)
}
def addShape(
name: String
): Unit = {
addGeometry(
shapeGeometry(
name
),
name
)
}
def addObject(
name: String
): Unit = {
addGeometry(
objectGeometry(
name
),
name
)
}
// ============================================================
// OBJECT SEARCH
// ============================================================
def searchObjects(): Unit = {
val q =
searchField
.getText
.trim
.toLowerCase
objectModel.clear()
var i = 0
while (
i < objectNames.length
) {
if (
q.isEmpty ||
objectNames(i)
.toLowerCase
.indexOf(q) >= 0
) {
objectModel.addElement(
objectNames(i)
)
}
i += 1
}
setStatus(
"OBJECT SEARCH COMPLETE"
)
}
// ============================================================
// OBJECT LIST
// ============================================================
val objectNames =
Array(
"CAR",
"SPORTS CAR",
"RACING CAR",
"TRUCK",
"BUS",
"TRAIN",
"AIRPLANE",
"JET",
"HELICOPTER",
"ROCKET",
"BICYCLE",
"MOTORBIKE",
"SCOOTER",
"BOAT",
"SHIP",
"SUBMARINE",
"DRONE",
"UFO",
"HOUSE",
"VILLA",
"PALACE",
"CASTLE",
"TOWER",
"SCHOOL",
"HOSPITAL",
"SHOP",
"LIGHTHOUSE",
"BRIDGE",
"CITY",
"SKYSCRAPER",
"STADIUM",
"SPACE STATION",
"TREE",
"PALM TREE",
"FLOWER",
"CACTUS",
"MUSHROOM",
"MOUNTAIN",
"VOLCANO",
"ISLAND",
"CLOUD",
"RAINBOW",
"SUN",
"MOON",
"PLANET",
"EARTH",
"GALAXY",
"SATURN",
"BOY",
"GIRL",
"MAN",
"WOMAN",
"PERSON",
"ROBOT",
"AI 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",
"DONUT",
"ICE CREAM",
"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",
"GOAL POST",
"CYBER CITY",
"FUTURE CAR",
"FUTURE HOUSE",
"NEON TOWER",
"HOLOGRAM",
"TIME MACHINE"
)
var oi = 0
while (
oi < objectNames.length
) {
objectModel.addElement(
objectNames(oi)
)
oi += 1
}
// ============================================================
// SHAPES
// ============================================================
val shapeNames =
Array(
"LINE",
"SQUARE",
"RECTANGLE",
"ROUNDED RECTANGLE",
"CIRCLE",
"ELLIPSE",
"TRIANGLE",
"DIAMOND",
"PENTAGON",
"HEXAGON",
"HEPTAGON",
"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",
"SNOWFLAKE",
"SMILEY"
)
var shi = 0
while (
shi < shapeNames.length
) {
shapeModel.addElement(
shapeNames(shi)
)
shi += 1
}
// ============================================================
// 2D ANIMATION
// ============================================================
def start2DAnimation(): Unit = {
if (
strokes.isEmpty
) {
setStatus(
"DRAW SOMETHING FIRST"
)
} else {
val preview =
new JFrame(
"EXACT DRAWING ANIMATION"
)
preview.setDefaultCloseOperation(
WindowConstants.DISPOSE_ON_CLOSE
)
preview.setSize(
1100,
750
)
preview.setLocationRelativeTo(
frame
)
val data =
copyStrokes(
strokes
)
var strokeIndex =
0
var pointIndex =
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 /
CANVAS_W,
(
getHeight -
40
).toDouble /
CANVAS_H
)
val ox =
(
getWidth -
CANVAS_W *
scale
) / 2.0
val oy =
(
getHeight -
CANVAS_H *
scale
) / 2.0
val old =
g2.getTransform
g2.translate(
ox,
oy
)
g2.scale(
scale,
scale
)
g2.setColor(
Color.WHITE
)
g2.fillRect(
0,
0,
CANVAS_W,
CANVAS_H
)
g2.setColor(
currentColor
)
g2.setStroke(
new BasicStroke(
brushSize,
BasicStroke.CAP_ROUND,
BasicStroke.JOIN_ROUND
)
)
var i = 0
while (
i < strokeIndex
) {
paintStroke(
g2,
data(i).points
)
i += 1
}
if (
strokeIndex <
data.length
) {
val pts =
data(
strokeIndex
).points
val count =
math.min(
pointIndex,
pts.length
)
if (
count > 0
) {
paintStroke(
g2,
pts.take(
count
)
)
}
}
g2.setTransform(
old
)
}
}
val play =
makeButton(
"PLAY / PAUSE"
)
val reset =
makeButton(
"RESET"
)
val close =
makeButton(
"CLOSE"
)
val timer =
new Timer(
math.max(
5,
animationSpeed
),
new ActionListener {
override def actionPerformed(
e: ActionEvent
): Unit = {
if (
running
) {
if (
strokeIndex <
data.length
) {
pointIndex += 4
if (
pointIndex >=
data(
strokeIndex
).points.length
) {
strokeIndex += 1
pointIndex = 1
}
panel.repaint()
} else {
running =
false
}
}
}
}
)
play.addActionListener(
new ActionListener {
override def actionPerformed(
e: ActionEvent
): Unit = {
running =
!running
}
}
)
reset.addActionListener(
new ActionListener {
override def actionPerformed(
e: ActionEvent
): Unit = {
strokeIndex =
0
pointIndex =
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(
FlowLayout.CENTER
)
)
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()
}
}
// ============================================================
// 3D PREVIEW
// ============================================================
def project3D(
x: Double,
y: Double,
z: Double,
angle: Double,
scale: Double,
ox: Int,
oy: Int
): Point = {
val radians =
angle *
math.Pi /
180.0
val rx =
x *
math.cos(
radians
) -
z *
math.sin(
radians
)
val rz =
x *
math.sin(
radians
) +
z *
math.cos(
radians
)
new Point(
ox +
(
rx *
scale
).toInt,
oy +
(
y *
scale -
rz *
scale *
0.50
).toInt
)
}
def render3D(
g2: Graphics2D,
pts: Seq[Point],
angle: Double,
depth: Double,
scale: Double,
ox: Int,
oy: Int
): Unit = {
if (
pts.length < 2
) {
return
}
val front =
ArrayBuffer[Point]()
val back =
ArrayBuffer[Point]()
var i = 0
while (
i < pts.length
) {
front +=
project3D(
pts(i).x -
CENTER_X,
pts(i).y -
CENTER_Y,
0,
angle,
scale,
ox,
oy
)
back +=
project3D(
pts(i).x -
CENTER_X,
pts(i).y -
CENTER_Y,
depth,
angle,
scale,
ox,
oy
)
i += 1
}
g2.setStroke(
new BasicStroke(
3,
BasicStroke.CAP_ROUND,
BasicStroke.JOIN_ROUND
)
)
g2.setColor(
new Color(
80,
150,
245
)
)
i = 1
while (
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(
150,
190,
255
)
)
i = 1
while (
i < back.length
) {
g2.drawLine(
back(i - 1).x,
back(i - 1).y,
back(i).x,
back(i).y
)
i += 1
}
g2.setColor(
new Color(
40,
90,
170
)
)
i = 0
while (
i < front.length &&
i < back.length
) {
g2.drawLine(
front(i).x,
front(i).y,
back(i).x,
back(i).y
)
i += 1
}
}
def start3DPreview(): Unit = {
if (
strokes.isEmpty
) {
setStatus(
"DRAW OR ADD OBJECT FIRST"
)
} else {
val preview =
new JFrame(
"ULTRA 3D DRAWING MODEL"
)
preview.setDefaultCloseOperation(
WindowConstants.DISPOSE_ON_CLOSE
)
preview.setSize(
1100,
760
)
preview.setLocationRelativeTo(
frame
)
val data =
copyStrokes(
strokes
)
var angle =
0.0
var depth =
80.0
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 w =
getWidth
val h =
getHeight
g2.setPaint(
new GradientPaint(
0,
0,
new Color(
5,
15,
40
),
0,
h,
new Color(
70,
20,
105
)
)
)
g2.fillRect(
0,
0,
w,
h
)
var i = 0
while (
i < data.length
) {
render3D(
g2,
data(i).points,
angle +
i * 7,
depth +
i * 6,
0.70,
w / 2,
h / 2
)
i += 1
}
g2.setColor(
Color.WHITE
)
g2.setFont(
new Font(
"Arial",
Font.BOLD,
18
)
)
g2.drawString(
"3D MODEL PREVIEW",
25,
35
)
g2.setFont(
new Font(
"Arial",
Font.PLAIN,
14
)
)
g2.drawString(
"DEPTH: " +
depth.toInt,
25,
60
)
}
}
val play =
makeButton(
"PLAY / PAUSE"
)
val reset =
makeButton(
"RESET"
)
val depthPlus =
makeButton(
"DEPTH +"
)
val depthMinus =
makeButton(
"DEPTH -"
)
val close =
makeButton(
"CLOSE"
)
val controls =
new JPanel(
new FlowLayout(
FlowLayout.CENTER
)
)
controls.add(
play
)
controls.add(
reset
)
controls.add(
depthPlus
)
controls.add(
depthMinus
)
controls.add(
close
)
val timer =
new Timer(
30,
new ActionListener {
override def actionPerformed(
e: ActionEvent
): Unit = {
if (
running
) {
angle +=
2.0
panel.repaint()
}
}
}
)
play.addActionListener(
new ActionListener {
override def actionPerformed(
e: ActionEvent
): Unit = {
running =
!running
}
}
)
reset.addActionListener(
new ActionListener {
override def actionPerformed(
e: ActionEvent
): Unit = {
angle =
0.0
depth =
80.0
panel.repaint()
}
}
)
depthPlus.addActionListener(
new ActionListener {
override def actionPerformed(
e: ActionEvent
): Unit = {
depth =
math.min(
300.0,
depth + 10.0
)
panel.repaint()
}
}
)
depthMinus.addActionListener(
new ActionListener {
override def actionPerformed(
e: ActionEvent
): Unit = {
depth =
math.max(
10.0,
depth - 10.0
)
panel.repaint()
}
}
)
close.addActionListener(
new ActionListener {
override def actionPerformed(
e: ActionEvent
): Unit = {
timer.stop()
preview.dispose()
}
}
)
preview.setLayout(
new BorderLayout()
)
preview.add(
panel,
BorderLayout.CENTER
)
preview.add(
controls,
BorderLayout.SOUTH
)
preview.setVisible(
true
)
timer.start()
}
}
// ============================================================
// BUTTONS
// ============================================================
pencilButton =
makeButton(
"PENCIL"
)
eraserButton =
makeButton(
"ERASER"
)
undoButton =
makeButton(
"UNDO"
)
redoButton =
makeButton(
"REDO"
)
clearButton =
makeButton(
"CLEAR"
)
generateButton =
makeButton(
"GENERATE CODE"
)
copyButton =
makeButton(
"COPY CODE"
)
saveButton =
makeButton(
"SAVE CODE"
)
animationButton =
makeButton(
"2D ANIMATION"
)
model3DButton =
makeButton(
"3D MODEL"
)
gridButton =
makeButton(
"GRID"
)
snapButton =
makeButton(
"SNAP"
)
colorButton =
makeButton(
"COLOR"
)
// ============================================================
// SEARCH
// ============================================================
searchField =
new JTextField(
12
)
val searchButton =
makeButton(
"SEARCH"
)
// ============================================================
// LISTS
// ============================================================
objectList =
new JList[String](
objectModel
)
objectList.setSelectionMode(
ListSelectionModel.SINGLE_SELECTION
)
objectList.setFont(
new Font(
"Arial",
Font.BOLD,
13
)
)
shapeList =
new JList[String](
shapeModel
)
shapeList.setSelectionMode(
ListSelectionModel.SINGLE_SELECTION
)
shapeList.setFont(
new Font(
"Arial",
Font.BOLD,
13
)
)
// ============================================================
// CODE AREA
// ============================================================
codeArea =
new JTextArea()
codeArea.setEditable(
false
)
codeArea.setFont(
new Font(
"Monospaced",
Font.PLAIN,
13
)
)
codeArea.setLineWrap(
false
)
codeArea.setText(
"// DRAW SOMETHING ON THE CANVAS\n" +
"// THEN PRESS GENERATE CODE"
)
// ============================================================
// SPEED
// ============================================================
speedSlider =
new JSlider(
5,
100,
25
)
speedSlider.setPreferredSize(
new Dimension(
130,
32
)
)
speedSlider.addChangeListener(
new javax.swing.event.ChangeListener {
override def stateChanged(
e: javax.swing.event.ChangeEvent
): Unit = {
animationSpeed =
speedSlider.getValue
}
}
)
// ============================================================
// 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(
saveButton
)
toolbar.add(
animationButton
)
toolbar.add(
model3DButton
)
toolbar.add(
gridButton
)
toolbar.add(
snapButton
)
toolbar.add(
colorButton
)
toolbar.add(
new JLabel(
"SPEED"
)
)
toolbar.add(
speedSlider
)
// ============================================================
// LEFT LIBRARY
// ============================================================
val libraryTabs =
new JTabbedPane()
val objectHeader =
new JPanel(
new BorderLayout()
)
val objectTitle =
new JLabel(
"ALL OBJECTS"
)
objectTitle.setFont(
new Font(
"Arial",
Font.BOLD,
17
)
)
objectHeader.add(
objectTitle,
BorderLayout.NORTH
)
objectHeader.add(
searchField,
BorderLayout.CENTER
)
objectHeader.add(
searchButton,
BorderLayout.EAST
)
val objectPanel =
new JPanel(
new BorderLayout(
4,
4
)
)
objectPanel.add(
objectHeader,
BorderLayout.NORTH
)
objectPanel.add(
new JScrollPane(
objectList
),
BorderLayout.CENTER
)
val shapePanel =
new JPanel(
new BorderLayout(
4,
4
)
)
val shapeTitle =
new JLabel(
"ALL SHAPES"
)
shapeTitle.setFont(
new Font(
"Arial",
Font.BOLD,
17
)
)
shapePanel.add(
shapeTitle,
BorderLayout.NORTH
)
shapePanel.add(
new JScrollPane(
shapeList
),
BorderLayout.CENTER
)
libraryTabs.addTab(
"OBJECTS",
objectPanel
)
libraryTabs.addTab(
"SHAPES",
shapePanel
)
// ============================================================
// CANVAS WRAPPER
// ============================================================
val canvasWrapper =
new JPanel(
new BorderLayout(
4,
4
)
)
val canvasTitle =
new JLabel(
"DRAWING CANVAS ? DRAG DIVIDERS TO RESIZE"
)
canvasTitle.setFont(
new Font(
"Arial",
Font.BOLD,
16
)
)
canvasWrapper.setBorder(
BorderFactory.createEmptyBorder(
5,
5,
5,
5
)
)
canvasWrapper.add(
canvasTitle,
BorderLayout.NORTH
)
canvasWrapper.add(
canvas,
BorderLayout.CENTER
)
// ============================================================
// CODE WRAPPER
// ============================================================
val codeWrapper =
new JPanel(
new BorderLayout(
4,
4
)
)
val codeTitle =
new JLabel(
"EXACT KOJO / SCALA CODE"
)
codeTitle.setFont(
new Font(
"Arial",
Font.BOLD,
16
)
)
codeWrapper.setBorder(
BorderFactory.createEmptyBorder(
5,
5,
5,
5
)
)
codeWrapper.add(
codeTitle,
BorderLayout.NORTH
)
codeWrapper.add(
new JScrollPane(
codeArea
),
BorderLayout.CENTER
)
// ============================================================
// CANVAS <-> CODE MOVABLE DIVIDER
// ============================================================
val canvasCodeSplit =
new JSplitPane(
JSplitPane.HORIZONTAL_SPLIT,
canvasWrapper,
codeWrapper
)
canvasCodeSplit.setDividerLocation(
620
)
canvasCodeSplit.setResizeWeight(
0.60
)
// ============================================================
// LIBRARY <-> MAIN MOVABLE DIVIDER
// ============================================================
val mainSplit =
new JSplitPane(
JSplitPane.HORIZONTAL_SPLIT,
libraryTabs,
canvasCodeSplit
)
mainSplit.setDividerLocation(
260
)
mainSplit.setResizeWeight(
0.0
)
// ============================================================
// STATUS
// ============================================================
status =
new JLabel(
"READY - DRAW ANYTHING"
)
status.setFont(
new Font(
"Arial",
Font.BOLD,
13
)
)
// ============================================================
// FRAME
// ============================================================
frame =
new JFrame(
"ULTRA LEGEND DRAW LAB X"
)
frame.setDefaultCloseOperation(
WindowConstants.EXIT_ON_CLOSE
)
frame.setSize(
1650,
900
)
frame.setMinimumSize(
new Dimension(
1050,
650
)
)
frame.setLocationRelativeTo(
null
)
frame.setLayout(
new BorderLayout(
4,
4
)
)
frame.add(
toolbar,
BorderLayout.NORTH
)
frame.add(
mainSplit,
BorderLayout.CENTER
)
frame.add(
status,
BorderLayout.SOUTH
)
// ============================================================
// BUTTON EVENTS
// ============================================================
pencilButton.addActionListener(
new ActionListener {
override def actionPerformed(
e: ActionEvent
): Unit = {
eraserOn =
false
setStatus(
"PENCIL MODE"
)
canvas.requestFocusInWindow()
}
}
)
eraserButton.addActionListener(
new ActionListener {
override def actionPerformed(
e: ActionEvent
): Unit = {
eraserOn =
true
setStatus(
"ERASER MODE"
)
canvas.requestFocusInWindow()
}
}
)
undoButton.addActionListener(
new ActionListener {
override def actionPerformed(
e: ActionEvent
): Unit = {
doUndo()
canvas.requestFocusInWindow()
}
}
)
redoButton.addActionListener(
new ActionListener {
override def actionPerformed(
e: ActionEvent
): Unit = {
doRedo()
canvas.requestFocusInWindow()
}
}
)
clearButton.addActionListener(
new ActionListener {
override def actionPerformed(
e: ActionEvent
): Unit = {
clearDrawing()
canvas.requestFocusInWindow()
}
}
)
generateButton.addActionListener(
new ActionListener {
override def actionPerformed(
e: ActionEvent
): Unit = {
generateKojoCode()
canvas.requestFocusInWindow()
}
}
)
copyButton.addActionListener(
new ActionListener {
override def actionPerformed(
e: ActionEvent
): Unit = {
copyCode()
canvas.requestFocusInWindow()
}
}
)
saveButton.addActionListener(
new ActionListener {
override def actionPerformed(
e: ActionEvent
): Unit = {
saveCode()
}
}
)
animationButton.addActionListener(
new ActionListener {
override def actionPerformed(
e: ActionEvent
): Unit = {
start2DAnimation()
}
}
)
model3DButton.addActionListener(
new ActionListener {
override def actionPerformed(
e: ActionEvent
): Unit = {
start3DPreview()
}
}
)
gridButton.addActionListener(
new ActionListener {
override def actionPerformed(
e: ActionEvent
): Unit = {
gridOn =
!gridOn
repaintCanvas()
setStatus(
if (
gridOn
)
"GRID ON"
else
"GRID OFF"
)
canvas.requestFocusInWindow()
}
}
)
snapButton.addActionListener(
new ActionListener {
override def actionPerformed(
e: ActionEvent
): Unit = {
snapOn =
!snapOn
setStatus(
if (
snapOn
)
"SNAP ON"
else
"SNAP OFF"
)
canvas.requestFocusInWindow()
}
}
)
colorButton.addActionListener(
new ActionListener {
override def actionPerformed(
e: ActionEvent
): Unit = {
val selected =
JColorChooser.showDialog(
frame,
"CHOOSE DRAW COLOR",
currentColor
)
if (
selected != null
) {
currentColor =
selected
repaintCanvas()
setStatus(
"COLOR CHANGED"
)
}
canvas.requestFocusInWindow()
}
}
)
// ============================================================
// SEARCH EVENTS
// ============================================================
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
)
canvas.requestFocusInWindow()
}
}
}
}
)
// ============================================================
// SHAPE DOUBLE CLICK
// ============================================================
shapeList.addMouseListener(
new MouseAdapter {
override def mouseClicked(
e: MouseEvent
): Unit = {
if (
e.getClickCount == 2
) {
val value =
shapeList.getSelectedValue
if (
value != null
) {
addShape(
value
)
canvas.requestFocusInWindow()
}
}
}
}
)
// ============================================================
// MOUSE DRAWING
// ============================================================
canvas.addMouseListener(
new MouseAdapter {
override def mousePressed(
e: MouseEvent
): Unit = {
if (
e.getButton ==
MouseEvent.BUTTON1
) {
remember()
activeStroke.clear()
activeStroke +=
mousePoint(
e
)
drawingNow =
true
setStatus(
"DRAWING..."
)
repaintCanvas()
}
}
override def mouseReleased(
e: MouseEvent
): Unit = {
if (
drawingNow
) {
activeStroke +=
mousePoint(
e
)
if (
eraserOn
) {
val hit =
activeStroke.last
val remaining =
ArrayBuffer[DrawStroke]()
var i = 0
while (
i < strokes.length
) {
val stroke =
strokes(i)
var remove =
false
var j = 0
while (
j < stroke.points.length &&
!remove
) {
if (
distance(
hit,
stroke.points(j)
) < 35
) {
remove =
true
}
j += 1
}
if (
!remove
) {
remaining +=
stroke
}
i += 1
}
strokes.clear()
strokes ++=
remaining
} else {
if (
activeStroke.length >= 2
) {
strokes +=
DrawStroke(
activeStroke.toVector
)
}
}
activeStroke.clear()
drawingNow =
false
repaintCanvas()
setStatus(
"STROKE RECORDED"
)
canvas.requestFocusInWindow()
}
}
}
)
// ============================================================
// MOUSE DRAGGING
// ============================================================
canvas.addMouseMotionListener(
new MouseMotionAdapter {
override def mouseDragged(
e: MouseEvent
): Unit = {
if (
drawingNow
) {
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 k =
e.getKeyCode
if (
e.isControlDown &&
k == KeyEvent.VK_Z
) {
doUndo()
} else if (
e.isControlDown &&
k == KeyEvent.VK_Y
) {
doRedo()
} else if (
e.isControlDown &&
k == KeyEvent.VK_G
) {
generateKojoCode()
} else if (
e.isControlDown &&
k == KeyEvent.VK_C
) {
copyCode()
} else if (
e.isControlDown &&
k == KeyEvent.VK_S
) {
saveCode()
} else if (
e.isControlDown &&
k == KeyEvent.VK_A
) {
start2DAnimation()
} else if (
e.isControlDown &&
k == KeyEvent.VK_3
) {
start3DPreview()
} else if (
k == KeyEvent.VK_DELETE
) {
clearDrawing()
} else if (
k == KeyEvent.VK_E
) {
eraserOn =
!eraserOn
setStatus(
if (
eraserOn
)
"ERASER ON"
else
"PENCIL ON"
)
} else if (
k == 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" +
"CTRL + 3 = 3D MODEL\n" +
"DELETE = CLEAR\n" +
"E = ERASER / PENCIL\n" +
"F1 = THIS HELP\n\n" +
"OBJECTS: DOUBLE CLICK\n" +
"SHAPES: DOUBLE CLICK\n" +
"DRAG DIVIDERS TO RESIZE PANELS",
"SECRET CONTROL CENTER",
JOptionPane.INFORMATION_MESSAGE
)
}
}
}
)
// ============================================================
// START
// ============================================================
SwingUtilities.invokeLater(
new Runnable {
override def run(): Unit = {
frame.setVisible(
true
)
canvas.requestFocusInWindow()
setStatus(
"ULTRA LEGEND X READY"
)
}
}
)