Code Sketch
yoiiii`11
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 java.awt.geom.Path2D
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 X3
// ============================================================
// DRAW
// SELECT
// MOVE
// RESIZE
// ERASER
// BUCKET FILL
// SHAPES
// OBJECTS
// EXACT GEOMETRY
// KOJO CODE
// 2D ANIMATION
// 3D STYLE PREVIEW
// COLORS
// GRID
// SNAP
// UNDO
// REDO
// COPY
// SAVE
// MOVABLE PANELS
// SHORTCUTS
// ============================================================
// ============================================================
// CANVAS
// ============================================================
val CW = 1000
val CH = 650
val CX = CW / 2
val CY = CH / 2
// ============================================================
// DRAW ITEM
// ============================================================
class DrawItem(
val points: Vector[Point],
var strokeColor: Color,
var fillColor: Color,
var filled: Boolean,
val itemName: String
)
// ============================================================
// STATE
// ============================================================
var items =
ArrayBuffer[DrawItem]()
var undoHistory =
ArrayBuffer[
ArrayBuffer[DrawItem]
]()
var redoHistory =
ArrayBuffer[
ArrayBuffer[DrawItem]
]()
var activePoints =
ArrayBuffer[Point]()
var drawingNow =
false
var currentTool =
"PENCIL"
var currentColor =
new Color(
25,
75,
190
)
var currentFillColor =
new Color(
255,
175,
40
)
var gridOn =
true
var snapOn =
false
var brushSize =
5.0f
var selectedIndex =
-1
var movingSelection =
false
var resizingSelection =
false
var resizeHandle =
""
var dragStart =
new Point(
0,
0
)
var originalSelectedPoints =
Vector[Point]()
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]()
// ============================================================
// BUTTONS
// ============================================================
var pencilButton: JButton =
null
var selectButton: JButton =
null
var eraserButton: JButton =
null
var bucketButton: 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 strokeColorButton: JButton =
null
var fillColorButton: 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
// ============================================================
def setStatus(
text: String
): Unit = {
if (
status != null
) {
//
status.setText(
text
)
//
}
}
def repaintCanvas(): Unit = {
if (
canvas != null
) {
//
canvas.repaint()
//
}
}
// ============================================================
// COPY ITEM LIST
// ============================================================
def copyItems(
source: ArrayBuffer[DrawItem]
): ArrayBuffer[DrawItem] = {
val result =
ArrayBuffer[DrawItem]()
var i =
0
while (
i < source.length
) {
//
val copiedPoints =
source(i).points.map(
p =>
new Point(
p.x,
p.y
)
)
result +=
new DrawItem(
copiedPoints,
source(i).strokeColor,
source(i).fillColor,
source(i).filled,
source(i).itemName
)
i += 1
//
}
result
}
// ============================================================
// HISTORY
// ============================================================
def remember(): Unit = {
undoHistory +=
copyItems(
items
)
redoHistory.clear()
while (
undoHistory.length > 40
) {
//
undoHistory.remove(
0
)
//
}
}
def doUndo(): Unit = {
if (
undoHistory.nonEmpty
) {
//
redoHistory +=
copyItems(
items
)
val last =
undoHistory(
undoHistory.length - 1
)
undoHistory.remove(
undoHistory.length - 1
)
items.clear()
items ++=
copyItems(
last
)
selectedIndex =
-1
repaintCanvas()
setStatus(
"UNDO COMPLETE"
)
//
} else {
//
setStatus(
"NOTHING TO UNDO"
)
//
}
}
def doRedo(): Unit = {
if (
redoHistory.nonEmpty
) {
//
undoHistory +=
copyItems(
items
)
val next =
redoHistory(
redoHistory.length - 1
)
redoHistory.remove(
redoHistory.length - 1
)
items.clear()
items ++=
copyItems(
next
)
selectedIndex =
-1
repaintCanvas()
setStatus(
"REDO COMPLETE"
)
//
} else {
//
setStatus(
"NOTHING TO REDO"
)
//
}
}
// ============================================================
// SNAP
// ============================================================
def snapValue(
n: Int
): Int = {
if (
snapOn
) {
//
math.max(
0,
math.min(
CW - 1,
(n / 10) * 10
)
)
//
} else {
//
n
//
}
}
// ============================================================
// 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(
snapValue(x),
snapValue(y)
)
}
// ============================================================
// DISTANCE
// ============================================================
def distance(
a: Point,
b: Point
): Double = {
math.hypot(
b.x - a.x,
b.y - a.y
)
}
// ============================================================
// BOUNDS
// ============================================================
def itemBounds(
item: DrawItem
): java.awt.Rectangle = {
if (
item.points.isEmpty
) {
//
new java.awt.Rectangle(
0,
0,
1,
1
)
//
} else {
//
var minX =
item.points.head.x
var maxX =
item.points.head.x
var minY =
item.points.head.y
var maxY =
item.points.head.y
var i =
1
while (
i < item.points.length
) {
val p =
item.points(i)
if (
p.x < minX
) {
minX =
p.x
}
if (
p.x > maxX
) {
maxX =
p.x
}
if (
p.y < minY
) {
minY =
p.y
}
if (
p.y > maxY
) {
maxY =
p.y
}
i += 1
}
new java.awt.Rectangle(
minX,
minY,
math.max(
1,
maxX - minX
),
math.max(
1,
maxY - minY
)
)
//
}
}
// ============================================================
// CLOSED
// ============================================================
def isClosed(
pts: Seq[Point]
): Boolean = {
pts.length >= 3 &&
distance(
pts.head,
pts.last
) <= 45
}
// ============================================================
// POINT IN POLYGON
// ============================================================
def pointInPolygon(
p: Point,
pts: Seq[Point]
): Boolean = {
if (
pts.length < 3
) {
//
false
//
} else {
//
var inside =
false
var j =
pts.length - 1
var i =
0
while (
i < pts.length
) {
val xi =
pts(i).x
val yi =
pts(i).y
val xj =
pts(j).x
val yj =
pts(j).y
val intersect =
(
(yi > p.y) !=
(yj > p.y)
) &&
(
p.x <
(
xj - xi
).toDouble *
(
p.y - yi
).toDouble /
(
yj - yi +
0.000001
) +
xi
)
if (
intersect
) {
inside =
!inside
}
j =
i
i += 1
}
inside
//
}
}
// ============================================================
// POINT TO SEGMENT
// ============================================================
def pointSegmentDistance(
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
)
//
}
}
// ============================================================
// FIND ITEM
// ============================================================
def findItemAt(
p: Point
): Int = {
var result =
-1
var i =
items.length - 1
while (
i >= 0 &&
result == -1
) {
//
val item =
items(i)
val bounds =
itemBounds(
item
)
val expanded =
new java.awt.Rectangle(
bounds.x - 15,
bounds.y - 15,
bounds.width + 30,
bounds.height + 30
)
if (
expanded.contains(
p
)
) {
if (
item.filled &&
pointInPolygon(
p,
item.points
)
) {
result =
i
} else {
var j =
1
while (
j < item.points.length &&
result == -1
) {
if (
pointSegmentDistance(
p,
item.points(j - 1),
item.points(j)
) < 18
) {
result =
i
}
j += 1
}
}
}
i -= 1
//
}
result
}
// ============================================================
// HANDLE POINTS
// ============================================================
def handlePoints(
r: java.awt.Rectangle
): Array[(String,Point)] = {
val midX =
r.x +
r.width / 2
val midY =
r.y +
r.height / 2
Array(
("TL", new Point(r.x, r.y)),
("TM", new Point(midX, r.y)),
("TR", new Point(r.x + r.width, r.y)),
("ML", new Point(r.x, midY)),
("MR", new Point(r.x + r.width, midY)),
("BL", new Point(r.x, r.y + r.height)),
("BM", new Point(midX, r.y + r.height)),
("BR", new Point(r.x + r.width, r.y + r.height))
)
}
def findHandle(
item: DrawItem,
p: Point
): String = {
val r =
itemBounds(
item
)
val box =
new java.awt.Rectangle(
r.x - 8,
r.y - 8,
r.width + 16,
r.height + 16
)
val handles =
handlePoints(
box
)
var found =
""
var i =
0
while (
i < handles.length &&
found == ""
) {
//
if (
distance(
p,
handles(i)._2
) <= 13
) {
found =
handles(i)._1
}
i += 1
//
}
found
}
// ============================================================
// GRID
// ============================================================
def paintGrid(
g2: Graphics2D
): Unit = {
if (
gridOn
) {
//
g2.setColor(
new Color(
0,
0,
0,
25
)
)
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,
100,
220,
60
)
)
g2.drawLine(
CX,
0,
CX,
CH
)
g2.drawLine(
0,
CY,
CW,
CY
)
//
}
}
// ============================================================
// PAINT ITEM
// ============================================================
def paintItem(
g2: Graphics2D,
item: DrawItem
): Unit = {
if (
item.points.length >= 2
) {
//
g2.setStroke(
new BasicStroke(
brushSize,
BasicStroke.CAP_ROUND,
BasicStroke.JOIN_ROUND
)
)
if (
item.filled &&
item.points.length >= 3
) {
val path =
new Path2D.Double()
path.moveTo(
item.points.head.x,
item.points.head.y
)
var i =
1
while (
i < item.points.length
) {
path.lineTo(
item.points(i).x,
item.points(i).y
)
i += 1
}
path.closePath()
g2.setColor(
item.fillColor
)
g2.fill(
path
)
}
g2.setColor(
item.strokeColor
)
var i =
1
while (
i < item.points.length
) {
g2.drawLine(
item.points(i - 1).x,
item.points(i - 1).y,
item.points(i).x,
item.points(i).y
)
i += 1
}
if (
item.filled &&
isClosed(
item.points
)
) {
g2.drawLine(
item.points.last.x,
item.points.last.y,
item.points.head.x,
item.points.head.y
)
}
//
}
}
// ============================================================
// SELECTION
// ============================================================
def paintSelection(
g2: Graphics2D,
item: DrawItem
): Unit = {
val b =
itemBounds(
item
)
val r =
new java.awt.Rectangle(
b.x - 8,
b.y - 8,
b.width + 16,
b.height + 16
)
g2.setColor(
new Color(
0,
110,
235
)
)
g2.setStroke(
new BasicStroke(
2
)
)
g2.drawRect(
r.x,
r.y,
r.width,
r.height
)
val handles =
handlePoints(
r
)
var i =
0
while (
i < handles.length
) {
//
val p =
handles(i)._2
g2.setColor(
Color.WHITE
)
g2.fillRect(
p.x - 5,
p.y - 5,
10,
10
)
g2.setColor(
new Color(
0,
110,
235
)
)
g2.drawRect(
p.x - 5,
p.y - 5,
10,
10
)
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
)
paintGrid(
g2
)
var i =
0
while (
i < items.length
) {
paintItem(
g2,
items(i)
)
i += 1
}
if (
activePoints.nonEmpty
) {
val temp =
new DrawItem(
activePoints.toVector,
currentColor,
currentFillColor,
false,
"CURRENT"
)
paintItem(
g2,
temp
)
}
if (
selectedIndex >= 0 &&
selectedIndex < items.length &&
currentTool == "SELECT"
) {
paintSelection(
g2,
items(
selectedIndex
)
)
}
}
//
}
// ============================================================
// MOVE SELECTED
// ============================================================
def moveSelected(
dx: Int,
dy: Int
): Unit = {
if (
selectedIndex >= 0 &&
selectedIndex < items.length
) {
//
val old =
items(
selectedIndex
)
val moved =
old.points.map(
p =>
new Point(
p.x + dx,
p.y + dy
)
)
items(
selectedIndex
) =
new DrawItem(
moved,
old.strokeColor,
old.fillColor,
old.filled,
old.itemName
)
//
}
}
// ============================================================
// RESIZE SELECTED
// ============================================================
def resizeSelected(
mouse: Point
): Unit = {
if (
selectedIndex >= 0 &&
selectedIndex < items.length &&
originalSelectedPoints.nonEmpty
) {
//
val original =
originalSelectedPoints
var minX =
original.head.x
var maxX =
original.head.x
var minY =
original.head.y
var maxY =
original.head.y
var i =
1
while (
i < original.length
) {
val p =
original(i)
if (
p.x < minX
) minX = p.x
if (
p.x > maxX
) maxX = p.x
if (
p.y < minY
) minY = p.y
if (
p.y > maxY
) maxY = p.y
i += 1
}
val oldW =
math.max(
1,
maxX - minX
)
val oldH =
math.max(
1,
maxY - minY
)
var newMinX =
minX
var newMaxX =
maxX
var newMinY =
minY
var newMaxY =
maxY
if (
resizeHandle == "TL"
) {
newMinX = mouse.x
newMinY = mouse.y
} else if (
resizeHandle == "TR"
) {
newMaxX = mouse.x
newMinY = mouse.y
} else if (
resizeHandle == "BL"
) {
newMinX = mouse.x
newMaxY = mouse.y
} else if (
resizeHandle == "BR"
) {
newMaxX = mouse.x
newMaxY = mouse.y
} else if (
resizeHandle == "TM"
) {
newMinY = mouse.y
} else if (
resizeHandle == "BM"
) {
newMaxY = mouse.y
} else if (
resizeHandle == "ML"
) {
newMinX = mouse.x
} else if (
resizeHandle == "MR"
) {
newMaxX = mouse.x
}
val newW =
math.max(
5,
newMaxX - newMinX
)
val newH =
math.max(
5,
newMaxY - newMinY
)
val current =
items(
selectedIndex
)
val scaled =
original.map(
p => {
val nx =
newMinX +
(
(
p.x - minX
).toDouble *
newW /
oldW
).toInt
val ny =
newMinY +
(
(
p.y - minY
).toDouble *
newH /
oldH
).toInt
new Point(
nx,
ny
)
}
)
items(
selectedIndex
) =
new DrawItem(
scaled,
current.strokeColor,
current.fillColor,
current.filled,
current.itemName
)
//
}
}
// ============================================================
// BUCKET
// ============================================================
def applyBucket(): Unit = {
if (
selectedIndex >= 0 &&
selectedIndex < items.length
) {
//
val old =
items(
selectedIndex
)
if (
isClosed(
old.points
)
) {
remember()
items(
selectedIndex
) =
new DrawItem(
old.points,
old.strokeColor,
currentFillColor,
true,
old.itemName
)
repaintCanvas()
setStatus(
"BUCKET FILL COMPLETE"
)
} else {
setStatus(
"CLOSED SHAPE REQUIRED"
)
}
//
} else {
//
setStatus(
"SELECT AN OBJECT FIRST"
)
//
}
}
// ============================================================
// POLYGON
// ============================================================
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(
CX +
(
math.cos(a) *
radius
).toInt,
CY +
(
math.sin(a) *
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 a =
-math.Pi / 2.0 +
i *
math.Pi /
points
val r =
if (
i % 2 == 0
)
radius
else
(
radius *
0.45
).toInt
out +=
new Point(
CX +
(
math.cos(a) *
r
).toInt,
CY +
(
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(
CX +
(
x * 13
).toInt,
CY -
(
y * 13
).toInt
)
i += 1
//
}
out.toVector
}
// ============================================================
// ELLIPSE
// ============================================================
def ellipsePoints(
rx: Int,
ry: Int
): Vector[Point] = {
val out =
ArrayBuffer[Point]()
var i =
0
while (
i <= 360
) {
//
val a =
i *
math.Pi /
180.0
out +=
new Point(
CX +
(
math.cos(a) *
rx
).toInt,
CY +
(
math.sin(a) *
ry
).toInt
)
i += 3
//
}
out.toVector
}
// ============================================================
// 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,180),
new Point(700,180),
new Point(700,580),
new Point(300,580),
new Point(300,180)
)
//
} else if (
n == "RECTANGLE"
) {
//
Vector(
new Point(170,220),
new Point(830,220),
new Point(830,480),
new Point(170,480),
new Point(170,220)
)
//
} 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"
) {
//
ellipsePoints(
220,
220
)
//
} else if (
n == "ELLIPSE"
) {
//
ellipsePoints(
300,
160
)
//
} else if (
n == "TRIANGLE"
) {
//
polygonPoints(
3,
250,
-math.Pi / 2
)
//
} else if (
n == "DIAMOND"
) {
//
Vector(
new Point(CX,100),
new Point(820,CY),
new Point(CX,550),
new Point(180,CY),
new Point(CX,100)
)
//
} else if (
n == "PENTAGON"
) {
//
polygonPoints(
5,
250,
-math.Pi / 2
)
//
} else if (
n == "HEXAGON"
) {
//
polygonPoints(
6,
245,
0
)
//
} else if (
n == "HEPTAGON"
) {
//
polygonPoints(
7,
245,
-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,250),
new Point(620,250),
new Point(620,150),
new Point(850,325),
new Point(620,500),
new Point(620,400),
new Point(150,400),
new Point(150,250)
)
//
} else if (
n == "ARROW LEFT"
) {
//
Vector(
new Point(850,250),
new Point(380,250),
new Point(380,150),
new Point(150,325),
new Point(380,500),
new Point(380,400),
new Point(850,400),
new Point(850,250)
)
//
} 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,190),
new Point(500,410),
new Point(750,190),
new Point(750,315),
new Point(500,540),
new Point(250,315),
new Point(250,190)
)
//
} 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)
)
//
} else if (
n == "X"
) {
//
Vector(
new Point(240,140),
new Point(760,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(220,440),
new Point(270,330),
new Point(370,260),
new Point(470,285),
new Point(555,190),
new Point(680,220),
new Point(760,320),
new Point(830,360),
new Point(800,470),
new Point(220,470),
new Point(220,440)
)
//
} else if (
n == "SUN"
) {
//
starPoints(
16,
250
)
//
} else if (
n == "MOON"
) {
//
Vector(
new Point(400,140),
new Point(620,150),
new Point(760,320),
new Point(620,500),
new Point(400,500),
new Point(520,410),
new Point(580,320),
new Point(520,220),
new Point(400,140)
)
//
} else if (
n == "CRESCENT"
) {
//
Vector(
new Point(460,130),
new Point(630,160),
new Point(740,320),
new Point(630,490),
new Point(460,520),
new Point(540,420),
new Point(590,320),
new Point(540,220),
new Point(460,130)
)
//
} else if (
n == "RING"
) {
//
ellipsePoints(
230,
230
)
//
} else if (
n == "ARC"
) {
//
val out =
ArrayBuffer[Point]()
var deg =
-110
while (
deg <= 110
) {
val a =
deg *
math.Pi /
180.0
out +=
new Point(
CX +
(
math.cos(a) *
280
).toInt,
CY +
(
math.sin(a) *
210
).toInt
)
deg += 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(
CX +
(
math.cos(a) *
250
).toInt,
CY +
(
math.sin(a) *
180
).toInt
)
i += 3
}
out.toVector
//
} else if (
n == "WAVE"
) {
//
val out =
ArrayBuffer[Point]()
var x =
70
while (
x <= 930
) {
val y =
CY +
(
math.sin(
(
x - 70
) *
0.04
) *
110
).toInt
out +=
new Point(
x,
y
)
x += 5
}
out.toVector
//
} else if (
n == "SPIRAL"
) {
//
val out =
ArrayBuffer[Point]()
var a =
0.0
var r =
4.0
while (
a <
math.Pi * 8
) {
out +=
new Point(
CX +
(
math.cos(a) *
r
).toInt,
CY +
(
math.sin(a) *
r
).toInt
)
a += 0.08
r += 1.7
}
out.toVector
//
} else if (
n == "BURST"
) {
//
starPoints(
20,
260
)
//
} else if (
n == "SNOWFLAKE"
) {
//
Vector(
new Point(CX,90),
new Point(CX,560),
new Point(260,170),
new Point(740,480),
new Point(260,480),
new Point(740,170)
)
//
} else if (
n == "SMILEY"
) {
//
ellipsePoints(
230,
230
)
//
} 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(140,470),
new Point(220,370),
new Point(380,365),
new Point(470,245),
new Point(670,245),
new Point(780,365),
new Point(890,370),
new Point(940,470),
new Point(140,470)
)
//
} else if (
n == "HOUSE" ||
n == "VILLA" ||
n == "PALACE" ||
n == "CASTLE"
) {
//
Vector(
new Point(240,540),
new Point(240,300),
new Point(500,80),
new Point(760,300),
new Point(760,540),
new Point(240,540)
)
//
} else if (
n == "TREE" ||
n == "PALM TREE"
) {
//
Vector(
new Point(455,575),
new Point(545,575),
new Point(545,345),
new Point(680,270),
new Point(600,155),
new Point(500,220),
new Point(400,155),
new Point(320,270),
new Point(455,345),
new Point(455,575)
)
//
} else if (
n == "ROCKET"
) {
//
Vector(
new Point(430,520),
new Point(450,220),
new Point(500,70),
new Point(550,220),
new Point(570,520),
new Point(500,590),
new Point(430,520)
)
//
} else if (
n == "ROBOT" ||
n == "AI ROBOT"
) {
//
Vector(
new Point(370,540),
new Point(370,300),
new Point(345,300),
new Point(345,150),
new Point(655,150),
new Point(655,300),
new Point(630,300),
new Point(630,540),
new Point(370,540)
)
//
} else if (
n == "FISH" ||
n == "SHARK" ||
n == "WHALE" ||
n == "DOLPHIN"
) {
//
Vector(
new Point(160,330),
new Point(330,220),
new Point(650,220),
new Point(830,330),
new Point(650,440),
new Point(330,440),
new Point(160,330)
)
//
} else if (
n == "BIRD"
) {
//
Vector(
new Point(220,350),
new Point(400,220),
new Point(500,300),
new Point(630,180),
new Point(780,320),
new Point(630,410),
new Point(460,395),
new Point(330,470),
new Point(220,350)
)
//
} else if (
n == "FLOWER"
) {
//
Vector(
new Point(500,570),
new Point(500,315),
new Point(390,230),
new Point(500,130),
new Point(610,230),
new Point(500,315),
new Point(500,570)
)
//
} else if (
n == "MOUNTAIN"
) {
//
Vector(
new Point(60,545),
new Point(290,220),
new Point(430,400),
new Point(620,90),
new Point(940,545),
new Point(60,545)
)
//
} else if (
n == "STAR"
) {
//
starPoints(
5,
260
)
//
} else if (
n == "HEART"
) {
//
heartPoints()
//
} else if (
n == "CROWN"
) {
//
Vector(
new Point(250,480),
new Point(210,170),
new Point(390,300),
new Point(500,110),
new Point(610,300),
new Point(790,170),
new Point(750,480),
new Point(250,480)
)
//
} else if (
n == "PLANET" ||
n == "EARTH" ||
n == "SATURN"
) {
//
ellipsePoints(
225,
225
)
//
} else {
//
polygonPoints(
6,
230,
0
)
//
}
}
// ============================================================
// 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 s0 =
0
while (
s0 < shapeNames.length
) {
shapeModel.addElement(
shapeNames(s0)
)
s0 += 1
}
// ============================================================
// OBJECT LIBRARY
// ============================================================
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",
"CYBER CITY",
"FUTURE CAR",
"FUTURE HOUSE",
"NEON TOWER",
"HOLOGRAM",
"TIME MACHINE"
//
)
var o0 =
0
while (
o0 < objectNames.length
) {
objectModel.addElement(
objectNames(o0)
)
o0 += 1
}
// ============================================================
// ADD SHAPE
// ============================================================
def addShape(
name: String
): Unit = {
remember()
val pts =
shapeGeometry(
name
)
items +=
new DrawItem(
pts,
currentColor,
currentFillColor,
isClosed(pts),
name
)
selectedIndex =
items.length - 1
currentTool =
"SELECT"
repaintCanvas()
setStatus(
"SHAPE ADDED: " +
name
)
}
// ============================================================
// ADD OBJECT
// ============================================================
def addObject(
name: String
): Unit = {
remember()
val pts =
objectGeometry(
name
)
items +=
new DrawItem(
pts,
currentColor,
currentFillColor,
isClosed(pts),
name
)
selectedIndex =
items.length - 1
currentTool =
"SELECT"
repaintCanvas()
setStatus(
"OBJECT ADDED: " +
name
)
}
// ============================================================
// SEARCH
// ============================================================
def searchObjects(): Unit = {
val query =
searchField
.getText
.trim
.toLowerCase
objectModel.clear()
var i =
0
while (
i < objectNames.length
) {
//
if (
query.isEmpty ||
objectNames(i)
.toLowerCase
.indexOf(
query
) >= 0
) {
objectModel.addElement(
objectNames(i)
)
}
i += 1
//
}
setStatus(
"SEARCH COMPLETE"
)
}
// ============================================================
// KOJO NUMBER
// ============================================================
def fmt(
d: Double
): String = {
if (
math.abs(
d -
d.round
) < 0.04
) {
//
d.round.toString
//
} else {
//
"%.2f".format(
d
)
//
}
}
// ============================================================
// KOJO COLOR
// ============================================================
def kojoColor(
c: Color
): String = {
val r =
c.getRed
val g =
c.getGreen
val b =
c.getBlue
if (
r > 220 &&
g < 90 &&
b < 90
) {
//
"red"
//
} else if (
r < 90 &&
g > 170 &&
b < 120
) {
//
"green"
//
} else if (
r < 100 &&
g < 150 &&
b > 170
) {
//
"blue"
//
} else if (
r > 210 &&
g > 175 &&
b < 110
) {
//
"yellow"
//
} else if (
r > 220 &&
g > 100 &&
b < 80
) {
//
"orange"
//
} else if (
r > 170 &&
b > 150
) {
//
"magenta"
//
} else {
//
"black"
//
}
}
// ============================================================
// KOJO COORDINATES
// ============================================================
def kx(
x: Int
): Int = {
x - CX
}
def ky(
y: Int
): Int = {
CY - y
}
// ============================================================
// EXACT ITEM CODE
// ============================================================
def exactItemCode(
item: DrawItem,
number: Int
): String = {
if (
item.points.isEmpty
) {
//
""
//
} else {
//
val sb =
new StringBuilder()
sb.append(
"// ITEM " +
number +
": " +
item.itemName +
"\n"
)
sb.append(
"setPenColor(" +
kojoColor(
item.strokeColor
) +
")\n"
)
sb.append(
"setPenThickness(" +
brushSize.toInt +
")\n"
)
if (
item.filled &&
item.points.length >= 3 &&
isClosed(item.points)
) {
sb.append(
"setFillColor(" +
kojoColor(
item.fillColor
) +
")\n"
)
sb.append(
"beginShape()\n"
)
var i =
0
while (
i < item.points.length
) {
sb.append(
"vertex(" +
kx(
item.points(i).x
) +
", " +
ky(
item.points(i).y
) +
")\n"
)
i += 1
}
sb.append(
"endShape()\n"
)
} else {
sb.append(
"penUp()\n"
)
sb.append(
"setPosition(" +
kx(
item.points.head.x
) +
", " +
ky(
item.points.head.y
) +
")\n"
)
sb.append(
"penDown()\n"
)
var i =
1
while (
i < item.points.length
) {
sb.append(
"lineTo(" +
kx(
item.points(i).x
) +
", " +
ky(
item.points(i).y
) +
")\n"
)
i += 1
}
}
sb.toString
//
}
}
// ============================================================
// GENERATE KOJO CODE
// ============================================================
def generateKojoCode(): Unit = {
if (
items.isEmpty
) {
//
codeArea.setText(
"// NOTHING DRAWN YET"
)
setStatus(
"DRAW SOMETHING FIRST"
)
//
} else {
//
val sb =
new StringBuilder()
sb.append(
"// ==================================================\n"
)
sb.append(
"// ULTRA LEGEND EXACT DRAWING -> KOJO\n"
)
sb.append(
"// STORED GEOMETRY + COLORS + FILL\n"
)
sb.append(
"// ==================================================\n\n"
)
sb.append(
"cleari()\n"
)
sb.append(
"setAnimationDelay(5)\n\n"
)
var i =
0
while (
i < items.length
) {
sb.append(
exactItemCode(
items(i),
i + 1
)
)
sb.append(
"\n"
)
i += 1
}
sb.append(
"// ==================================================\n"
)
sb.append(
"// END OF GENERATED DRAWING\n"
)
sb.append(
"// ==================================================\n"
)
generatedCode =
sb.toString
codeArea.setText(
generatedCode
)
codeArea.setCaretPosition(
0
)
setStatus(
"EXACT KOJO CODE GENERATED"
)
//
}
}
// ============================================================
// COPY
// ============================================================
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
// ============================================================
def saveCode(): Unit = {
if (
generatedCode.trim.isEmpty
) {
//
generateKojoCode()
//
}
val chooser =
new JFileChooser()
chooser.setDialogTitle(
"SAVE KOJO CODE"
)
if (
chooser.showSaveDialog(
frame
) ==
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
// ============================================================
def clearAll(): Unit = {
remember()
items.clear()
activePoints.clear()
selectedIndex =
-1
generatedCode =
""
codeArea.setText(
"// CANVAS CLEARED"
)
repaintCanvas()
setStatus(
"CANVAS CLEARED"
)
}
// ============================================================
// 2D ANIMATION
// ============================================================
def start2DAnimation(): Unit = {
if (
items.isEmpty
) {
//
setStatus(
"DRAW SOMETHING FIRST"
)
//
} else {
//
val preview =
new JFrame(
"EXACT DRAWING ANIMATION"
)
preview.setDefaultCloseOperation(
WindowConstants.DISPOSE_ON_CLOSE
)
preview.setSize(
1100,
760
)
preview.setLocationRelativeTo(
frame
)
val data =
copyItems(
items
)
var itemIndex =
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 /
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
)
var i =
0
while (
i < itemIndex
) {
paintItem(
g2,
data(i)
)
i += 1
}
if (
itemIndex <
data.length
) {
val original =
data(
itemIndex
)
val n =
math.min(
pointIndex,
original.points.length
)
val partial =
new DrawItem(
original.points.take(n),
original.strokeColor,
original.fillColor,
false,
original.itemName
)
if (
n > 0
) {
paintItem(
g2,
partial
)
}
}
g2.setTransform(
old
)
}
}
val playButton =
makeButton(
"PLAY / PAUSE"
)
val resetButton =
makeButton(
"RESET"
)
val closeButton =
makeButton(
"CLOSE"
)
val controls =
new JPanel(
new FlowLayout()
)
controls.add(
playButton
)
controls.add(
resetButton
)
controls.add(
closeButton
)
val timer =
new Timer(
math.max(
5,
animationSpeed
),
new ActionListener {
override def actionPerformed(
e: ActionEvent
): Unit = {
if (
running
) {
if (
itemIndex <
data.length
) {
pointIndex +=
4
if (
pointIndex >=
data(
itemIndex
).points.length
) {
itemIndex +=
1
pointIndex =
1
}
panel.repaint()
} else {
running =
false
}
}
}
}
)
playButton.addActionListener(
new ActionListener {
override def actionPerformed(
e: ActionEvent
): Unit = {
running =
!running
}
}
)
resetButton.addActionListener(
new ActionListener {
override def actionPerformed(
e: ActionEvent
): Unit = {
itemIndex =
0
pointIndex =
1
running =
true
panel.repaint()
}
}
)
closeButton.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()
//
}
}
// ============================================================
// 3D PROJECTION
// ============================================================
def project3D(
x: Double,
y: Double,
z: Double,
angle: Double,
scale: Double,
ox: Int,
oy: Int
): Point = {
val r =
angle *
math.Pi /
180.0
val rx =
x *
math.cos(r) -
z *
math.sin(r)
val rz =
x *
math.sin(r) +
z *
math.cos(r)
new Point(
ox +
(
rx *
scale
).toInt,
//
oy +
(
y *
scale -
rz *
scale *
0.50
).toInt
//
)
}
// ============================================================
// 3D RENDER
// ============================================================
def render3DItem(
g2: Graphics2D,
item: DrawItem,
angle: Double,
depth: Double,
scale: Double,
ox: Int,
oy: Int
): Unit = {
if (
item.points.length < 2
) {
//
return
//
}
val front =
ArrayBuffer[Point]()
val back =
ArrayBuffer[Point]()
var i =
0
while (
i < item.points.length
) {
//
front +=
project3D(
item.points(i).x - CX,
item.points(i).y - CY,
0,
angle,
scale,
ox,
oy
)
back +=
project3D(
item.points(i).x - CX,
item.points(i).y - CY,
depth,
angle,
scale,
ox,
oy
)
i += 1
//
}
if (
item.filled &&
front.length >= 3
) {
//
val poly =
new java.awt.Polygon()
i =
0
while (
i < front.length
) {
poly.addPoint(
front(i).x,
front(i).y
)
i += 1
}
g2.setColor(
item.fillColor
)
g2.fillPolygon(
poly
)
//
}
g2.setStroke(
new BasicStroke(
3,
BasicStroke.CAP_ROUND,
BasicStroke.JOIN_ROUND
)
)
g2.setColor(
new Color(
math.max(
0,
item.strokeColor.getRed - 55
),
//
math.max(
0,
item.strokeColor.getGreen - 55
),
math.max(
0,
item.strokeColor.getBlue - 55
)
)
//
)
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(
60,
100,
175
)
)
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
//
}
g2.setColor(
item.strokeColor
)
i =
1
while (
i < front.length
) {
//
g2.drawLine(
front(i - 1).x,
front(i - 1).y,
front(i).x,
front(i).y
)
i += 1
//
}
if (
item.filled &&
isClosed(
item.points
)
) {
//
g2.drawLine(
front.last.x,
front.last.y,
front.head.x,
front.head.y
)
//
}
}
// ============================================================
// 3D PREVIEW
// ============================================================
def start3DPreview(): Unit = {
if (
items.isEmpty
) {
//
setStatus(
"DRAW OR ADD AN OBJECT FIRST"
)
//
} else {
//
val preview =
new JFrame(
"ULTRA 3D COLOR MODEL"
)
preview.setDefaultCloseOperation(
WindowConstants.DISPOSE_ON_CLOSE
)
preview.setSize(
1150,
780
)
preview.setLocationRelativeTo(
frame
)
val data =
copyItems(
items
)
var angle =
0.0
var depth =
90.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
),
w,
h,
new Color(
80,
25,
115
)
)
)
g2.fillRect(
0,
0,
w,
h
)
var i =
0
while (
i < data.length
) {
render3DItem(
g2,
data(i),
angle +
i * 8,
depth +
i * 6,
0.70,
w / 2,
h / 2
)
i += 1
}
g2.setColor(
Color.WHITE
)
g2.setFont(
new Font(
"Arial",
Font.BOLD,
19
)
)
g2.drawString(
"3D COLOUR MODEL",
25,
35
)
g2.setFont(
new Font(
"Arial",
Font.PLAIN,
14
)
)
g2.drawString(
"ROTATION: " +
angle.toInt +
" DEPTH: " +
depth.toInt,
25,
58
)
}
}
val playButton =
makeButton(
"PLAY / PAUSE"
)
val resetButton =
makeButton(
"RESET"
)
val depthPlusButton =
makeButton(
"DEPTH +"
)
val depthMinusButton =
makeButton(
"DEPTH -"
)
val closeButton =
makeButton(
"CLOSE"
)
val controls =
new JPanel(
new FlowLayout()
)
controls.add(
playButton
)
controls.add(
resetButton
)
controls.add(
depthPlusButton
)
controls.add(
depthMinusButton
)
controls.add(
closeButton
)
val timer =
new Timer(
30,
new ActionListener {
override def actionPerformed(
e: ActionEvent
): Unit = {
if (
running
) {
angle +=
2.0
panel.repaint()
}
}
}
)
playButton.addActionListener(
new ActionListener {
override def actionPerformed(
e: ActionEvent
): Unit = {
running =
!running
}
}
)
resetButton.addActionListener(
new ActionListener {
override def actionPerformed(
e: ActionEvent
): Unit = {
angle =
0.0
depth =
90.0
panel.repaint()
}
}
)
depthPlusButton.addActionListener(
new ActionListener {
override def actionPerformed(
e: ActionEvent
): Unit = {
depth =
math.min(
300.0,
depth + 10.0
)
panel.repaint()
}
}
)
depthMinusButton.addActionListener(
new ActionListener {
override def actionPerformed(
e: ActionEvent
): Unit = {
depth =
math.max(
10.0,
depth - 10.0
)
panel.repaint()
}
}
)
closeButton.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()
//
}
}
// ============================================================
// CREATE BUTTONS
// ============================================================
pencilButton =
makeButton(
"PENCIL"
)
selectButton =
makeButton(
"SELECT"
)
eraserButton =
makeButton(
"ERASER"
)
bucketButton =
makeButton(
"BUCKET"
)
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"
)
strokeColorButton =
makeButton(
"STROKE COLOR"
)
fillColorButton =
makeButton(
"FILL 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\n" +
"// THEN PRESS GENERATE CODE"
)
// ============================================================
// SPEED SLIDER
// ============================================================
speedSlider =
new JSlider(
5,
100,
25
)
speedSlider.setPreferredSize(
new Dimension(
135,
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(
selectButton
)
toolbar.add(
eraserButton
)
toolbar.add(
bucketButton
)
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(
strokeColorButton
)
toolbar.add(
fillColorButton
)
toolbar.add(
new JLabel(
"SPEED"
)
)
toolbar.add(
speedSlider
)
// ============================================================
// OBJECT HEADER
// ============================================================
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
)
// ============================================================
// OBJECT PANEL
// ============================================================
val objectPanel =
new JPanel(
new BorderLayout(
4,
4
)
)
objectPanel.add(
objectHeader,
BorderLayout.NORTH
)
objectPanel.add(
new JScrollPane(
objectList
),
BorderLayout.CENTER
)
// ============================================================
// SHAPE PANEL
// ============================================================
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
)
// ============================================================
// TABS
// ============================================================
val tabs =
new JTabbedPane()
tabs.addTab(
"OBJECTS",
objectPanel
)
tabs.addTab(
"SHAPES",
shapePanel
)
// ============================================================
// CANVAS WRAPPER
// ============================================================
val canvasWrapper =
new JPanel(
new BorderLayout(
4,
4
)
)
val canvasTitle =
new JLabel(
"CANVAS ? DRAW / SELECT / MOVE / 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
)
// ============================================================
// MOVABLE CANVAS <-> CODE
// ============================================================
val canvasCodeSplit =
new JSplitPane(
JSplitPane.HORIZONTAL_SPLIT,
canvasWrapper,
codeWrapper
)
canvasCodeSplit.setDividerLocation(
650
)
canvasCodeSplit.setResizeWeight(
0.62
)
// ============================================================
// MOVABLE LIBRARY
// ============================================================
val mainSplit =
new JSplitPane(
JSplitPane.HORIZONTAL_SPLIT,
tabs,
canvasCodeSplit
)
mainSplit.setDividerLocation(
270
)
mainSplit.setResizeWeight(
0.0
)
// ============================================================
// STATUS
// ============================================================
status =
new JLabel(
"READY"
)
status.setFont(
new Font(
"Arial",
Font.BOLD,
13
)
)
// ============================================================
// MAIN FRAME
// ============================================================
frame =
new JFrame(
"ULTRA LEGEND DRAW LAB X3"
)
frame.setDefaultCloseOperation(
WindowConstants.EXIT_ON_CLOSE
)
frame.setSize(
1650,
900
)
frame.setMinimumSize(
new Dimension(
1100,
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
)
// ============================================================
// PENCIL EVENT
// ============================================================
pencilButton.addActionListener(
new ActionListener {
//
override def actionPerformed(
e: ActionEvent
): Unit = {
currentTool =
"PENCIL"
eraserButton.setEnabled(
true
)
selectedIndex =
-1
setStatus(
"PENCIL MODE"
)
canvas.requestFocusInWindow()
}
//
}
)
// ============================================================
// SELECT EVENT
// ============================================================
selectButton.addActionListener(
new ActionListener {
//
override def actionPerformed(
e: ActionEvent
): Unit = {
currentTool =
"SELECT"
setStatus(
"SELECT: CLICK OBJECT, DRAG TO MOVE, DRAG HANDLE TO RESIZE"
)
repaintCanvas()
canvas.requestFocusInWindow()
}
//
}
)
// ============================================================
// ERASER EVENT
// ============================================================
eraserButton.addActionListener(
new ActionListener {
//
override def actionPerformed(
e: ActionEvent
): Unit = {
currentTool =
"ERASER"
selectedIndex =
-1
setStatus(
"ERASER MODE - CLICK OBJECT"
)
canvas.requestFocusInWindow()
}
//
}
)
// ============================================================
// BUCKET EVENT
// ============================================================
bucketButton.addActionListener(
new ActionListener {
//
override def actionPerformed(
e: ActionEvent
): Unit = {
currentTool =
"BUCKET"
setStatus(
"BUCKET MODE - CLICK CLOSED OBJECT"
)
canvas.requestFocusInWindow()
}
//
}
)
// ============================================================
// UNDO EVENT
// ============================================================
undoButton.addActionListener(
new ActionListener {
//
override def actionPerformed(
e: ActionEvent
): Unit = {
doUndo()
canvas.requestFocusInWindow()
}
//
}
)
// ============================================================
// REDO EVENT
// ============================================================
redoButton.addActionListener(
new ActionListener {
//
override def actionPerformed(
e: ActionEvent
): Unit = {
doRedo()
canvas.requestFocusInWindow()
}
//
}
)
// ============================================================
// CLEAR EVENT
// ============================================================
clearButton.addActionListener(
new ActionListener {
//
override def actionPerformed(
e: ActionEvent
): Unit = {
clearAll()
canvas.requestFocusInWindow()
}
//
}
)
// ============================================================
// GENERATE EVENT
// ============================================================
generateButton.addActionListener(
new ActionListener {
//
override def actionPerformed(
e: ActionEvent
): Unit = {
generateKojoCode()
canvas.requestFocusInWindow()
}
//
}
)
// ============================================================
// COPY EVENT
// ============================================================
copyButton.addActionListener(
new ActionListener {
//
override def actionPerformed(
e: ActionEvent
): Unit = {
copyCode()
canvas.requestFocusInWindow()
}
//
}
)
// ============================================================
// SAVE EVENT
// ============================================================
saveButton.addActionListener(
new ActionListener {
//
override def actionPerformed(
e: ActionEvent
): Unit = {
saveCode()
}
//
}
)
// ============================================================
// 2D ANIMATION EVENT
// ============================================================
animationButton.addActionListener(
new ActionListener {
//
override def actionPerformed(
e: ActionEvent
): Unit = {
start2DAnimation()
}
//
}
)
// ============================================================
// 3D EVENT
// ============================================================
model3DButton.addActionListener(
new ActionListener {
//
override def actionPerformed(
e: ActionEvent
): Unit = {
start3DPreview()
}
//
}
)
// ============================================================
// GRID
// ============================================================
gridButton.addActionListener(
new ActionListener {
//
override def actionPerformed(
e: ActionEvent
): Unit = {
gridOn =
!gridOn
repaintCanvas()
setStatus(
if (
gridOn
)
"GRID ON"
else
"GRID OFF"
)
}
//
}
)
// ============================================================
// SNAP
// ============================================================
snapButton.addActionListener(
new ActionListener {
//
override def actionPerformed(
e: ActionEvent
): Unit = {
snapOn =
!snapOn
setStatus(
if (
snapOn
)
"SNAP ON"
else
"SNAP OFF"
)
}
//
}
)
// ============================================================
// STROKE COLOR
// ============================================================
strokeColorButton.addActionListener(
new ActionListener {
//
override def actionPerformed(
e: ActionEvent
): Unit = {
val selected =
JColorChooser.showDialog(
frame,
"STROKE COLOR",
currentColor
)
if (
selected != null
) {
currentColor =
selected
if (
selectedIndex >= 0 &&
selectedIndex < items.length
) {
remember()
val old =
items(
selectedIndex
)
items(
selectedIndex
) =
new DrawItem(
old.points,
selected,
old.fillColor,
old.filled,
old.itemName
)
}
repaintCanvas()
setStatus(
"STROKE COLOR CHANGED"
)
}
}
//
}
)
// ============================================================
// FILL COLOR
// ============================================================
fillColorButton.addActionListener(
new ActionListener {
//
override def actionPerformed(
e: ActionEvent
): Unit = {
val selected =
JColorChooser.showDialog(
frame,
"FILL COLOR",
currentFillColor
)
if (
selected != null
) {
currentFillColor =
selected
setStatus(
"FILL COLOR SELECTED"
)
if (
selectedIndex >= 0 &&
selectedIndex < items.length
) {
applyBucket()
}
}
}
//
}
)
// ============================================================
// 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 selected =
objectList.getSelectedValue
if (
selected != null
) {
addObject(
selected
)
canvas.requestFocusInWindow()
}
}
}
//
}
)
// ============================================================
// SHAPE DOUBLE CLICK
// ============================================================
shapeList.addMouseListener(
new MouseAdapter {
//
override def mouseClicked(
e: MouseEvent
): Unit = {
if (
e.getClickCount == 2
) {
val selected =
shapeList.getSelectedValue
if (
selected != null
) {
addShape(
selected
)
canvas.requestFocusInWindow()
}
}
}
//
}
)
// ============================================================
// MOUSE PRESSED
// ============================================================
canvas.addMouseListener(
new MouseAdapter {
//
override def mousePressed(
e: MouseEvent
): Unit = {
val p =
mousePoint(
e
)
if (
currentTool ==
"SELECT"
) {
if (
selectedIndex >= 0 &&
selectedIndex < items.length
) {
val handle =
findHandle(
items(
selectedIndex
),
p
)
if (
handle != ""
) {
remember()
resizingSelection =
true
movingSelection =
false
resizeHandle =
handle
originalSelectedPoints =
items(
selectedIndex
).points
dragStart =
p
setStatus(
"RESIZE HANDLE: " +
handle
)
} else {
val clicked =
findItemAt(
p
)
if (
clicked ==
selectedIndex
) {
remember()
movingSelection =
true
resizingSelection =
false
dragStart =
p
setStatus(
"MOVING OBJECT"
)
} else {
selectedIndex =
clicked
movingSelection =
false
resizingSelection =
false
if (
clicked >= 0
) {
setStatus(
"SELECTED: " +
items(clicked).itemName
)
} else {
setStatus(
"NOTHING SELECTED"
)
}
}
}
} else {
selectedIndex =
findItemAt(
p
)
if (
selectedIndex >= 0
) {
setStatus(
"SELECTED: " +
items(
selectedIndex
).itemName
)
} else {
setStatus(
"NOTHING SELECTED"
)
}
}
repaintCanvas()
} else if (
currentTool ==
"BUCKET"
) {
val clicked =
findItemAt(
p
)
if (
clicked >= 0
) {
selectedIndex =
clicked
applyBucket()
} else {
setStatus(
"CLICK INSIDE CLOSED OBJECT"
)
}
} else if (
currentTool ==
"ERASER"
) {
val clicked =
findItemAt(
p
)
if (
clicked >= 0
) {
remember()
items.remove(
clicked
)
selectedIndex =
-1
repaintCanvas()
setStatus(
"OBJECT ERASED"
)
} else {
setStatus(
"CLICK OBJECT TO ERASE"
)
}
} else {
remember()
activePoints.clear()
activePoints +=
p
drawingNow =
true
selectedIndex =
-1
setStatus(
"DRAWING..."
)
repaintCanvas()
}
}
// ========================================================
// MOUSE RELEASE
// ========================================================
override def mouseReleased(
e: MouseEvent
): Unit = {
if (
currentTool ==
"SELECT"
) {
movingSelection =
false
resizingSelection =
false
repaintCanvas()
} else {
if (
drawingNow
) {
activePoints +=
mousePoint(
e
)
if (
activePoints.length >= 2
) {
items +=
new DrawItem(
activePoints.toVector,
currentColor,
currentFillColor,
false,
"FREEHAND"
)
selectedIndex =
items.length - 1
}
activePoints.clear()
drawingNow =
false
repaintCanvas()
setStatus(
"DRAWING RECORDED"
)
}
}
}
//
}
)
// ============================================================
// MOUSE DRAG
// ============================================================
canvas.addMouseMotionListener(
new MouseMotionAdapter {
//
override def mouseDragged(
e: MouseEvent
): Unit = {
val p =
mousePoint(
e
)
if (
currentTool ==
"SELECT"
) {
if (
movingSelection &&
selectedIndex >= 0
) {
val dx =
p.x -
dragStart.x
val dy =
p.y -
dragStart.y
moveSelected(
dx,
dy
)
dragStart =
p
repaintCanvas()
} else if (
resizingSelection &&
selectedIndex >= 0
) {
resizeSelected(
p
)
repaintCanvas()
}
} else {
if (
drawingNow
) {
if (
activePoints.isEmpty ||
distance(
activePoints.last,
p
) >= 2
) {
activePoints +=
p
}
repaintCanvas()
}
}
}
//
}
)
// ============================================================
// KEYBOARD
// ============================================================
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
) {
if (
selectedIndex >= 0 &&
selectedIndex < items.length
) {
remember()
items.remove(
selectedIndex
)
selectedIndex =
-1
repaintCanvas()
setStatus(
"SELECTED OBJECT DELETED"
)
} else {
clearAll()
}
} else if (
k == KeyEvent.VK_V
) {
currentTool =
"SELECT"
setStatus(
"SELECT TOOL"
)
} else if (
k == KeyEvent.VK_P
) {
currentTool =
"PENCIL"
setStatus(
"PENCIL TOOL"
)
} else if (
k == KeyEvent.VK_E
) {
currentTool =
"ERASER"
setStatus(
"ERASER TOOL"
)
} else if (
k == KeyEvent.VK_B
) {
currentTool =
"BUCKET"
setStatus(
"BUCKET TOOL"
)
} else if (
k == KeyEvent.VK_F1
) {
JOptionPane.showMessageDialog(
frame,
"ULTRA LEGEND X3 SHORTCUTS\n\n" +
"V = SELECT\n" +
"P = PENCIL\n" +
"E = ERASER\n" +
"B = BUCKET\n\n" +
"CTRL+Z = UNDO\n" +
"CTRL+Y = REDO\n" +
"CTRL+G = GENERATE CODE\n" +
"CTRL+C = COPY CODE\n" +
"CTRL+S = SAVE CODE\n" +
"CTRL+A = 2D ANIMATION\n" +
"CTRL+3 = 3D MODEL\n" +
"DELETE = DELETE / CLEAR\n\n" +
"DOUBLE CLICK OBJECT = ADD\n" +
"DOUBLE CLICK SHAPE = ADD\n\n" +
"SELECT OBJECT:\n" +
"CLICK = SELECT\n" +
"DRAG = MOVE\n" +
"HANDLE DRAG = RESIZE\n\n" +
"DRAG PANEL DIVIDERS TO RESIZE UI",
"SECRET CONTROL CENTER",
JOptionPane.INFORMATION_MESSAGE
)
}
}
//
}
)
// ============================================================
// FINAL FOCUS
// ============================================================
canvas.setFocusable(
true
)
canvas.setCursor(
Cursor.getPredefinedCursor(
Cursor.CROSSHAIR_CURSOR
)
)
// ============================================================
// START
// ============================================================
SwingUtilities.invokeLater(
new Runnable {
//
override def run(): Unit = {
frame.setVisible(
true
)
canvas.requestFocusInWindow()
setStatus(
"ULTRA LEGEND X3 READY"
)
}
//
}
)
//
//