Skip to content

Instantly share code, notes, and snippets.

@belyaev-mikhail
Created August 5, 2020 17:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save belyaev-mikhail/f789f1bc494abd7abe1a481e05c3e0c7 to your computer and use it in GitHub Desktop.
Save belyaev-mikhail/f789f1bc494abd7abe1a481e05c3e0c7 to your computer and use it in GitHub Desktop.
package ru.spbstu.draw
import ru.spbstu.wheels.aStarSearch
import java.awt.*
import java.awt.event.ActionEvent
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import java.awt.event.MouseWheelEvent
import java.awt.geom.*
import javax.swing.*
import kotlin.math.abs
import kotlin.math.floor
fun Action(body: (ActionEvent) -> Unit) = object: AbstractAction() {
override fun actionPerformed(e: ActionEvent) {
return body(e)
}
}
operator fun Point2D.component1() = x
operator fun Point2D.component2() = y
fun Point2D(x: Double, y: Double): Point2D = Point2D.Double(x, y)
fun Point2D(x: Int, y: Int): Point2D = Point2D.Double(x.toDouble(), y.toDouble())
fun Point2D.copy(x: Double = this.x, y: Double = this.y) = Point2D(x, y)
operator fun Point2D.plus(rhv: Point2D) = Point2D(x + rhv.x, y + rhv.y)
operator fun Point2D.plusAssign(rhv: Point2D) = setLocation(x + rhv.x, y + rhv.y)
infix fun Point2D.distance(that: Point2D) = this.distance(that)
infix fun Point2D.manhattanDistance(that: Point2D) = maxOf(abs(x - that.x), abs(y - that.y))
infix fun Point2D.chebyshevDistance(that: Point2D) = abs(x - that.x) + abs(y - that.y)
val Rectangle2D.center get() = Point2D(centerX, centerY)
fun Rectangle2D(x: Double, y: Double, w: Double, h: Double = w): Rectangle2D =
Rectangle2D.Double(x, y, w, h)
fun Rectangle2D(p: Point2D, w: Double, h: Double = w): Rectangle2D =
Rectangle2D.Double(p.x, p.y, w, h)
fun Rectangle2D(p1: Point2D, p2: Point2D): Rectangle2D =
Rectangle2D.Double(p1.x, p2.y, 0.0, 0.0).apply { add(p2) }
fun Rectangle2D.copy(x: Double = this.x,
y: Double = this.y,
w: Double = this.width,
h: Double = this.height) = Rectangle2D(x, y, w, h)
operator fun Rectangle2D.plusAssign(point: Point2D) = add(point)
operator fun Rectangle2D.plusAssign(rectangle2D: Rectangle2D) = add(rectangle2D)
fun Ellipse2D(x: Double, y: Double, w: Double, h: Double): Ellipse2D =
Ellipse2D.Double(x, y, w, h)
fun Ellipse2D(center: Point2D, w: Double, h: Double = w): Ellipse2D =
Ellipse2D.Double(center.x - w / 2, center.y - h / 2, w, h)
fun Line2D(p1: Point2D, p2: Point2D): Line2D = Line2D.Double(p1, p2)
operator fun Line2D.component1() = p1
operator fun Line2D.component2() = p2
inline fun dumbCanvas(width: Int = 800, height: Int = 600, crossinline draw: Graphics2D.() -> Unit) = object : TransformablePanel() {
override val transform: AffineTransform = AffineTransform()
override fun getPreferredSize(): Dimension = Dimension(width, height)
override fun paintComponent(g: Graphics) {
super.paintComponent(g)
g as Graphics2D
val oldTransform = g.transform
g.transform(transform)
g.stroke = BasicStroke((1.0 / maxOf(transform.scaleX, transform.scaleY)).toFloat())
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY)
g.draw()
g.transform = oldTransform
}
}
fun dumbFrame(panel: JPanel, title: String = "") = JFrame(title).apply {
add(panel)
pack()
isVisible = true
}
inline fun dumbFrame(title: String = "", width: Int = 800, height: Int = 600, crossinline draw: Graphics2D.() -> Unit) =
dumbFrame(title = title, panel = dumbCanvas(width, height, draw))
abstract class TransformablePanel: JPanel() {
abstract val transform: AffineTransform
fun scale(scaleX: Double, scaleY: Double = scaleX) {
SwingUtilities.invokeLater {
transform.scale(scaleX, scaleY)
repaint()
}
}
fun translate(tx: Double, ty: Double) {
SwingUtilities.invokeLater {
transform.translate(tx, ty)
repaint()
}
}
fun zoomTo(point: Point2D, scaleX: Double, scaleY: Double = scaleX) {
SwingUtilities.invokeLater {
transform.apply {
translate(point.x, point.y)
scale(scaleX, scaleY)
translate(-point.x, -point.y)
}
repaint()
}
}
fun invokeRepaint() = SwingUtilities.invokeLater { repaint() }
val canvasMousePosition: Point2D? get() = this.mousePosition?.let { transform.inverseTransform(it) }
val MouseEvent.canvasPoint: Point2D get() = transform.inverseTransform(point, Point2D.Double())
}
inline fun Graphics2D.withPaint(p: Paint, body: Graphics2D.() -> Unit) {
val oldPaint = paint
paint = p
body()
paint = oldPaint
}
inline fun Graphics2D.withFont(f: Font, body: Graphics2D.() -> Unit) {
val oldFont = font
font = f
body()
font = oldFont
}
inline fun Graphics2D.withStroke(s: Stroke, body: Graphics2D.() -> Unit) {
val oldStroke = stroke
stroke = s
body()
stroke = oldStroke
}
inline fun Graphics2D.absolute(body: Graphics2D.() -> Unit) {
val oldTransform = transform
transform = AffineTransform()
body()
transform = oldTransform
}
inline fun TransformablePanel.onMouseClick(crossinline body: TransformablePanel.(e: MouseEvent) -> Unit) {
addMouseListener(object: MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
body(e)
}
})
}
inline fun TransformablePanel.onMouseWheel(crossinline body: TransformablePanel.(e: MouseWheelEvent) -> Unit) {
addMouseWheelListener { e -> body(e) }
}
fun sameButton(start: MouseEvent, e: MouseEvent) =
start.modifiersEx == e.modifiersEx
inline fun TransformablePanel.onMousePan(
crossinline filter: TransformablePanel.(e: MouseEvent) -> Boolean = { true },
crossinline destructor: TransformablePanel.(start: MouseEvent, prev: MouseEvent) -> Unit = { _, _ -> },
crossinline body: TransformablePanel.(start: MouseEvent, prev: MouseEvent, e: MouseEvent) -> Unit) {
val panner = object: MouseAdapter() {
private var start: MouseEvent? = null
private var prev: MouseEvent? = null
override fun mousePressed(e: MouseEvent) {
if(filter(e)) {
start = e
prev = e
}
}
override fun mouseReleased(e: MouseEvent) {
val start = start
val prev = prev
if(start != null && prev != null && e.button == start.button) {
body(start, prev, e)
destructor(start, e)
this.start = null
this.prev = null
}
}
override fun mouseDragged(e: MouseEvent) {
val start = start
val prev = prev
if(start != null && prev != null && sameButton(start, e)) {
body(start, prev, e)
this.prev = e
}
}
}
addMouseListener(panner)
addMouseMotionListener(panner)
}
inline fun JComponent.onKey(stroke: KeyStroke, crossinline body: () -> Unit) {
val skey = "$stroke".replace(" ", "+")
inputMap.put(stroke, skey)
actionMap.put(skey, Action { body() })
}
inline fun JComponent.onKey(stroke: String, crossinline body: () -> Unit) {
val skey = stroke.replace(" ", "+")
inputMap.put(KeyStroke.getKeyStroke(stroke), skey)
actionMap.put(skey, Action { body() })
}
operator fun AffineTransform.invoke(p: Point2D) = transform(p, Point2D.Double())
fun AffineTransform.inverseTransform(p: Point2D) = inverseTransform(p, Point2D.Double())
enum class CellState {
Free, NotFree, Start, Finish;
val invert: CellState
get() = when(this) {
Free -> NotFree
NotFree -> Free
Start -> Free
Finish -> Free
}
}
data class Cell(var state: CellState, val x: Int, val y: Int)
interface Drawable {
fun drawOn(graphics2D: Graphics2D)
object Nil: Drawable {
override fun drawOn(graphics2D: Graphics2D){}
}
data class Shape(val inner: java.awt.Shape): Drawable {
override fun drawOn(graphics2D: Graphics2D) {
graphics2D.fill(inner)
}
}
class Multi(vararg val inner: Drawable): Drawable {
constructor(vararg shapes: java.awt.Shape): this(*shapes.map { Shape(it) }.toTypedArray<Drawable>())
override fun drawOn(graphics2D: Graphics2D) {
for(e in inner) e.drawOn(graphics2D)
}
}
}
fun Graphics2D.draw(drawable: Drawable) = drawable.drawOn(this)
fun main() {
val field = List(18) { x -> List(18) { y -> Cell(CellState.Free, x, y) } }
fun getCell(pt: Point2D) = field.getOrNull(floor(pt.x).toInt())?.getOrNull(floor(pt.y).toInt())
val overlays: MutableMap<String, Drawable> = mutableMapOf()
val canvas = dumbCanvas {
withPaint(Color.BLUE.darker()) {
for((state, x, y) in field.flatten()) {
val rec = Rectangle2D.Double(x.toDouble(), y.toDouble(), 1.0, 1.0)
val paint = when(state) {
CellState.NotFree -> Color.DARK_GRAY
CellState.Start -> Color.BLUE.brighter()
CellState.Finish -> Color.RED
CellState.Free -> null
}
if(paint != null) withPaint(paint) {
fill(rec)
}
draw(rec)
}
}
withPaint(Color(255, 175, 175, 100)) {
for((_, overlay) in overlays) if(overlay != null) draw(overlay)
}
absolute {
withFont(Font.decode("Fira-Mono-Bold-20")) {
drawString("Hello", 20.0f, 20.0f)
}
}
}
canvas.translate(20.0, 20.0)
canvas.scale(30.0)
canvas.onMouseClick { e ->
val pt = e.canvasPoint
getCell(pt)?.apply {
state = state.invert
}
canvas.invokeRepaint()
}
canvas.onKey("S") {
val pt = canvas.canvasMousePosition ?: return@onKey
getCell(pt)?.apply {
field.flatten().filter { it.state == CellState.Start }.forEach { it.state = CellState.Free }
state = CellState.Start
}
canvas.invokeRepaint()
}
canvas.onKey("F") {
val pt = canvas.canvasMousePosition ?: return@onKey
getCell(pt)?.apply {
field.flatten().filter { it.state == CellState.Finish }.forEach { it.state = CellState.Free }
state = CellState.Finish
}
canvas.invokeRepaint()
}
canvas.onKey("DOWN") {
val ty = 20.0 / canvas.transform.scaleY
canvas.translate(0.0, ty)
}
canvas.onKey("UP") {
val ty = -20.0 / canvas.transform.scaleY
canvas.translate(0.0, ty)
}
canvas.onKey("LEFT") {
val tx = -20.0 / canvas.transform.scaleX
canvas.translate(tx, 0.0)
}
canvas.onKey("RIGHT") {
val tx = 20.0 / canvas.transform.scaleX
canvas.translate(tx, 0.0)
}
canvas.onKey(KeyStroke.getKeyStroke('+', 0)) {
canvas.scale(1.1)
}
canvas.onKey(KeyStroke.getKeyStroke('-', 0)) {
canvas.scale(0.9)
}
canvas.onKey("SPACE") {
val start = field.flatten().firstOrNull { it.state == CellState.Start } ?: return@onKey
val end = field.flatten().firstOrNull { it.state == CellState.Finish } ?: return@onKey
val spt = Point2D(start.x, start.y)
val ept = Point2D(end.x, end.y)
var astarPath by overlays
val epath = aStarSearch(
spt,
heur = { it.distance(ept) * 2 },
goal = { it == ept },
neighbours = { el ->
val neigh = sequenceOf(
Point2D(0, 1),
Point2D(1, 0),
Point2D(0, -1),
Point2D(-1, 0),
Point2D(-2, -2)
)
neigh.map { el + it }.filter {
getCell(it)?.state != CellState.NotFree && it.x.toInt() in 0..17 && it.y.toInt() in 0..17
}
}
) ?: run { astarPath = Drawable.Nil; return@onKey }
astarPath = Drawable.Multi(shapes = *epath.map { Rectangle2D(it, 1.0, 1.0) }.toTypedArray())
canvas.invokeRepaint()
}
canvas.onMouseWheel { e ->
val rot = e.preciseWheelRotation
val point = e.canvasPoint
val scale = 1.0 - rot * 0.1
canvas.zoomTo(point, scale)
}
canvas.onMousePan(filter = { SwingUtilities.isRightMouseButton(it) }) { _, prev, e ->
val st = prev.canvasPoint
val end = e.canvasPoint
canvas.translate(end.x - st.x, end.y - st.y)
}
var panline by overlays
canvas.onMousePan(
destructor = { _, _ -> panline = Drawable.Nil },
filter = { SwingUtilities.isLeftMouseButton(it) }) { start, _, e ->
val st = start.canvasPoint
val end = e.canvasPoint
val line = Line2D.Double(st, end)
panline = Drawable.Shape(BasicStroke(0.2f).createStrokedShape(line))
val nstate = getCell(st)?.state?.takeIf { it == CellState.NotFree } ?: CellState.Free
for(cell in field.flatten()) {
val (_, x, y) = cell
val rec = Rectangle2D.Double(x.toDouble(), y.toDouble(), 1.0, 1.0)
if(line.intersects(rec)) cell.state = nstate
}
canvas.invokeRepaint()
}
dumbFrame(canvas).defaultCloseOperation = JFrame.EXIT_ON_CLOSE
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment