├── scage-logo.png
├── phys2d-060408.jar
├── rotating_hello.png
├── src
├── main
│ ├── resources
│ │ ├── maven.properties
│ │ ├── resources
│ │ │ ├── fonts
│ │ │ │ └── DroidSans.ttf
│ │ │ └── images
│ │ │ │ └── scage-logo.png
│ │ ├── controller-actor-system.conf
│ │ └── logback.xml
│ └── scala
│ │ └── com
│ │ └── github
│ │ └── dunnololda
│ │ └── scage
│ │ ├── support
│ │ ├── ScageId.scala
│ │ ├── physics
│ │ │ ├── objects
│ │ │ │ ├── StaticBall.scala
│ │ │ │ ├── DynaBall.scala
│ │ │ │ ├── StaticLine.scala
│ │ │ │ ├── StaticBox.scala
│ │ │ │ ├── DynaBox.scala
│ │ │ │ └── StaticPolygon.scala
│ │ │ ├── Physical.scala
│ │ │ └── ScagePhysics.scala
│ │ ├── messages
│ │ │ ├── unicode
│ │ │ │ ├── Effect.java
│ │ │ │ ├── ConfigurableEffect.java
│ │ │ │ ├── ColorEffect.java
│ │ │ │ ├── MyImage.java
│ │ │ │ ├── Glyph.java
│ │ │ │ └── GlyphPage.java
│ │ │ ├── ColoredString.scala
│ │ │ ├── ScageMessageD.scala
│ │ │ ├── ScageXML.scala
│ │ │ └── ScageMessage.scala
│ │ ├── XInitThreadsCaller.scala
│ │ ├── PathFinder.scala
│ │ ├── tracer3
│ │ │ ├── Trace.scala
│ │ │ ├── CoordTracer.scala
│ │ │ └── ScageTracer.scala
│ │ ├── ScageColorTest.scala
│ │ ├── parsers
│ │ │ └── VecParser.scala
│ │ ├── Events.scala
│ │ ├── SortedBuffer.scala
│ │ ├── LWJGLKeyboard.scala
│ │ ├── Vec.scala
│ │ └── ScageColor.scala
│ │ ├── handlers
│ │ ├── controller2
│ │ │ ├── ScageController.scala
│ │ │ └── SingleController.scala
│ │ └── controller3
│ │ │ └── ActorSingleController.scala
│ │ ├── ScageScreen.scala
│ │ └── ScageLib.scala
└── test
│ ├── resources
│ ├── colortest-properties.txt
│ ├── resources
│ │ ├── fonts
│ │ │ ├── comic.ttf
│ │ │ └── DroidSans.ttf
│ │ ├── strings
│ │ │ ├── scagetest_strings_ru.xml
│ │ │ └── scagetest_strings_en.xml
│ │ └── interfaces
│ │ │ ├── scagetest_interfaces_en.xml
│ │ │ └── scagetest_interfaces_ru.xml
│ └── scagetest-properties.txt
│ └── scala
│ └── com
│ └── github
│ └── dunnololda
│ └── scage
│ └── test
│ └── ScageTest.scala
├── .gitignore
├── xdll.cpp
├── project
└── Build.scala
└── README.md
/scage-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/delorum/scage/HEAD/scage-logo.png
--------------------------------------------------------------------------------
/phys2d-060408.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/delorum/scage/HEAD/phys2d-060408.jar
--------------------------------------------------------------------------------
/rotating_hello.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/delorum/scage/HEAD/rotating_hello.png
--------------------------------------------------------------------------------
/src/main/resources/maven.properties:
--------------------------------------------------------------------------------
1 | app.version = ${project.version}
2 | app.name = ${project.artifactId}
--------------------------------------------------------------------------------
/src/test/resources/colortest-properties.txt:
--------------------------------------------------------------------------------
1 | app.name = Color Test
2 | app.version = 1.1
3 |
4 | screen.width = 640
5 | screen.height = 480
--------------------------------------------------------------------------------
/src/test/resources/resources/fonts/comic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/delorum/scage/HEAD/src/test/resources/resources/fonts/comic.ttf
--------------------------------------------------------------------------------
/src/main/resources/resources/fonts/DroidSans.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/delorum/scage/HEAD/src/main/resources/resources/fonts/DroidSans.ttf
--------------------------------------------------------------------------------
/src/test/resources/resources/fonts/DroidSans.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/delorum/scage/HEAD/src/test/resources/resources/fonts/DroidSans.ttf
--------------------------------------------------------------------------------
/src/main/resources/resources/images/scage-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/delorum/scage/HEAD/src/main/resources/resources/images/scage-logo.png
--------------------------------------------------------------------------------
/src/main/resources/controller-actor-system.conf:
--------------------------------------------------------------------------------
1 | controller-system {
2 | my-pinned-dispatcher {
3 | executor = "thread-pool-executor"
4 | type = PinnedDispatcher
5 | }
6 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .settings
2 | .classpath
3 | .project
4 | .idea
5 | .svn
6 | *.iml
7 | *.ipr
8 | *.iws
9 | target
10 | local-build.properties
11 | libs
12 | settings.xml
13 | libxx.so
14 |
--------------------------------------------------------------------------------
/src/test/resources/resources/strings/scagetest_strings_ru.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Строка с кодом ? не найдена
5 |
6 |
7 |
8 | Привет, Мир!
9 |
10 |
11 |
12 | WASD и Левая Кнопка Мыши,
13 | Колесико - приблизить/отдалить
14 |
15 |
--------------------------------------------------------------------------------
/src/test/resources/resources/strings/scagetest_strings_en.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The string with code ? not found
5 |
6 |
7 |
8 | Hello World!
9 |
10 |
11 |
12 | Use WASD to move, Left Mouse Button to shoot and
13 | Mouse Wheel for zoom
14 |
15 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/dunnololda/scage/support/ScageId.scala:
--------------------------------------------------------------------------------
1 | package com.github.dunnololda.scage.support
2 |
3 | trait ScageIdTrait {
4 | def nextId:Int
5 | }
6 |
7 | object ScageId extends ScageIdTrait { // TODO: add some id rotation algorithm or throw error and exit on id amount exceeded
8 | protected var id = 10000 // maybe switch to Long, BigInt or something
9 | def nextId = {
10 | synchronized {
11 | id += 1
12 | id
13 | }
14 | }
15 | }
--------------------------------------------------------------------------------
/xdll.cpp:
--------------------------------------------------------------------------------
1 | //--------------------------------------------------------------------------------------------------
2 | //xdll.cpp
3 | //--------------------------------------------------------------------------------------------------
4 |
5 | // compile with g++ -o libxx.so -shared -fPIC -Wl,-soname,libxx.so -L/usr/lib/X11 -I/usr/include/X11 xdll.cpp -lX11
6 |
7 | #include
8 | #include
9 |
10 | class a{
11 | public:
12 | a() { XInitThreads(); }
13 | };
14 |
15 | a X;
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/main/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
7 | %date [%-5level] %msg%n
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/test/resources/scagetest-properties.txt:
--------------------------------------------------------------------------------
1 | xml.lang = ru
2 |
3 | screen.width=640
4 | screen.height=480
5 |
6 | field.from.x = 20
7 | field.to.x = screen.width - 60
8 | field.N_x = (field.to.x - field.from.x)/40
9 |
10 | field.from.y = 20
11 | field.to.y = screen.height - 20
12 | field.N_y = (field.to.y - field.from.y)/40
13 |
14 | field.solid_edges = off
15 |
16 | screen.splash = resources/images/scage-logo.png
17 |
18 | physics.restitution = on
19 | physics.dt = 1
20 | physics.gravity = [-1, -1]
21 |
22 | rect.color = green
23 |
24 | #render.framerate = 15
25 |
26 | xml.interfaces.base = resources/interfaces/scagetest_interfaces
27 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/dunnololda/scage/support/physics/objects/StaticBall.scala:
--------------------------------------------------------------------------------
1 | package com.github.dunnololda.scage.support.physics.objects
2 |
3 | import net.phys2d.raw.shapes.Circle
4 | import net.phys2d.raw.StaticBody
5 | import com.github.dunnololda.scage.support.Vec
6 | import com.github.dunnololda.scage.support.physics.Physical
7 | import com.github.dunnololda.cli.AppProperties._
8 |
9 | class StaticBall(init_coord:Vec, val radius:Int, restitution:Boolean = property("physics.restitution", true)) extends Physical {
10 | val body = new StaticBody("StaticBall", new Circle(radius))
11 | if(restitution) body.setRestitution(1.0f)
12 | body.setPosition(init_coord.x, init_coord.y)
13 |
14 | def points = Array(coord)
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/dunnololda/scage/support/physics/objects/DynaBall.scala:
--------------------------------------------------------------------------------
1 | package com.github.dunnololda.scage.support.physics.objects
2 |
3 | import net.phys2d.raw.Body
4 | import com.github.dunnololda.scage.support.Vec
5 | import net.phys2d.raw.shapes.Circle
6 | import com.github.dunnololda.scage.support.physics.Physical
7 | import com.github.dunnololda.cli.AppProperties._
8 | import java.lang.Float
9 |
10 | class DynaBall(init_coord:Vec, val radius:Int, mass:Float = 1, restitution:Boolean = property("physics.restitution", true)) extends Physical {
11 | val body = new Body(new Circle(radius), mass)
12 | if(restitution) body.setRestitution(1.0f)
13 | body.setPosition(init_coord.x, init_coord.y)
14 |
15 | def points = Array(coord)
16 | }
--------------------------------------------------------------------------------
/src/main/scala/com/github/dunnololda/scage/support/messages/unicode/Effect.java:
--------------------------------------------------------------------------------
1 | package com.github.dunnololda.scage.support.messages.unicode;
2 |
3 | import java.awt.*;
4 | import java.awt.image.BufferedImage;
5 |
6 | /**
7 | * A graphical effect that is applied to glyphs in a {@link UnicodeFont}.
8 | *
9 | * @author Nathan Sweet
10 | */
11 | public interface Effect {
12 | /**
13 | * Called to draw the effect.
14 | *
15 | * @param image The image to draw into
16 | * @param g The graphics context to use for applying the effect
17 | * @param unicodeFont The font being rendered
18 | * @param glyph The particular glyph being rendered
19 | */
20 | public void draw (BufferedImage image, Graphics2D g, UnicodeFont unicodeFont, Glyph glyph);
21 | }
22 |
23 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/dunnololda/scage/support/physics/objects/StaticLine.scala:
--------------------------------------------------------------------------------
1 | package com.github.dunnololda.scage.support.physics.objects
2 |
3 | import net.phys2d.raw.shapes.Line
4 | import net.phys2d.raw.StaticBody
5 | import com.github.dunnololda.scage.support.Vec
6 | import com.github.dunnololda.scage.support.physics.Physical
7 | import com.github.dunnololda.cli.AppProperties._
8 |
9 | class StaticLine(start:Vec, end:Vec, restitution:Boolean = property("physics.restitution", true)) extends Physical {
10 | private val line = new Line((end-start).x, (end-start).y)
11 |
12 | val body = new StaticBody("line", line)
13 | if(restitution) body.setRestitution(1.0f)
14 | body.setPosition(start.x, start.y)
15 |
16 | def points = line.getVertices(body.getPosition, body.getRotation).map(v => Vec(v.getX, v.getY))
17 | }
--------------------------------------------------------------------------------
/src/main/scala/com/github/dunnololda/scage/support/physics/objects/StaticBox.scala:
--------------------------------------------------------------------------------
1 | package com.github.dunnololda.scage.support.physics.objects
2 |
3 | import net.phys2d.raw.StaticBody
4 | import net.phys2d.raw.shapes.Box
5 | import com.github.dunnololda.scage.support.Vec
6 | import com.github.dunnololda.scage.support.physics.Physical
7 | import com.github.dunnololda.cli.AppProperties._
8 |
9 | class StaticBox(init_coord:Vec, val box_width:Float, val box_height:Float, restitution:Boolean = property("physics.restitution", true)) extends Physical {
10 | val box = new Box(box_width, box_height)
11 |
12 | val body = new StaticBody("StaticBox", box)
13 | if(restitution) body.setRestitution(1.0f)
14 | body.setPosition(init_coord.x, init_coord.y)
15 |
16 | def points = box.getPoints(body.getPosition, body.getRotation).map(v => Vec(v.getX, v.getY))
17 | }
--------------------------------------------------------------------------------
/src/main/scala/com/github/dunnololda/scage/support/physics/objects/DynaBox.scala:
--------------------------------------------------------------------------------
1 | package com.github.dunnololda.scage.support.physics.objects
2 |
3 | import com.github.dunnololda.scage.support.Vec
4 | import com.github.dunnololda.scage.support.physics.Physical
5 | import net.phys2d.raw.Body
6 | import net.phys2d.raw.shapes.Box
7 | import java.lang.Float
8 | import com.github.dunnololda.cli.AppProperties._
9 |
10 | class DynaBox(init_coord:Vec, val box_width:Float, val box_height:Float, val box_mass:Float = 1, restitution:Boolean = property("physics.restitution", true)) extends Physical {
11 | val box = new Box(box_width, box_height)
12 | val body = new Body(box, box_mass)
13 | if(restitution) body.setRestitution(1.0f)
14 | body.setPosition(init_coord.x, init_coord.y)
15 |
16 | def points = box.getPoints(body.getPosition, body.getRotation).map(v => Vec(v.getX, v.getY))
17 | }
--------------------------------------------------------------------------------
/src/main/scala/com/github/dunnololda/scage/support/XInitThreadsCaller.scala:
--------------------------------------------------------------------------------
1 | package com.github.dunnololda.scage.support
2 |
3 | import com.github.dunnololda.mysimplelogger.MySimpleLogger.MySimpleLogger
4 |
5 | object XInitThreadsCaller {
6 | def xInitThreads(scage_log: MySimpleLogger): Unit = {
7 | if ("sun.awt.X11.XToolkit" == System.getProperty("awt.toolkit")) {
8 | // in Windows: sun.awt.windows.WToolkit
9 | if ("32" == System.getProperty("sun.arch.data.model")) {
10 | scage_log.info("32 bit linux detected, performing XInitThreads() call to make multi-threading work by loading library libxx32.so")
11 | System.loadLibrary("xx32")
12 | } else if ("64" == System.getProperty("sun.arch.data.model")) {
13 | scage_log.info("64 bit linux detected, performing XInitThreads() call to make multi-threading work by loading library libxx64.so")
14 | System.loadLibrary("xx64")
15 | } else {
16 | scage_log.warn("linux of unknown arch detected, don't now how to perform XInitThreads() call, so perform nothing. Maybe the app will crash. Sorry.")
17 | }
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/test/resources/resources/interfaces/scagetest_interfaces_en.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Use WASD to move,
5 | Le[rft Mo[yuse \[Butto\]n to s[ghoo]t and
6 | Mous]e Wheel to zo]om
7 |
8 |
9 |
10 | Special [{GREEN}gre[{RED}en-re]d] строка
11 | Special \[{GREEN}gre[{RED}en-re]d\] строка
12 | Special [{GREEN}gre\[{RED}en-re\]d] строка
13 | Special \[{GREEN}gre\[{RED}en-re\]d\] строка
14 |
15 |
16 | Location: $0
17 |
18 |
19 | Point: $0
20 |
21 |
22 | FPS: $0
23 |
24 |
25 |
26 | F1: [gInp[rut te]xt]
27 |
28 |
29 | Your text: $0
30 |
31 |
32 |
--------------------------------------------------------------------------------
/src/test/resources/resources/interfaces/scagetest_interfaces_ru.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | WASD и Ле[rвая К[yн\[bопк\]а Мы\\[gши,
5 | Кол\\]есико - пр]иблизить/отд]алить.
6 |
7 |
8 |
9 | Cпециальная [{GREEN}зел[{RED}ено-кра]сная] строка
10 | Cпециальная \[{GREEN}зел[{RED}ено-кра]сная\] строка
11 | Cпециальная [{GREEN}зел\[{RED}ено-кра\]сная] строка
12 | Cпециальная \[{GREEN}зел\[{RED}ено-кра\]сная\] строка
13 |
14 |
15 | Позиция: $0
16 |
17 |
18 | Точка: $0
19 |
20 |
21 | FPS: $0
22 |
23 |
24 |
25 | F1: [gВве[rдите те]кст]
26 |
27 |
28 | Your text: $0
29 |
30 |
31 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/dunnololda/scage/support/physics/objects/StaticPolygon.scala:
--------------------------------------------------------------------------------
1 | package com.github.dunnololda.scage.support.physics.objects
2 |
3 | import com.github.dunnololda.scage.support.Vec
4 | import com.github.dunnololda.scage.support.physics.Physical
5 | import net.phys2d.raw.shapes.Polygon
6 | import net.phys2d.raw.StaticBody
7 | import com.github.dunnololda.cli.AppProperties._
8 |
9 | class StaticPolygon(restitution:Boolean, vertices:List[Vec]) extends Physical {
10 | def this(vertices:Vec*) {this(property("physics.restitution", true), vertices.toList)}
11 | def this(vertices:Array[Vec]) {this(property("physics.restitution", true), vertices.toList)}
12 | def this(vertices:List[Vec]) {this(property("physics.restitution", true), vertices)}
13 |
14 | private val polygon_vertices = for {
15 | vertice <- vertices
16 | new_vertice = vertice - vertices(0)
17 | } yield new_vertice
18 | private val polygon = new Polygon(polygon_vertices.map(_.toPhys2dVec).toArray)
19 |
20 | val body = new StaticBody("StaticPolygon", polygon)
21 | if(restitution) body.setRestitution(1.0f)
22 | body.setPosition(vertices(0).x, vertices(0).y)
23 |
24 | def points = polygon.getVertices(body.getPosition, body.getRotation).map(v => Vec(v.getX, v.getY))
25 | }
--------------------------------------------------------------------------------
/src/main/scala/com/github/dunnololda/scage/support/PathFinder.scala:
--------------------------------------------------------------------------------
1 | package com.github.dunnololda.scage.support
2 |
3 | import org.newdawn.slick.util.pathfinding.{PathFindingContext, TileBasedMap, AStarPathFinder}
4 | import collection.mutable
5 |
6 | class PathFinder(width:Int, height:Int, val is_blocked:(Int, Int) => Boolean = (x, y) => false, val cost:(Int, Int) => Float = (x, y) => 1f) {
7 | private val slick_astar_path_finder = new AStarPathFinder(new TileBasedMap {
8 | def getWidthInTiles = width
9 | def getHeightInTiles = height
10 | def pathFinderVisited(x:Int, y:Int) {}
11 | def blocked(context:PathFindingContext, tx:Int, ty:Int) = is_blocked(tx, ty)
12 | def getCost(context:PathFindingContext, tx:Int, ty:Int) = cost(tx, ty)
13 | }, 100500, true)
14 |
15 | def findPath(p1:Vec, p2:Vec) = {
16 | val slick_path = slick_astar_path_finder.findPath(null, p1.ix, p1.iy, p2.ix, p2.iy)
17 | if(slick_path != null) mutable.Stack((for(i <- 0 until slick_path.getLength) yield Vec(slick_path.getX(i), slick_path.getY(i))):_*)
18 | else mutable.Stack[Vec]()
19 | }
20 | }
21 |
22 | object PathFinder {
23 | def apply(width:Int, height:Int, is_blocked:(Int, Int) => Boolean = (x, y) => false, cost:(Int, Int) => Float = (x, y) => 1f) = {
24 | new PathFinder(width, height, is_blocked, cost)
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/dunnololda/scage/support/tracer3/Trace.scala:
--------------------------------------------------------------------------------
1 | package com.github.dunnololda.scage.support.tracer3
2 |
3 | import com.github.dunnololda.scage.support.ScageId._
4 | import com.github.dunnololda.scage.support.Vec
5 | import com.github.dunnololda.state.State
6 |
7 | trait TraceTrait {
8 | type ChangerType <: TraceTrait // changer type must be the type of actual Trace's child in client code
9 | def changeState(changer:ChangerType, state:State) // maybe 'changeState' is not the right name..
10 | def state:State
11 |
12 | final val id = nextId // make it final because it is very important to keep it completely unique!
13 | private[tracer3] var _location = Vec(0, 0)
14 | def location:Vec = _location
15 |
16 | override def toString = getClass.getName+"(id="+id+", state="+state+")"
17 | }
18 |
19 | trait Trace extends TraceTrait {
20 | type ChangerType = Trace
21 | }
22 |
23 | trait DefaultTrace extends Trace {
24 | def state = State()
25 | def changeState(changer:Trace, state:State) {}
26 | }
27 |
28 | object Trace {
29 | def apply(changeState:(Trace, State) => Unit = (changer, state) => {}, state: => State = State()) = {
30 | val (_changeState, _state) = (changeState, state)
31 | new Trace {
32 | def changeState(changer:Trace, state:State) {_changeState(changer, state)}
33 | def state:State = _state
34 | }
35 | }
36 | }
--------------------------------------------------------------------------------
/src/main/scala/com/github/dunnololda/scage/support/messages/unicode/ConfigurableEffect.java:
--------------------------------------------------------------------------------
1 | package com.github.dunnololda.scage.support.messages.unicode;
2 |
3 | import java.util.List;
4 |
5 | /**
6 | * An effect that has a number of configuration values. This allows the effect to be configured in the Hiero GUI and to be saved
7 | * and loaded to and from a file.
8 | *
9 | * @author Nathan Sweet
10 | */
11 | public interface ConfigurableEffect extends Effect {
12 | /**
13 | * Returns the list of {@link Value}s for this effect. This list is not typically backed by the effect, so changes to the
14 | * values will not take affect until {@link #setValues(List)} is called.
15 | */
16 | public List getValues();
17 |
18 | /**
19 | * Sets the list of {@link Value}s for this effect.
20 | */
21 | public void setValues(List values);
22 |
23 | /**
24 | * Represents a configurable value for an effect.
25 | */
26 | static public interface Value {
27 | /**
28 | * Returns the name of the value.
29 | */
30 | public String getName ();
31 |
32 | /**
33 | * Sets the string representation of the value.
34 | */
35 | public void setString (String value);
36 |
37 | /**
38 | * Gets the string representation of the value.
39 | */
40 | public String getString ();
41 |
42 | /**
43 | * Gets the object representation of the value.
44 | */
45 | public Object getObject ();
46 |
47 | /**
48 | * Shows a dialog allowing a user to configure this value.
49 | */
50 | public void showDialog ();
51 | }
52 | }
53 |
54 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/dunnololda/scage/support/ScageColorTest.scala:
--------------------------------------------------------------------------------
1 | package com.github.dunnololda.scage.support
2 |
3 | import com.github.dunnololda.scage.ScageLib._
4 |
5 | object ScageColorTest extends ScageScreenApp("Color Test") {
6 | val fields = ScageColor.getClass.getDeclaredFields
7 | val colors = fields.map(f => {
8 | f.setAccessible(true)
9 | try{f.get(ScageColor).asInstanceOf[ScageColor]}
10 | catch {
11 | case ex:Exception => WHITE
12 | }
13 | })
14 |
15 | var color_num = 1
16 | interface {
17 | if(color_num >= 0 && color_num < fields.length) {
18 | print(colors(color_num), 20, windowHeight/2,
19 | if("BLACK".equalsIgnoreCase(fields(color_num).getName)) WHITE else BLACK)
20 | try {backgroundColor = colors(color_num)}
21 | catch {
22 | case ex:java.lang.Exception =>
23 | }
24 | }
25 | }
26 |
27 | key(KEY_LEFT, onKeyDown = {
28 | def nextColorNumInc() {
29 | if(color_num < fields.length - 1) color_num += 1
30 | else color_num = 0
31 | if(colors(color_num) != null && (!WHITE.equals(colors(color_num)) || "WHITE".equalsIgnoreCase(fields(color_num).getName))) {}
32 | else nextColorNumInc()
33 | }
34 | nextColorNumInc()
35 | })
36 | key(KEY_RIGHT, onKeyDown = {
37 | def nextColorNumDec() {
38 | if(color_num > 0) color_num -= 1
39 | else color_num = fields.length - 1
40 | if(!WHITE.equals(colors(color_num)) || "WHITE".equalsIgnoreCase(fields(color_num).getName)) {}
41 | else nextColorNumDec()
42 | }
43 | nextColorNumDec()
44 | })
45 | key(KEY_ESCAPE, onKeyDown = stop())
46 | }
--------------------------------------------------------------------------------
/src/main/scala/com/github/dunnololda/scage/support/parsers/VecParser.scala:
--------------------------------------------------------------------------------
1 | package com.github.dunnololda.scage.support.parsers
2 |
3 | import util.parsing.combinator.JavaTokenParsers
4 | import com.github.dunnololda.scage.support.{DVec, Vec}
5 | import com.github.dunnololda.mysimplelogger.MySimpleLogger
6 |
7 | class VecParser extends JavaTokenParsers {
8 | private val log = MySimpleLogger(this.getClass.getName)
9 |
10 | private lazy val vec:Parser[Vec] =
11 | "["~floatingPointNumber~","~floatingPointNumber~"]" ^^ {case "["~x~","~y~"]" => new Vec(x.toFloat, y.toFloat)}
12 |
13 | def evaluate(vec_str:String) = parseAll(vec, vec_str) match {
14 | case Success(result, _) =>
15 | log.debug("successfully parsed "+result+" from string "+vec_str)
16 | Some(result)
17 | case x @ Failure(msg, _) =>
18 | log.error("failed to parse Vec from stirng "+vec_str)
19 | None
20 | case x @ Error(msg, _) =>
21 | log.error("failed to parse Vec from stirng "+vec_str)
22 | None
23 | }
24 | }
25 |
26 | class DVecParser extends JavaTokenParsers {
27 | private val log = MySimpleLogger(this.getClass.getName)
28 |
29 | private lazy val dvec:Parser[DVec] =
30 | "["~floatingPointNumber~","~floatingPointNumber~"]" ^^ {case "["~x~","~y~"]" => new DVec(x.toDouble, y.toDouble)}
31 |
32 | def evaluate(vec_str:String) = parseAll(dvec, vec_str) match {
33 | case Success(result, _) =>
34 | log.debug("successfully parsed "+result+" from string "+vec_str)
35 | Some(result)
36 | case x @ Failure(msg, _) =>
37 | log.error("failed to parse Vec from stirng "+vec_str)
38 | None
39 | case x @ Error(msg, _) =>
40 | log.error("failed to parse Vec from stirng "+vec_str)
41 | None
42 | }
43 | }
44 |
45 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/dunnololda/scage/support/messages/unicode/ColorEffect.java:
--------------------------------------------------------------------------------
1 | package com.github.dunnololda.scage.support.messages.unicode;
2 |
3 | import org.newdawn.slick.font.effects.EffectUtil;
4 | import java.awt.*;
5 | import java.awt.image.BufferedImage;
6 | import java.util.ArrayList;
7 | import java.util.List;
8 |
9 | /**
10 | * Makes glyphs a solid color.
11 | *
12 | * @author Nathan Sweet
13 | */
14 | public class ColorEffect implements ConfigurableEffect {
15 | /** The colour that will be applied across the text */
16 | private Color color = Color.white;
17 |
18 | /**
19 | * Default constructor for injection
20 | */
21 | public ColorEffect() {
22 | }
23 |
24 | /**
25 | * Create a new effect to colour the text
26 | *
27 | * @param color The colour to apply across the text
28 | */
29 | public ColorEffect(Color color) {
30 | this.color = color;
31 | }
32 |
33 | /**
34 | * @see org.newdawn.slick.font.effects.Effect#draw(java.awt.image.BufferedImage, java.awt.Graphics2D, org.newdawn.slick.UnicodeFont, org.newdawn.slick.font.Glyph)
35 | */
36 | public void draw(BufferedImage image, Graphics2D g, UnicodeFont unicodeFont, Glyph glyph) {
37 | g.setColor(color);
38 | g.fill(glyph.getShape());
39 | }
40 |
41 | /**
42 | * Get the colour being applied by this effect
43 | *
44 | * @return The colour being applied by this effect
45 | */
46 | public Color getColor() {
47 | return color;
48 | }
49 |
50 | /**
51 | * Set the colour being applied by this effect
52 | *
53 | * @param color The colour being applied by this effect
54 | */
55 | public void setColor(Color color) {
56 | if (color == null) throw new IllegalArgumentException("color cannot be null.");
57 | this.color = color;
58 | }
59 |
60 | /**
61 | * @see java.lang.Object#toString()
62 | */
63 | public String toString () {
64 | return "Color";
65 | }
66 |
67 | /**
68 | * @see org.newdawn.slick.font.effects.ConfigurableEffect#getValues()
69 | */
70 | public List getValues() {
71 | List values = new ArrayList();
72 | values.add(EffectUtil.colorValue("Color", color));
73 | return values;
74 | }
75 |
76 | /**
77 | * @see org.newdawn.slick.font.effects.ConfigurableEffect#setValues(java.util.List)
78 | */
79 | public void setValues(List values) {
80 | for (Object value1 : values) {
81 | Value value = (Value) value1;
82 | if (value.getName().equals("Color")) {
83 | setColor((Color) value.getObject());
84 | }
85 | }
86 | }
87 | }
88 |
89 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/dunnololda/scage/support/messages/unicode/MyImage.java:
--------------------------------------------------------------------------------
1 | package com.github.dunnololda.scage.support.messages.unicode;
2 |
3 | import org.newdawn.slick.SlickException;
4 |
5 | public class MyImage extends org.newdawn.slick.Image
6 | {
7 | public MyImage()
8 | {
9 | super();
10 | }
11 |
12 | public MyImage(int width, int height) throws SlickException
13 | {
14 | super(width, height);
15 | }
16 |
17 | @Override protected void initImpl()
18 | {
19 |
20 | }
21 |
22 | public void drawEmbedded(float x, float y, float width, float height, int size)
23 | {
24 | init();
25 | if (corners == null) {
26 | GL.glTexCoord2f(textureOffsetX, textureOffsetY);
27 | GL.glVertex3f(x, -y + size, 0);
28 |
29 | GL.glTexCoord2f(textureOffsetX, textureOffsetY + textureHeight);
30 | GL.glVertex3f(x, -y - height + size, 0);
31 |
32 | GL.glTexCoord2f(textureOffsetX + textureWidth, textureOffsetY + textureHeight);
33 | GL.glVertex3f(x + width, -y - height + size, 0);
34 |
35 | GL.glTexCoord2f(textureOffsetX + textureWidth, textureOffsetY);
36 | GL.glVertex3f(x + width, -y + size, 0);
37 | } else {
38 | corners[TOP_LEFT].bind();
39 | GL.glTexCoord2f(textureOffsetX, textureOffsetY);
40 | GL.glVertex3f(x, y, 0);
41 |
42 | corners[BOTTOM_LEFT].bind();
43 | GL.glTexCoord2f(textureOffsetX, textureOffsetY + textureHeight);
44 | GL.glVertex3f(x, y + height, 0);
45 |
46 | corners[BOTTOM_RIGHT].bind();
47 | GL.glTexCoord2f(textureOffsetX + textureWidth, textureOffsetY + textureHeight);
48 | GL.glVertex3f(x + width, y + height, 0);
49 |
50 | corners[TOP_RIGHT].bind();
51 | GL.glTexCoord2f(textureOffsetX + textureWidth, textureOffsetY);
52 | GL.glVertex3f(x + width, y, 0);
53 |
54 |
55 |
56 |
57 | }
58 | }
59 |
60 | @Override public MyImage getSubImage(int x,int y,int width,int height) {
61 | init();
62 |
63 | float newTextureOffsetX = ((x / (float) this.width) * textureWidth) + textureOffsetX;
64 | float newTextureOffsetY = ((y / (float) this.height) * textureHeight) + textureOffsetY;
65 | float newTextureWidth = ((width / (float) this.width) * textureWidth);
66 | float newTextureHeight = ((height / (float) this.height) * textureHeight);
67 |
68 | MyImage sub = new MyImage();
69 | sub.inited = true;
70 | sub.texture = this.texture;
71 | sub.textureOffsetX = newTextureOffsetX;
72 | sub.textureOffsetY = newTextureOffsetY;
73 | sub.textureWidth = newTextureWidth;
74 | sub.textureHeight = newTextureHeight;
75 |
76 | sub.width = width;
77 | sub.height = height;
78 | sub.ref = ref;
79 | sub.centerX = width / 2;
80 | sub.centerY = height / 2;
81 |
82 | return sub;
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/dunnololda/scage/support/physics/Physical.scala:
--------------------------------------------------------------------------------
1 | package com.github.dunnololda.scage.support.physics
2 |
3 | import net.phys2d.math.Vector2f
4 | import com.github.dunnololda.scage.support.Vec
5 | import net.phys2d.raw.{CollisionEvent, Body, BodyList}
6 | import collection.mutable.ArrayBuffer
7 |
8 | trait Physical {
9 | def body:Body
10 |
11 | def addForce(force:Vec) {
12 | body.setIsResting(false)
13 | body.addForce(new Vector2f(force.x, force.y))
14 | }
15 |
16 | def coord = {
17 | val pos = body.getPosition
18 | Vec(pos.getX, pos.getY)
19 | }
20 | def coord_=(new_coord:Vec) {
21 | body.move(new_coord.x, new_coord.y)
22 | }
23 | def move(delta:Vec) {
24 | val new_coord = coord + delta
25 | body.move(new_coord.x, new_coord.y)
26 | }
27 |
28 | def velocity = {
29 | val vel = body.getVelocity
30 | Vec(vel.getX, vel.getY)
31 | }
32 | def velocity_=(new_velocity:Vec) {
33 | val delta = new_velocity - velocity
34 | body.adjustVelocity(new Vector2f(delta.x, delta.y))
35 | }
36 |
37 | private var is_touching = false
38 |
39 | private val touching_bodies = ArrayBuffer[Body]()
40 | def touchingBodies = touching_bodies.toList
41 |
42 | private val touching_points = ArrayBuffer[(Vec, Vec)]()
43 | def isTouching = is_touching
44 | def isTouching(p:Physical) = touching_bodies.contains(p.body)
45 |
46 | private[physics] def clearTouches() { // will remove private modifier if needed
47 | is_touching = false
48 | touching_bodies.clear()
49 | touching_points.clear()
50 | }
51 |
52 | private[physics] def updateCollisions(collisions:Array[CollisionEvent]) {
53 | val new_touching_bodies = body.getTouching
54 | is_touching = new_touching_bodies.size > 0
55 | if(is_touching) {
56 | for {
57 | i <- 0 until new_touching_bodies.size
58 | body = new_touching_bodies.get(i)
59 | if !touching_bodies.contains(body)
60 | } touching_bodies += body
61 | for {
62 | ce <- collisions
63 | new_tp = (new Vec(ce.getPoint), new Vec(ce.getNormal))
64 | if !touching_points.exists(tp => tp._1 == new_tp._1 && tp._2 == new_tp._2)
65 | } touching_points += new_tp
66 | }
67 | }
68 |
69 | /*private[physics] def isTouching_=(new_is_touching:Boolean) {
70 | is_touching = new_is_touching
71 | if(is_touching) {
72 | val new_touching_bodies = body.getTouching
73 | for {
74 | i <- 0 until new_touching_bodies.size
75 | body = new_touching_bodies.get(i)
76 | if !touching_bodies.contains(body)
77 | } touching_bodies.add(body)
78 | }
79 | else touching_bodies.clear()
80 | }*/
81 |
82 | def points:Array[Vec]
83 |
84 | def touchingPoints = touching_points.toList
85 | }
--------------------------------------------------------------------------------
/project/Build.scala:
--------------------------------------------------------------------------------
1 | import sbt._
2 | import Keys._
3 | import io.Source
4 | import Classpaths.managedJars
5 |
6 | // All credits for some parts of the code go to philcali
7 | // @see https://github.com/philcali/sbt-lwjgl-plugin/
8 | object ScageBuild extends Build {
9 | lazy val phys2DPatch = TaskKey[Unit]("phys2D-patch", "The phys2d dependency pom is broken. Patch aims to fix it")
10 |
11 | lazy val nativesExtract = TaskKey[Unit]("natives-extract", "Extracts LWJGL Native JAR file")
12 |
13 | def defineOs = System.getProperty("os.name").toLowerCase.take(3).toString match {
14 | case "lin" => ("linux", "so")
15 | case "mac" | "dar" => ("osx", "lib")
16 | case "win" => ("windows", "dll")
17 | case "sun" => ("solaris", "so")
18 | case _ => ("unknown", "")
19 | }
20 |
21 | lazy val baseSettings: Seq[Setting[_]] = Defaults.defaultSettings ++ Seq(
22 | phys2DPatch <<= (streams, ivyPaths) map { (s, ivys) =>
23 | val base = ivys.ivyHome.getOrElse(Path.userHome / ".ivy2")
24 |
25 | val path = base / "cache" / "phys2d" / "phys2d" / "ivy-060408.xml"
26 |
27 | if (path.exists) {
28 | s.log.info("Patching %s ..." format(path))
29 | val pattern = "zip".r
30 | val ivysource = Source.fromFile(path)
31 | val text = ivysource.getLines.mkString
32 | val writer = new java.io.FileWriter(path)
33 | writer.write(pattern.replaceAllIn(text, "jar"))
34 | writer.close
35 | s.log.info("Done.")
36 | } else {
37 | s.log.warn("Update might fail. This is expected.")
38 | s.log.warn("Please run update one more time.")
39 | }
40 | },
41 |
42 | nativesExtract <<= (streams, classpathTypes, update) map { (s, ct, up) =>
43 | val natives = managedJars(Compile, ct, up) map { _.data } find { (j) =>
44 | j.getName.startsWith("lwjgl-platform") && j.getName.endsWith("%s.jar".format(defineOs._1))
45 | }
46 | natives map { (e) =>
47 | val target = file(".") / "libs" / "natives"
48 | s.log.info("Extracting LWJGL natives to " + target)
49 | IO.unzip(e, target)
50 | } getOrElse {
51 | s.log.warn("Unable to find LWJGL natives jar, try to update again.")
52 | }
53 | },
54 |
55 | // TODO should run on "update" only.
56 | update <<= update dependsOn phys2DPatch,
57 | // TODO : UH ?
58 | run <<= run in Runtime dependsOn nativesExtract,
59 | fork := true,
60 | javaOptions ++= Seq("-Djava.library.path=%s".format(file(".") / "libs" / "natives"), "-DLWJGL_DISABLE_XRANDR=true"),
61 | javaOptions in Test ++= Seq("-Dapp.properties=scagetest-properties.txt")
62 | )
63 |
64 | lazy val project = Project (
65 | "scage",
66 | file ("."),
67 | settings = baseSettings
68 | )
69 |
70 | }
71 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/dunnololda/scage/support/Events.scala:
--------------------------------------------------------------------------------
1 | package com.github.dunnololda.scage.support
2 |
3 | import com.github.dunnololda.mysimplelogger.MySimpleLogger
4 | import com.github.dunnololda.scage.support.ScageId._
5 |
6 | import scala.collection.mutable
7 |
8 | trait EventsTrait {
9 | def onEventWithArguments(event_name: String)(event_action: PartialFunction[Any, Unit]):(String, Int)
10 | def onEvent(event_name: String)(event_action: => Unit):(String, Int)
11 | def callEvent(event_name: String, arg: Any)
12 | def callEvent(event_name: String)
13 | def delEvents(event_ids: (String, Int)*)
14 | }
15 |
16 | object Events extends EventsTrait {
17 | private val log = MySimpleLogger(this.getClass.getName)
18 | private val events = mutable.HashMap[String, mutable.HashMap[Int, PartialFunction[Any, Unit]]]()
19 |
20 | def onEventWithArguments(event_name: String)(event_action: PartialFunction[Any, Unit]):(String, Int) = {
21 | val event_id = nextId
22 | (events.get(event_name) match {
23 | case Some(events_for_name) =>
24 | events_for_name += (event_id -> event_action)
25 | case None => events += (event_name -> mutable.HashMap(event_id -> event_action))
26 | }): Unit // this fixes some very huge compilation problem (very slow compilation)
27 | (event_name, event_id)
28 | }
29 |
30 | def onEvent(event_name: String)(event_action: => Unit):(String, Int) = {
31 | val event_id = nextId
32 | (events.get(event_name) match {
33 | case Some(events_for_name) => events_for_name += (event_id -> {
34 | case _ => event_action
35 | })
36 | case None => events += (event_name -> mutable.HashMap(event_id -> {
37 | case _ => event_action
38 | }))
39 | }): Unit
40 | (event_name, event_id)
41 | }
42 |
43 | def callEvent(event_name: String, arg: Any) {
44 | events.get(event_name) match {
45 | case Some(events_for_name) =>
46 | for (event <- events_for_name.values) event(arg) // fail-fast if not matched!
47 | case None => //log.warn("event "+event_name+" not found")
48 | }
49 | }
50 |
51 | def callEvent(event_name: String) {
52 | events.get(event_name) match {
53 | case Some(events_for_name) =>
54 | for (event <- events_for_name.values) event(()) // fail-fast if not matched!
55 | case None => //log.warn("event "+event_name+" not found")
56 | }
57 | }
58 |
59 | def delEvents(event_ids: (String, Int)*) {
60 | for ((event_name, event_id) <- event_ids) {
61 | events.get(event_name) match {
62 | case Some(events_for_name) =>
63 | if (events_for_name.contains(event_id)) {
64 | events_for_name -= event_id
65 | log.debug("deleted event for name " + event_name + " with id " + event_id)
66 | } else {
67 | log.warn("event for name " + event_name + " with id " + event_id + " not found among events so wasn't deleted")
68 | }
69 | case None =>
70 | log.warn("events for name " + event_name + " not found so event with id " + event_id + " wasn't deleted")
71 | }
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/dunnololda/scage/support/SortedBuffer.scala:
--------------------------------------------------------------------------------
1 | package com.github.dunnololda.scage.support
2 |
3 | import com.github.dunnololda.scage.ScageOperation
4 |
5 | import scala.collection.generic.{Growable, Shrinkable}
6 | import scala.collection.{Set, mutable}
7 |
8 | class SortedBuffer(init_arr:ScageOperation*) extends Iterable[ScageOperation] with Growable[ScageOperation] with Shrinkable[ScageOperation] {
9 | private val m = new java.util.TreeMap[Int, java.util.TreeMap[Int, ScageOperation]]
10 | private val position_by_op_id = mutable.HashMap[Int, Int]()
11 | private var _length = 0
12 |
13 | def operationIdsSet:Set[Int] = position_by_op_id.keySet
14 | def operationIdsSeq:Seq[Int] = position_by_op_id.keys.toSeq
15 | def operationIdsList:List[Int] = position_by_op_id.keys.toList
16 | def operationIdsIterable:Iterable[Int] = position_by_op_id.keys
17 |
18 | init_arr.foreach(elem => {
19 | this += elem
20 | })
21 |
22 | def +=(elem: ScageOperation): this.type = {
23 | val a = m.get(elem.position) // O(logN)
24 | if(a == null) {
25 | m.put(elem.position, new java.util.TreeMap[Int, ScageOperation] {put(elem.op_id, elem)}) // O(logN)
26 | } else {
27 | a.put(elem.op_id, elem) // O(logN)
28 | }
29 | position_by_op_id += elem.op_id -> elem.position // O(1)
30 | _length += 1 // O(1)
31 | this
32 | }
33 |
34 | def clear(): Unit = {
35 | m.clear()
36 | position_by_op_id.clear()
37 | _length = 0
38 | }
39 |
40 | def remove(op_id: Int): Option[ScageOperation] = {
41 | position_by_op_id.get(op_id) match { // O(1)
42 | case Some(position) =>
43 | val a = m.get(position) // O(logN)
44 | if(a != null) {
45 | val ans = Option(a.remove(op_id))
46 | position_by_op_id -= op_id // O(1)
47 | if(a.isEmpty) {
48 | m.remove(position) // O(logN)
49 | }
50 | _length -= 1
51 | ans
52 | } else None
53 | case None =>
54 | None
55 | }
56 | }
57 |
58 | def -=(elem: ScageOperation): this.type = {
59 | this.remove(elem.op_id)
60 | this
61 | }
62 |
63 | def iterator: Iterator[ScageOperation] = if(m.isEmpty) {
64 | new Iterator[ScageOperation] {
65 | def hasNext: Boolean = false
66 | def next(): ScageOperation = throw new NoSuchElementException("next on empty iterator")
67 | }
68 | } else {
69 | new Iterator[ScageOperation] {
70 | private val buffers_iterator = m.values().iterator()
71 | private var current_buffer_iterator = buffers_iterator.next().values().iterator()
72 |
73 | def hasNext: Boolean = current_buffer_iterator.hasNext || buffers_iterator.hasNext
74 |
75 | def next(): ScageOperation = {
76 | if(current_buffer_iterator.hasNext) {
77 | current_buffer_iterator.next()
78 | } else {
79 | current_buffer_iterator = buffers_iterator.next().values().iterator
80 | current_buffer_iterator.next()
81 | }
82 | }
83 | }
84 | }
85 |
86 | def length: Int = _length
87 | }
88 |
89 | object SortedBuffer {
90 | def apply(init_arr:ScageOperation*) = new SortedBuffer(init_arr:_*)
91 | }
--------------------------------------------------------------------------------
/src/main/scala/com/github/dunnololda/scage/support/physics/ScagePhysics.scala:
--------------------------------------------------------------------------------
1 | package com.github.dunnololda.scage.support.physics
2 |
3 | import net.phys2d.raw.World
4 | import com.github.dunnololda.cli.AppProperties._
5 | import net.phys2d.math.Vector2f
6 | import net.phys2d.raw.strategies.QuadSpaceStrategy
7 | import com.github.dunnololda.scage.support.Vec
8 | import com.github.dunnololda.mysimplelogger.MySimpleLogger
9 | import collection.mutable
10 |
11 | object ScagePhysics {
12 | def str2vec(s:String):Vec = {
13 | val coords = s.replace("[", "").replace("]", "").split(",")
14 | try {
15 | Vec(coords(0).trim.toFloat, coords(1).trim.toFloat)
16 | } catch {
17 | case e:Exception => Vec.zero
18 | }
19 | }
20 |
21 | def apply(dt:Int = property("physics.dt", 5),
22 | gravity:Vec = str2vec(property("physics.gravity", "[0, 0]"))) = {
23 | new ScagePhysics(dt, gravity)
24 | }
25 | }
26 |
27 | class ScagePhysics(
28 | val dt:Int = property("physics.dt", 5), // see exactly no purposes to make it changeable. If I find any - I will do it.
29 | val gravity:Vec = ScagePhysics.str2vec(property("physics.gravity", "[0, 0]"))
30 | ) {
31 | private val log = MySimpleLogger(this.getClass.getName)
32 |
33 | /*def dt = _dt
34 | def dt_=(new_dt:Int) {
35 | if(new_dt > 0) dt = new_dt
36 | else log.error("failed to update dt: must be more then zero but the value is "+new_dt)
37 | }*/
38 |
39 | val world = new World(new Vector2f(gravity.x, gravity.y), 10, new QuadSpaceStrategy(20,10))
40 | world.enableRestingBodyDetection(0.01f, 0.2f, 0.2f)
41 |
42 | private val _physicals = mutable.HashSet[Physical]()
43 | def physicals = _physicals.toList
44 | def addPhysical(physical:Physical) = {
45 | if(!world.getBodies.contains(physical.body)) world.add(physical.body)
46 | _physicals += physical
47 | physical.clearTouches()
48 | log.debug("added new physical "+physical)
49 | physical
50 | }
51 | def addPhysicals(physicals:Physical*) {
52 | physicals.foreach(addPhysical)
53 | }
54 |
55 | // TODO: мб запилить по аналогии removePhysical, возвращающий, кого удалил.
56 | // TODO: И метод, принимающий условие в качестве параметра. И все такое
57 | def removePhysicals(physicals_to_delete:Physical*) {
58 | for(p <- physicals_to_delete) {
59 | if(_physicals.contains(p)) {
60 | world.remove(p.body)
61 | _physicals -= p
62 | log.debug("removed physical "+p)
63 | } else {
64 | log.warn("physical "+p+" not found")
65 | }
66 | }
67 | }
68 | def removeAll() {
69 | world.clear()
70 | _physicals.clear()
71 | log.info("deleted all physical objects")
72 | }
73 |
74 | def containsPhysical(p:Physical) = _physicals.contains(p)
75 |
76 | def step() {
77 | _physicals.foreach(_.clearTouches())
78 |
79 | for(i <- 1 to dt) {
80 | world.step()
81 | for(p <- _physicals) {
82 | p.updateCollisions(world.getContacts(p.body))
83 | }
84 | }
85 | }
86 |
87 | def touchingPoints(p:Physical) = {
88 | (for(ce <- world.getContacts(p.body)) yield {
89 | val phys2d_point= ce.getPoint
90 | val phys2d_normal = ce.getNormal
91 | (new Vec(phys2d_point), new Vec(phys2d_normal))
92 | }).toList
93 | }
94 | }
--------------------------------------------------------------------------------
/src/main/scala/com/github/dunnololda/scage/support/messages/unicode/Glyph.java:
--------------------------------------------------------------------------------
1 | package com.github.dunnololda.scage.support.messages.unicode;
2 |
3 | import java.awt.*;
4 | import java.awt.font.GlyphMetrics;
5 | import java.awt.font.GlyphVector;
6 |
7 | /**
8 | * Represents the glyph in a font for a unicode codepoint.
9 | *
10 | * @author Nathan Sweet
11 | */
12 | public class Glyph {
13 | /** The code point in which this glyph is found */
14 | private int codePoint;
15 | /** The width of this glyph in pixels */
16 | private short width;
17 | /** The height of this glyph in pixels */
18 | private short height;
19 | /** The offset on the y axis to draw the glyph at */
20 | private short yOffset;
21 | /** True if the glyph isn't defined */
22 | private boolean isMissing;
23 | /** The shape drawn for this glyph */
24 | private Shape shape;
25 | /** The image generated for this glyph */
26 | private MyImage image;
27 |
28 | /**
29 | * Create a new glyph
30 | *
31 | * @param codePoint The code point in which this glyph can be found
32 | * @param bounds The bounds that this glrph can fill
33 | * @param vector The vector this glyph is part of
34 | * @param index The index of this glyph within the vector
35 | * @param unicodeFont The font this glyph forms part of
36 | */
37 | public Glyph(int codePoint, Rectangle bounds, GlyphVector vector, int index, UnicodeFont unicodeFont) {
38 | this.codePoint = codePoint;
39 |
40 | GlyphMetrics metrics = vector.getGlyphMetrics(index);
41 | int lsb = (int)metrics.getLSB();
42 | if (lsb > 0) lsb = 0;
43 | int rsb = (int)metrics.getRSB();
44 | if (rsb > 0) rsb = 0;
45 |
46 | int glyphWidth = bounds.width - lsb - rsb;
47 | int glyphHeight = bounds.height;
48 | if (glyphWidth > 0 && glyphHeight > 0) {
49 | int padTop = unicodeFont.getPaddingTop();
50 | int padRight = unicodeFont.getPaddingRight();
51 | int padBottom = unicodeFont.getPaddingBottom();
52 | int padLeft = unicodeFont.getPaddingLeft();
53 | int glyphSpacing = 1; // Needed to prevent filtering problems.
54 | width = (short)(glyphWidth + padLeft + padRight + glyphSpacing);
55 | height = (short)(glyphHeight + padTop + padBottom + glyphSpacing);
56 | yOffset = (short)(unicodeFont.getAscent() + bounds.y - padTop);
57 | }
58 |
59 | shape = vector.getGlyphOutline(index, -bounds.x + unicodeFont.getPaddingLeft(), -bounds.y + unicodeFont.getPaddingTop());
60 |
61 | isMissing = !unicodeFont.getFont().canDisplay((char)codePoint);
62 | }
63 |
64 | /**
65 | * The unicode codepoint the glyph represents.
66 | *
67 | * @return The codepoint the glyph represents
68 | */
69 | public int getCodePoint () {
70 | return codePoint;
71 | }
72 |
73 | /**
74 | * Returns true if the font does not have a glyph for this codepoint.
75 | *
76 | * @return True if this glyph is not defined in the given code point
77 | */
78 | public boolean isMissing () {
79 | return isMissing;
80 | }
81 |
82 | /**
83 | * The width of the glyph's image.
84 | *
85 | * @return The width in pixels of the glyphs image
86 | */
87 | public int getWidth () {
88 | return width;
89 | }
90 |
91 | /**
92 | * The height of the glyph's image.
93 | *
94 | * @return The height in pixels of the glyphs image
95 | */
96 | public int getHeight () {
97 | return height;
98 | }
99 |
100 | /**
101 | * The shape to use to draw this glyph. This is set to null after the glyph is stored
102 | * in a GlyphPage.
103 | *
104 | * @return The shape drawn for this glyph
105 | */
106 | public Shape getShape () {
107 | return shape;
108 | }
109 |
110 | /**
111 | * Set the shape that should be drawn for this glyph
112 | *
113 | * @param shape The shape that should be drawn for this glyph
114 | */
115 | public void setShape(Shape shape) {
116 | this.shape = shape;
117 | }
118 |
119 | /**
120 | * The image to use for this glyph. This is null until after the glyph is stored in a
121 | * GlyphPage.
122 | *
123 | * @return The image that has been generated for this glyph
124 | */
125 | public MyImage getImage () {
126 | return image;
127 | }
128 |
129 | /**
130 | * Set the image that has been generated for this glyph
131 | *
132 | * @param image The image that has been generated for this glyph
133 | */
134 | public void setImage(MyImage image) {
135 | this.image = image;
136 | this.image.rotate(180);
137 | }
138 |
139 | /**
140 | * The distance from drawing y location to top of this glyph, causing the glyph to sit
141 | * on the baseline.
142 | *
143 | * @return The offset on the y axis this glyph should be drawn at
144 | */
145 | public int getYOffset() {
146 | return yOffset;
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/dunnololda/scage/support/messages/ColoredString.scala:
--------------------------------------------------------------------------------
1 | package com.github.dunnololda.scage.support.messages
2 |
3 | import com.github.dunnololda.scage.support.ScageColor
4 | import com.github.dunnololda.scage.support.ScageColor._
5 | import org.newdawn.slick.Color
6 |
7 | import scala.collection.mutable
8 | import scala.collection.mutable.ArrayBuffer
9 | import collection.JavaConversions._
10 |
11 | /**
12 | * Этот класс представляет собой специальные строки, разные участки которых движок scage будет рисовать разными цветами.
13 | * Другой цвет для участка задается специальной разметкой вот так: "специальная строка с [rкрасным] участком"
14 | * то есть, участок отделяется квадратными скобками и сразу за открывающей квадратной скобкой ставится символ нужного цвета.
15 | * Поддерживается вложенность: "специальная [gзел[rено-кра]сная] строка".
16 | * Если функционал перекрашивания не нужен, а нужно именно написать что то в квадратных скобках, то перед открывающей и закрывающей
17 | * скобками следует поставить слеш: "неизменяемая \[rстрока\]".
18 | * Поскольку оперировать одним символом не очень удобно (задано мало цветов), синтаксис разметки расширен:
19 | * вместо одного символа можно задавать полное имя цвета в фигурных скобках. Примеры:
20 | * "специальная строка с [{RED}красным] участком"
21 | * "специальная [{GREEN}зел[{RED}ено-кра]сная] строка"
22 | * @param original_text - текст с разметкой цветных участков
23 | * @param default_color - цвет по умолчанию, которым будут нарисованы участки вне разметки
24 | */
25 | class ColoredString(original_text:String, default_color:ScageColor) {
26 | def this(original_text:String, c:Color) {this(original_text, new ScageColor(c))}
27 |
28 | private val color_switches = mutable.HashMap[Int, ScageColor]()
29 | private val new_text = ArrayBuffer[Char]()
30 | private val previous_colors = mutable.Stack[ScageColor]()
31 | private var pos_offset = 0
32 |
33 | def colorSwitches:java.util.Map[Int, ScageColor] = color_switches
34 | def originalText = original_text
35 | def text = new_text.mkString
36 |
37 | /**
38 | * метод вычисляет цвет в фигурных скобках
39 | * @return
40 | */
41 | private def calculateColor(text_arr:Array[Char], left_bracket_pos:Int):Option[(ScageColor, Int)] = {
42 | val right_bracket_pos = text_arr.indexOf('}', left_bracket_pos)
43 | val color_name = text_arr.drop(left_bracket_pos+1).take(right_bracket_pos-1 - left_bracket_pos).mkString
44 | ScageColor.fromString(color_name) match {
45 | case Some(color) => Some((color, right_bracket_pos - left_bracket_pos + 2))
46 | case None => None
47 | }
48 | }
49 |
50 | private var is_previous_slash = false
51 | private def findColorSwitches(text_arr:Array[Char], pos:Int = 0, current_color:ScageColor = default_color) {
52 | if(0 <= pos && pos < text_arr.length) {
53 | text_arr(pos) match {
54 | case '\\' =>
55 | if(!is_previous_slash) {
56 | is_previous_slash = true
57 | pos_offset += 1
58 | if(pos < text_arr.length-1) findColorSwitches(text_arr, pos+1, current_color)
59 | } else {
60 | is_previous_slash = false
61 | new_text += text_arr(pos)
62 | if(pos < text_arr.length-1) findColorSwitches(text_arr, pos+1, current_color)
63 | }
64 | case '[' if !is_previous_slash && pos < text_arr.length-1 =>
65 | (text_arr(pos+1) match {
66 | case 'r' => Some((RED, 2))
67 | case 'g' => Some((GREEN, 2))
68 | case 'b' => Some((BLUE, 2))
69 | case 'y' => Some((YELLOW, 2))
70 | case 'o' => Some((ORANGE, 2))
71 | case 'p' => Some((PURPLE, 2))
72 | case 'm' => Some((MAGENTA, 2))
73 | case 'c' => Some((CYAN, 2))
74 | case '{' => calculateColor(text_arr, pos+1)
75 | case _ => None
76 | }) match {
77 | case Some((color, offset)) =>
78 | color_switches += ((pos - pos_offset) -> color)
79 | pos_offset += offset
80 | previous_colors.push(current_color)
81 | if(pos < text_arr.length-offset) findColorSwitches(text_arr, pos+offset, color)
82 | case _ =>
83 | new_text += text_arr(pos)
84 | findColorSwitches(text_arr, pos+1, current_color)
85 | }
86 | case ']' if !is_previous_slash =>
87 | val previous_color = if(previous_colors.length > 0) previous_colors.pop() else default_color
88 | color_switches += ((pos - pos_offset) -> previous_color)
89 | pos_offset += 1
90 | if(pos < text_arr.length-1) findColorSwitches(text_arr, pos+1, previous_color)
91 | case _ =>
92 | is_previous_slash = false
93 | new_text += text_arr(pos)
94 | if(pos < text_arr.length-1) findColorSwitches(text_arr, pos+1, current_color)
95 | }
96 | }
97 | }
98 | findColorSwitches(original_text.toCharArray)
99 |
100 | override def toString = s"ColoredString($original_text, $text, $color_switches)"
101 | }
--------------------------------------------------------------------------------
/src/main/scala/com/github/dunnololda/scage/support/LWJGLKeyboard.scala:
--------------------------------------------------------------------------------
1 | package com.github.dunnololda.scage.support
2 |
3 | import org.lwjgl.input.Keyboard
4 |
5 |
6 | object LWJGLKeyboard extends LWJGLKeyboard
7 |
8 | trait LWJGLKeyboard {
9 | val MOUSE_LEFT_BUTTON = 0
10 | val MOUSE_RIGHT_BUTTON = 1
11 | val MOUSE_MIDDLE_BUTTON = 2 // need a test to prove that
12 |
13 | val KEY_ESCAPE = Keyboard.KEY_ESCAPE
14 | val KEY_1 = Keyboard.KEY_1
15 | val KEY_2 = Keyboard.KEY_2
16 | val KEY_3 = Keyboard.KEY_3
17 | val KEY_4 = Keyboard.KEY_4
18 | val KEY_5 = Keyboard.KEY_5
19 | val KEY_6 = Keyboard.KEY_6
20 | val KEY_7 = Keyboard.KEY_7
21 | val KEY_8 = Keyboard.KEY_8
22 | val KEY_9 = Keyboard.KEY_9
23 | val KEY_0 = Keyboard.KEY_0
24 | val KEY_MINUS = Keyboard.KEY_MINUS
25 | val KEY_EQUALS = Keyboard.KEY_EQUALS
26 | val KEY_BACK = Keyboard.KEY_BACK
27 | val KEY_TAB = Keyboard.KEY_TAB
28 | val KEY_Q = Keyboard.KEY_Q
29 | val KEY_W = Keyboard.KEY_W
30 | val KEY_E = Keyboard.KEY_E
31 | val KEY_R = Keyboard.KEY_R
32 | val KEY_T = Keyboard.KEY_T
33 | val KEY_Y = Keyboard.KEY_Y
34 | val KEY_U = Keyboard.KEY_U
35 | val KEY_I = Keyboard.KEY_I
36 | val KEY_O = Keyboard.KEY_O
37 | val KEY_P = Keyboard.KEY_P
38 | val KEY_LBRACKET = Keyboard.KEY_LBRACKET
39 | val KEY_RBRACKET = Keyboard.KEY_RBRACKET
40 | val KEY_RETURN = Keyboard.KEY_RETURN
41 | val KEY_LCONTROL = Keyboard.KEY_LCONTROL
42 | val KEY_A = Keyboard.KEY_A
43 | val KEY_S = Keyboard.KEY_S
44 | val KEY_D = Keyboard.KEY_D
45 | val KEY_F = Keyboard.KEY_F
46 | val KEY_G = Keyboard.KEY_G
47 | val KEY_H = Keyboard.KEY_H
48 | val KEY_J = Keyboard.KEY_J
49 | val KEY_K = Keyboard.KEY_K
50 | val KEY_L = Keyboard.KEY_L
51 | val KEY_SEMICOLON = Keyboard.KEY_SEMICOLON
52 | val KEY_APOSTROPHE = Keyboard.KEY_APOSTROPHE
53 | val KEY_GRAVE = Keyboard.KEY_GRAVE
54 | val KEY_LSHIFT = Keyboard.KEY_LSHIFT
55 | val KEY_BACKSLASH = Keyboard.KEY_BACKSLASH
56 | val KEY_Z = Keyboard.KEY_Z
57 | val KEY_X = Keyboard.KEY_X
58 | val KEY_C = Keyboard.KEY_C
59 | val KEY_V = Keyboard.KEY_V
60 | val KEY_B = Keyboard.KEY_B
61 | val KEY_N = Keyboard.KEY_N
62 | val KEY_M = Keyboard.KEY_M
63 | val KEY_COMMA = Keyboard.KEY_COMMA
64 | val KEY_PERIOD = Keyboard.KEY_PERIOD
65 | val KEY_SLASH = Keyboard.KEY_SLASH
66 | val KEY_RSHIFT = Keyboard.KEY_RSHIFT
67 | val KEY_MULTIPLY = Keyboard.KEY_MULTIPLY
68 | val KEY_LMENU = Keyboard.KEY_LMENU
69 | val KEY_SPACE = Keyboard.KEY_SPACE
70 | val KEY_CAPITAL = Keyboard.KEY_CAPITAL
71 | val KEY_F1 = Keyboard.KEY_F1
72 | val KEY_F2 = Keyboard.KEY_F2
73 | val KEY_F3 = Keyboard.KEY_F3
74 | val KEY_F4 = Keyboard.KEY_F4
75 | val KEY_F5 = Keyboard.KEY_F5
76 | val KEY_F6 = Keyboard.KEY_F6
77 | val KEY_F7 = Keyboard.KEY_F7
78 | val KEY_F8 = Keyboard.KEY_F8
79 | val KEY_F9 = Keyboard.KEY_F9
80 | val KEY_F10 = Keyboard.KEY_F10
81 | val KEY_NUMLOCK = Keyboard.KEY_NUMLOCK
82 | val KEY_SCROLL = Keyboard.KEY_SCROLL
83 | val KEY_NUMPAD7 = Keyboard.KEY_NUMPAD7
84 | val KEY_NUMPAD8 = Keyboard.KEY_NUMPAD8
85 | val KEY_NUMPAD9 = Keyboard.KEY_NUMPAD9
86 | val KEY_SUBTRACT = Keyboard.KEY_SUBTRACT
87 | val KEY_NUMPAD4 = Keyboard.KEY_NUMPAD4
88 | val KEY_NUMPAD5 = Keyboard.KEY_NUMPAD5
89 | val KEY_NUMPAD6 = Keyboard.KEY_NUMPAD6
90 | val KEY_ADD = Keyboard.KEY_ADD
91 | val KEY_NUMPAD1 = Keyboard.KEY_NUMPAD1
92 | val KEY_NUMPAD2 = Keyboard.KEY_NUMPAD2
93 | val KEY_NUMPAD3 = Keyboard.KEY_NUMPAD3
94 | val KEY_NUMPAD0 = Keyboard.KEY_NUMPAD0
95 | val KEY_DECIMAL = Keyboard.KEY_DECIMAL
96 | val KEY_F11 = Keyboard.KEY_F11
97 | val KEY_F12 = Keyboard.KEY_F12
98 | val KEY_F13 = Keyboard.KEY_F13
99 | val KEY_F14 = Keyboard.KEY_F14
100 | val KEY_F15 = Keyboard.KEY_F15
101 | val KEY_KANA = Keyboard.KEY_KANA
102 | val KEY_CONVERT = Keyboard.KEY_CONVERT
103 | val KEY_NOCONVERT = Keyboard.KEY_NOCONVERT
104 | val KEY_YEN = Keyboard.KEY_YEN
105 | val KEY_NUMPADEQUALS = Keyboard.KEY_NUMPADEQUALS
106 | val KEY_CIRCUMFLEX = Keyboard.KEY_CIRCUMFLEX
107 | val KEY_AT = Keyboard.KEY_AT
108 | val KEY_COLON = Keyboard.KEY_COLON
109 | val KEY_UNDERLINE = Keyboard.KEY_UNDERLINE
110 | val KEY_KANJI = Keyboard.KEY_KANJI
111 | val KEY_STOP = Keyboard.KEY_STOP
112 | val KEY_AX = Keyboard.KEY_AX
113 | val KEY_UNLABELED = Keyboard.KEY_UNLABELED
114 | val KEY_NUMPADENTER = Keyboard.KEY_NUMPADENTER
115 | val KEY_RCONTROL = Keyboard.KEY_RCONTROL
116 | val KEY_NUMPADCOMMA = Keyboard.KEY_NUMPADCOMMA
117 | val KEY_DIVIDE = Keyboard.KEY_DIVIDE
118 | val KEY_SYSRQ = Keyboard.KEY_SYSRQ
119 | val KEY_RMENU = Keyboard.KEY_RMENU
120 | val KEY_PAUSE = Keyboard.KEY_PAUSE
121 | val KEY_HOME = Keyboard.KEY_HOME
122 | val KEY_UP = Keyboard.KEY_UP
123 | val KEY_PRIOR = Keyboard.KEY_PRIOR
124 | val KEY_LEFT = Keyboard.KEY_LEFT
125 | val KEY_RIGHT = Keyboard.KEY_RIGHT
126 | val KEY_END = Keyboard.KEY_END
127 | val KEY_DOWN = Keyboard.KEY_DOWN
128 | val KEY_NEXT = Keyboard.KEY_NEXT
129 | val KEY_INSERT = Keyboard.KEY_INSERT
130 | val KEY_DELETE = Keyboard.KEY_DELETE
131 | val KEY_LMETA = Keyboard.KEY_LMETA
132 | val KEY_RMETA = Keyboard.KEY_RMETA
133 | val KEY_APPS = Keyboard.KEY_APPS
134 | val KEY_POWER = Keyboard.KEY_POWER
135 | val KEY_SLEEP = Keyboard.KEY_SLEEP
136 | }
137 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | Latest Stable Version
4 | ---------------------
5 |
6 | 11.4
7 |
8 | Introduction
9 | ------------
10 |
11 | Scage is a framework to write simple 2D opengl games. It is written in [Scala](http://scala-lang.org/) and based on several java libraries:
12 |
13 | - [phys2d](https://code.google.com/p/phys2d/) as a physics engine
14 | - [lwjgl](http://lwjgl.org) as an opengl wrapper
15 | - [slick](https://code.google.com/p/phys2d/) as a resource and texture loader
16 | -
17 |
18 | The main purpose of this project is to give a convenient tool for game-developers to write a code of pure functionality without any boilerplate.
19 |
20 | Features
21 | --------
22 |
23 | - Architecture similar to actors framework with different kinds of tasks executing on different stages of app lifecycle. Simililar to actors these tasks are anonymous functions, and you can add and remove them in runtime in any scope of your app. Its all singlethreaded, so you dont have to mess with messages.
24 | - Vast drawing library for any kinds of 2D opengl primitives.
25 | - Loading and utilizing fonts from ttf-files (based on 'Slick2D' api but with improvements).
26 | - i18n: loading strings and even the whole interfaces from xml files. Runtime language change.
27 | - Framework to build in-game interfaces from xml files of simple structure.
28 | - App settings can be specified in a text files as a key-value pairs (moved to the external project: [scala-cli](https://github.com/dunnololda/scala-cli)). Lots of engine options are set that way (alongside with the standard possibility to set them as parameters) allowing fine-tuning without app rebuilding.
29 | - Tracers framework: easy game objects tracking and interacting on a two-dimensional game map.
30 | - Lightweight wrapper upon phys2d engine.
31 | - Easy app building/deploing (as a standalone or via webstart) using maven infrastructure.
32 | - Multiple platforms support: Windows, Linux, Mac, Solaris (thanks to Java and lwjgl actually). Similar build process for any platform (with maven).
33 | - Client/server network api upon actors with simple text protocol based on json format (moved to the external project: [simple-net](https://github.com/dunnololda/simple-net)).
34 |
35 | Please read the project wiki and especially see [Examples](https://github.com/dunnololda/scage/wiki/Examples) page to learn more!
36 |
37 | Hello World Example
38 | -------------------
39 |
40 | ###Rotating 'Hello World!' label
41 |
42 | import com.github.dunnololda.scage.ScageLib._
43 |
44 | object HelloWorldExample extends ScageScreenApp("Scage App", 640, 480) {
45 | private var ang = 0f
46 | actionStaticPeriod(100) {
47 | ang += 5
48 | }
49 |
50 | backgroundColor = BLACK
51 | render {
52 | openglMove(windowSize/2)
53 | openglRotate(ang)
54 | print("Hello World!", Vec(-50, -5), GREEN)
55 | }
56 | }
57 |
58 | 
59 |
60 | More examples
61 | -------------
62 |
63 | See [Examples Page](https://github.com/dunnololda/scage/wiki/Examples)
64 |
65 | Engine brief description
66 | ------------------------
67 |
68 | See [Engine methods overview](https://github.com/dunnololda/scage/wiki/Scage-methods-overview)
69 |
70 | Usage
71 | ------------
72 |
73 | ###For Maven users
74 |
75 | You can use the scage archetype to create a new scage project stub:
76 |
77 | $ mvn org.apache.maven.plugins:maven-archetype-plugin:2.4:generate -DarchetypeGroupId=scage -DarchetypeArtifactId=project-archetype -DarchetypeVersion=11.4 -DarchetypeRepository=https://raw.githubusercontent.com/dunnololda/mvn-repo/master/
78 |
79 | This utilize the maven's "archetype" feature - create a simple example project with all needed stuff. Answer some questions about its name and version and you are done.
80 |
81 | For example:
82 | groupId: mygroup
83 | artifactId: myartifact
84 | version: 0.1
85 | package mygroup.myartifact
86 |
87 | (these all names are not really important, you can choose anything)
88 |
89 | In the end type Y, hit 'enter' and the folder "myartifact" will be created. Inside will be a small application, ready to compile, run and deploy - simple Light Cycles game based on the Tron movie.
90 |
91 | To launch app from the project stub you can type:
92 |
93 | $ mvn clean test
94 |
95 | This project stub has two profiles in its pom.xml for the app building. To build a standalone app type in your console:
96 |
97 | $ mvn clean package -Pbuild -Dmaven.test.skip
98 |
99 | Or just:
100 |
101 | $ mvn clean package -Dmaven.test.skip
102 |
103 | as "build" is a default profile.
104 |
105 | To build a webstart app type:
106 |
107 | $ mvn clean package -Pwebstart -Dmaven.test.skip
108 |
109 | This command will create "jnlp" folder in "target". Then you can upload this folder to your host.
110 |
111 | More info you can find in the readme file inside the project's root.
112 |
113 | ###OpenJDK
114 |
115 | If you use OpenJDK (not Oracle JDK) you need to add the openjdk profile to all mvn commands above:
116 |
117 | $ mvn clean test -Popenjdk
118 | $ mvn clean package -Pbuild,openjdk
119 | $ mvn clean package -Pwebstart,openjdk
120 |
121 | Additionally you need to install the package "icedtea-web" (this is the name in Archlinux, in Ubuntu it should be something similar).
122 |
123 | ###Intellij IDEA
124 |
125 | You also can use some IDE with good Maven and Scala support (for example, [IntelliJ IDEA](http://www.jetbrains.com/idea/)). Here are the steps for IDEA:
126 |
127 | Download Intellij IDEA Community Edition from there: https://www.jetbrains.com/idea/download/ (it's free).
128 |
129 | Unzip it and run. Setup scala plugin.
130 |
131 | Then in the main menu click "Import Project" - choose the folder "myartifact".
132 | In the new window choose "Import project from external model" - "Maven". Then just hit "Next" several times.
133 |
134 | Then wait for a while and IDEA will setup the project for you.
135 |
136 | How to run it:
137 |
138 | In the left panel go to src/main/scala/mygroup.myartifact. There is only one file - LightCyclesOffline.scala. Open it. Place the cursor in the row second to "object LightCyclesOffline extends ScageScreenApp("Light Cycles", 640, 480) {".
139 |
140 | Right click - Create LightCyclesOffline. In the new window in the "VM Options" type:
141 |
142 | -Djava.library.path=target/natives -DLWJGL_DISABLE_XRANDR=true -Dfile.encoding=UTF-8
143 |
144 | Then click OK. Then for example right click again and choose Run LightCyclesOffline. IDEA will build and run the app.
145 |
146 | If you see
147 |
148 | "Exception in thread "main" java.lang.UnsatisfiedLinkError: no lwjgl in java.library.path"
149 |
150 | or
151 |
152 | "Exception in thread "main" java.lang.UnsatisfiedLinkError: no lwjgl64 in java.library.path"
153 |
154 | this means you have no native libraries in target/natives. In order to generate them, type some mvn command from above. For example, type "mvn clean compile". As a first step of the compilation process maven will generate those libraries in target/natives.
155 |
156 | ###For non-Maven users.
157 |
158 | You can both :
159 | - Compile Scage with SBT
160 | - Use SBT in your own Scage projects with [SBT Scage Plugin](https://github.com/mvallerie/sbt-scage-plugin). Just follow the README :).
161 |
162 | Feedback
163 | --------
164 |
165 | Feel free to ask any questions by email or using issue tracker.
166 |
167 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/dunnololda/scage/handlers/controller2/ScageController.scala:
--------------------------------------------------------------------------------
1 | package com.github.dunnololda.scage.handlers.controller2
2 |
3 | import java.text.SimpleDateFormat
4 | import java.util.Date
5 |
6 | import com.github.dunnololda.mysimplelogger.MySimpleLogger
7 | import com.github.dunnololda.scage.{ScagePhase, Scage}
8 | import com.github.dunnololda.scage.ScageLib.coordOnArea
9 | import com.github.dunnololda.scage.support.Vec
10 | import org.lwjgl.input.{Keyboard, Mouse}
11 |
12 | import scala.collection.mutable
13 |
14 | case class KeyPress(key_code: Int, var was_pressed: Boolean, var pressed_start_time: Long, private var last_pressed_time: Long) {
15 | def lastPressedTime: Long = last_pressed_time
16 |
17 | def updateLastPressedTime(new_time: Long): Unit = {
18 | last_pressed_time = new_time
19 | ScageController.updateMaxLastPressedTime(last_pressed_time)
20 | }
21 |
22 | def immutable: ImmutableKeyPress = ImmutableKeyPress(key_code, was_pressed, pressed_start_time, last_pressed_time)
23 |
24 | private val f = new SimpleDateFormat("HH:mm:ss.S")
25 |
26 | override def toString = s"KeyPress($key_code, $was_pressed, ${f.format(new Date(pressed_start_time))}, ${f.format(new Date(last_pressed_time))})"
27 | }
28 |
29 | case class ImmutableKeyPress(key_code: Int, was_pressed: Boolean, pressed_start_time: Long, last_pressed_time: Long)
30 |
31 | case class MouseButtonPress(button_code: Int, var was_pressed: Boolean, var pressed_start_time: Long, private var last_pressed_time: Long) {
32 | def lastPressedTime: Long = last_pressed_time
33 |
34 | def updateLastPressedTime(new_time: Long): Unit = {
35 | last_pressed_time = new_time
36 | ScageController.updateMaxLastPressedTime(last_pressed_time)
37 | }
38 |
39 | def immutable: ImmutableMouseButtonPress = ImmutableMouseButtonPress(button_code, was_pressed, pressed_start_time, last_pressed_time)
40 |
41 | private val f = new SimpleDateFormat("HH:mm:ss.S")
42 |
43 | override def toString = s"MouseButtonPress($button_code, $was_pressed, ${f.format(new Date(pressed_start_time))}, ${f.format(new Date(last_pressed_time))})"
44 | }
45 |
46 | case class ImmutableMouseButtonPress(button_code: Int, was_pressed: Boolean, pressed_start_time: Long, last_pressed_time: Long)
47 |
48 | object ScageController {
49 | private val key_presses = mutable.HashMap[Int, KeyPress]()
50 | private val mouse_button_presses = mutable.HashMap[Int, MouseButtonPress]()
51 |
52 | private var _max_last_pressed_time: Long = 0l
53 |
54 | private[scage] def updateMaxLastPressedTime(new_time: Long) {
55 | if (new_time > _max_last_pressed_time) {
56 | _max_last_pressed_time = new_time
57 | }
58 | }
59 | }
60 |
61 | trait ScageController extends Scage {
62 | private val log = MySimpleLogger(this.getClass.getName)
63 |
64 | protected def mappedKeyboardKeys: scala.collection.Set[Int]
65 |
66 | protected def mappedMouseButtons: scala.collection.Set[Int]
67 |
68 | private implicit class SeqLongRich(s: Iterable[Long]) {
69 | def maxOption: Option[Long] = if (s.nonEmpty) Some(s.max) else None
70 | }
71 |
72 | protected def maxLastPressedTime: Long = ScageController._max_last_pressed_time
73 |
74 | protected def innerKeyPress(key_code: Int): Option[KeyPress] = {
75 | ScageController.key_presses.get(key_code) match {
76 | case skp@Some(kp: KeyPress) => skp
77 | case None if mappedKeyboardKeys.contains(key_code) =>
78 | val kp = KeyPress(key_code, was_pressed = false, 0L, 0L)
79 | ScageController.key_presses += (key_code -> kp)
80 | Some(kp)
81 | case _ => None
82 | }
83 | }
84 |
85 | def keyPress(key_code: Int): Option[ImmutableKeyPress] = innerKeyPress(key_code).map(_.immutable)
86 |
87 | protected def innerMouseButtonPress(mouse_button: Int): Option[MouseButtonPress] = {
88 | ScageController.mouse_button_presses.get(mouse_button) match {
89 | case smbp@Some(mbp: MouseButtonPress) => smbp
90 | case None if mappedMouseButtons.contains(mouse_button) =>
91 | val mbp = MouseButtonPress(mouse_button, was_pressed = false, 0L, 0L)
92 | ScageController.mouse_button_presses += (mouse_button -> mbp)
93 | Some(mbp)
94 | case _ => None
95 | }
96 | }
97 |
98 | def mouseButtonPress(key_code: Int): Option[ImmutableMouseButtonPress] = innerMouseButtonPress(key_code).map(_.immutable)
99 |
100 | def keyPressed(key_code: Int): Boolean = {
101 | /*val KeyPress(_, was_pressed, _) = keyPress(key_code)
102 | was_pressed*/
103 | Keyboard.isKeyDown(key_code)
104 | }
105 |
106 | def leftMousePressed: Boolean = {
107 | /*val MouseButtonPress(_, was_pressed, _) = mouseButtonPress(0)
108 | was_pressed*/
109 | Mouse.isButtonDown(0)
110 | }
111 |
112 | def rightMousePressed: Boolean = {
113 | /*val MouseButtonPress(_, was_pressed, _) = mouseButtonPress(1)
114 | was_pressed*/
115 | Mouse.isButtonDown(1)
116 | }
117 |
118 | def mouseOnArea(area: List[Vec]): Boolean = {
119 | coordOnArea(mouseCoord, area)
120 | }
121 |
122 | def key(key_code: Int, repeat_time: => Long = 0, onKeyDown: => Any, onKeyUp: => Any = {}): Int
123 |
124 | def keyIgnorePause(key_code: Int, repeat_time: => Long = 0, onKeyDown: => Any, onKeyUp: => Any = {}): Int
125 |
126 | def keyOnPause(key_code: Int, repeat_time: => Long = 0, onKeyDown: => Any, onKeyUp: => Any = {}): Int
127 |
128 | def anykey(onKeyDown: => Any): Int
129 |
130 | def anykeyIgnorePause(onKeyDown: => Any): Int
131 |
132 | def anykeyOnPause(onKeyDown: => Any): Int
133 |
134 | def mouseCoord: Vec
135 |
136 | def isMouseMoved: Boolean
137 |
138 | def leftMouse(repeat_time: => Long = 0, onBtnDown: Vec => Any, onBtnUp: Vec => Any = Vec => {}): Int
139 |
140 | def leftMouseIgnorePause(repeat_time: => Long = 0, onBtnDown: Vec => Any, onBtnUp: Vec => Any = Vec => {}): Int
141 |
142 | def leftMouseOnPause(repeat_time: => Long = 0, onBtnDown: Vec => Any, onBtnUp: Vec => Any = Vec => {}): Int
143 |
144 | def rightMouse(repeat_time: => Long = 0, onBtnDown: Vec => Any, onBtnUp: Vec => Any = Vec => {}): Int
145 |
146 | def rightMouseIgnorePause(repeat_time: => Long = 0, onBtnDown: Vec => Any, onBtnUp: Vec => Any = Vec => {}): Int
147 |
148 | def rightMouseOnPause(repeat_time: => Long = 0, onBtnDown: Vec => Any, onBtnUp: Vec => Any = Vec => {}): Int
149 |
150 | def mouseMotion(onMotion: Vec => Any): Int
151 |
152 | def mouseMotionIgnorePause(onMotion: Vec => Any): Int
153 |
154 | def mouseMotionOnPause(onMotion: Vec => Any): Int
155 |
156 | def leftMouseDrag(onDrag: Vec => Any): Int
157 |
158 | def leftMouseDragIgnorePause(onDrag: Vec => Any): Int
159 |
160 | def leftMouseDragOnPause(onDrag: Vec => Any): Int
161 |
162 | def rightMouseDrag(onDrag: Vec => Any): Int
163 |
164 | def rightMouseDragIgnorePause(onDrag: Vec => Any): Int
165 |
166 | def rightMouseDragOnPause(onDrag: Vec => Any): Int
167 |
168 | def mouseWheelUp(onWheelUp: Vec => Any): Int
169 |
170 | def mouseWheelUpIgnorePause(onWheelUp: Vec => Any): Int
171 |
172 | def mouseWheelUpOnPause(onWheelUp: Vec => Any): Int
173 |
174 | def mouseWheelDown(onWheelDown: Vec => Any): Int
175 |
176 | def mouseWheelDownIgnorePause(onWheelDown: Vec => Any): Int
177 |
178 | def mouseWheelDownOnPause(onWheelDown: Vec => Any): Int
179 |
180 | private[scage] def checkControls()
181 |
182 | private[scage] val control_deletion_operations = defaultContainer("control_deleters", ScagePhase.Controls, execute_if_app_running = false, execute_on_deletion = true)
183 |
184 | def delControl(control_id: Int) = {
185 | delOp(control_id, show_warnings = true)
186 | }
187 |
188 | def delControls(control_ids: Int*) {
189 | control_ids.foreach(control_id => {
190 | delOp(control_id, show_warnings = true)
191 | })
192 | }
193 |
194 | def delAllControls() {
195 | control_deletion_operations.operations.operationIdsIterable.foreach(control_id => {
196 | delOp(control_id, show_warnings = true)
197 | })
198 | }
199 |
200 | def delAllControlsExcept(except_control_ids: Int*) {
201 | control_deletion_operations.operations.operationIdsIterable.filterNot(op_id => {
202 | except_control_ids.contains(op_id)
203 | }).foreach(control_id => {
204 | delOp(control_id, show_warnings = true)
205 | })
206 | }
207 | }
208 |
209 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/dunnololda/scage/support/Vec.scala:
--------------------------------------------------------------------------------
1 | package com.github.dunnololda.scage.support
2 |
3 | import _root_.net.phys2d.math.{ROVector2f, Vector2f}
4 | import parsers.{DVecParser, VecParser}
5 |
6 | /**
7 | * I believe for now this glorious piece of code has all the needed to be the exact match to 'case Vec(x:Int, y:Int) {...}':
8 | * 1. lots of apply() methods
9 | * 2. one pretty cool unapply() method
10 | * 3. redefined equals(), hashCode() and canEquals() methods! (- that part was hard)
11 | */
12 | object Vec {
13 | def apply(x:Float, y:Float) = new Vec(x, y)
14 | def apply(v:Vec) = v.copy
15 | def apply(v:ROVector2f) = new Vec(v.getX, v.getY)
16 | def apply(x:Double, y:Double) = new Vec(x.toFloat, y.toFloat)
17 | def apply() = new Vec(0, 0)
18 |
19 | def unapply(data:Any):Option[(Float, Float)] = data match {
20 | case v:Vec => Some((v.x, v.y))
21 | case _ => None
22 | }
23 |
24 | private val vec_parser= new VecParser()
25 | def fromString(vec_str:String):Option[Vec] = vec_parser.evaluate(vec_str)
26 | def fromStringOrDefault(vec_str:String, default_vec:Vec = zero):Vec = vec_parser.evaluate(vec_str) match {
27 | case Some(v:Vec) => v
28 | case None => default_vec
29 | }
30 |
31 | lazy val zero = new Vec(0, 0)
32 | }
33 |
34 | class Vec(val x:Float = 0, val y:Float = 0) {
35 | lazy val ix = x.toInt
36 | lazy val iy = y.toInt
37 |
38 | def this(v:Vec) = this(v.x, v.y)
39 | def this(v:ROVector2f) = this(v.getX, v.getY)
40 | def this(x:Double, y:Double) = this(x.toFloat, y.toFloat)
41 | def this(x:Int, y:Int) = this(x.toFloat, y.toFloat)
42 | def this() = this(0,0)
43 |
44 | def +(v:Vec) = new Vec(x+v.x, y+v.y)
45 | def -(v:Vec) = new Vec(x-v.x, y-v.y)
46 |
47 | def unary_-():Vec = new Vec(-x, -y)
48 |
49 | def project(v:Vec):Vec = v*(this*v)
50 |
51 | def *(v:Vec):Float = x*v.x + y*v.y
52 | def */(v:Vec):Float = x*v.y - y*v.x // maybe another symbol as it is a closing comment symbol
53 | def */(k:Float):Vec = Vec(y*k, -x*k)
54 |
55 | def *(k:Double) = new Vec(x*k, y*k)
56 | def *(k:Float) = new Vec(x*k, y*k)
57 | def *(k:Int) = new Vec(x*k, y*k)
58 |
59 | def **(v:Vec) = new Vec(x*v.x, y*v.y)
60 |
61 | def /(k:Double):Vec = this / k.toFloat
62 | def /(k:Float):Vec = if(k == 0) Vec(x*1000, y*1000) else Vec(x/k, y/k)
63 | def /(k:Int):Vec = this / k.toFloat
64 |
65 | def norma2:Float = x*x + y*y
66 | def norma = math.sqrt(norma2).toFloat
67 | def n = this/norma
68 |
69 | def perpendicular = new Vec(-y, x)
70 | def p = perpendicular.n
71 |
72 | def dist2(v:Vec) = (x - v.x)*(x - v.x) + (y - v.y)*(y - v.y)
73 | def dist(v:Vec) = math.sqrt(dist2(v)).toFloat
74 |
75 | def notZero = x != 0 || y != 0
76 | def isZero = x == 0 && y == 0
77 | override def equals(other:Any):Boolean = other match {
78 | case that:Vec => (that canEqual this) && this.x == that.x && this.y == that.y
79 | case _ => false
80 | }
81 | override val hashCode:Int = (41*(41 + x) + y).toInt
82 | def canEqual(other: Any) = other.isInstanceOf[Vec]
83 |
84 | def absDeg(v:Vec):Float = math.acos(math.max(math.min(n * v.n, 1.0), -1.0)).toFloat/math.Pi.toFloat*180f
85 | def deg(v:Vec):Float = absDeg(v)
86 | def signedDeg(v:Vec):Float = {
87 | val scalar = perpendicular*v
88 | if(scalar >= 0) absDeg(v) else -absDeg(v)
89 | }
90 |
91 | def absRad(v:Vec) = math.acos(math.max(math.min(n * v.n, 1.0), -1.0)).toFloat
92 | def rad(v:Vec) = absRad(v)
93 | def signedRad(v:Vec):Float = {
94 | val scalar = perpendicular*v
95 | if(scalar >= 0) absRad(v) else -absRad(v)
96 | }
97 |
98 | def rotateRad(ang_rad:Double) = new Vec((x * math.cos(ang_rad) - y * math.sin(ang_rad)).toFloat,
99 | (x * math.sin(ang_rad) + y * math.cos(ang_rad)).toFloat)
100 | def rotate(ang_rad:Double) = rotateRad(ang_rad)
101 | def rotateDeg(ang_deg:Double) = rotateRad(ang_deg/180*math.Pi)
102 |
103 | def copy = new Vec(x, y)
104 | def copy(x:Float = x, y:Float = y) = new Vec(x, y)
105 |
106 | def toDVec = new DVec(x, y)
107 |
108 | def toPhys2dVec = new Vector2f(x, y)
109 |
110 | override def toString = s"Vec($x, $y)"
111 |
112 | def map[A](f:(Vec) => A):A = f(this)
113 | }
114 |
115 | object DVec {
116 | def apply(x:Float, y:Float) = new DVec(x, y)
117 | def apply(dv:DVec) = dv.copy
118 | def apply(v:ROVector2f) = new DVec(v.getX, v.getY)
119 | def apply(x:Double, y:Double) = new DVec(x, y)
120 | def apply() = new DVec(0, 0)
121 |
122 | def unapply(data:Any):Option[(Double, Double)] = data match {
123 | case dv:DVec => Some((dv.x, dv.y))
124 | case _ => None
125 | }
126 |
127 | private lazy val dvec_parser = new DVecParser()
128 | def fromString(vec_str:String):Option[DVec] = dvec_parser.evaluate(vec_str)
129 | def fromStringOrDefault(vec_str:String, default_vec:DVec = dzero):DVec = dvec_parser.evaluate(vec_str) match {
130 | case Some(dv:DVec) => dv
131 | case None => default_vec
132 | }
133 |
134 | lazy val dzero = new DVec(0, 0)
135 | lazy val zero = new DVec(0, 0)
136 | }
137 |
138 | class DVec(val x:Double = 0, val y:Double = 0) {
139 | lazy val ix = x.toInt
140 | lazy val iy = y.toInt
141 |
142 | def this(dv:DVec) = this(dv.x, dv.y)
143 | def this(v:Vec) = this(v.x, v.y)
144 | def this(v:ROVector2f) = this(v.getX, v.getY)
145 | def this() = this(0,0)
146 |
147 | def +(dv:DVec) = new DVec(x+dv.x, y+dv.y)
148 | def -(dv:DVec) = new DVec(x-dv.x, y-dv.y)
149 |
150 | def unary_-():DVec = new DVec(-x, -y)
151 |
152 | def *(dv:DVec) = x*dv.x + y*dv.y
153 | def */(v:DVec) = x*v.y - y*v.x
154 | def */(k:Double):DVec = DVec(y*k, -x*k)
155 |
156 | def *(k:Double) = new DVec(x*k, y*k)
157 | def *(k:Float) = new DVec(x*k, y*k)
158 | def *(k:Int) = new DVec(x*k, y*k)
159 |
160 | def **(dv:DVec) = new DVec(x*dv.x, y*dv.y)
161 |
162 | def /(k:Double):DVec = if(k == 0) new DVec(x*1000, y*1000) else new DVec(x/k, y/k)
163 | def /(k:Float):DVec = this / k.toDouble
164 | def /(k:Int):DVec = this / k.toDouble
165 |
166 | def norma2:Double = x*x + y*y
167 | def norma = math.sqrt(norma2)
168 | def n = this/norma
169 |
170 | def perpendicular = new DVec(-y, x)
171 | def p = perpendicular.n
172 |
173 | def dist2(dv:DVec) = (x - dv.x)*(x - dv.x) + (y - dv.y)*(y - dv.y)
174 | def dist(dv:DVec) = math.sqrt(dist2(dv))
175 |
176 | def notZero = x != 0 || y != 0
177 | def isZero = x == 0 && y == 0
178 | override def equals(other:Any):Boolean = other match {
179 | case that:DVec => (that canEqual this) && this.x == that.x && this.y == that.y
180 | case _ => false
181 | }
182 | override val hashCode:Int = (41*(41 + x) + y).toInt
183 | def canEqual(other: Any) = other.isInstanceOf[DVec]
184 |
185 | def absDeg(dv:DVec) = 180 / math.Pi * math.acos(math.max(math.min(n * dv.n, 1.0), -1.0))
186 | def deg(dv:DVec) = absDeg(dv)
187 | def signedDeg(dv:DVec) = {
188 | val scalar = perpendicular*dv
189 | if(scalar >= 0) absDeg(dv) else -absDeg(dv)
190 | }
191 |
192 | def absRad(dv:DVec) = math.acos(math.max(math.min(n * dv.n, 1.0), -1.0))
193 | def rad(dv:DVec) = absRad(dv)
194 | def signedRad(dv:DVec) = {
195 | val scalar = perpendicular*dv
196 | if(scalar >= 0) absRad(dv) else -absRad(dv)
197 | }
198 |
199 | def rotateRad(ang_rad:Double) = new DVec(x * math.cos(ang_rad) - y * math.sin(ang_rad),
200 | x * math.sin(ang_rad) + y * math.cos(ang_rad))
201 | def rotate(ang_rad:Double) = rotateRad(ang_rad)
202 | def rotateDeg(ang_deg:Double) = rotateRad(ang_deg/180*math.Pi)
203 |
204 | def copy = new DVec(x, y)
205 | def copy(x:Double = x, y:Double = y) = new DVec(x, y)
206 |
207 | def toVec = new Vec(x, y)
208 |
209 | def toPhys2dVec = new Vector2f(x.toFloat, y.toFloat)
210 |
211 | override def toString = s"DVec($x, $y)"
212 |
213 | def map[A](f:(DVec) => A):A = f(this)
214 | }
--------------------------------------------------------------------------------
/src/main/scala/com/github/dunnololda/scage/support/tracer3/CoordTracer.scala:
--------------------------------------------------------------------------------
1 | package com.github.dunnololda.scage.support.tracer3
2 |
3 | import com.github.dunnololda.scage.support.Vec
4 | import com.github.dunnololda.cli.AppProperties._
5 | import com.github.dunnololda.mysimplelogger.MySimpleLogger
6 | import com.github.dunnololda.scage.handlers.RendererLib
7 |
8 | class CoordTracer[T <: TraceTrait](field_from_x:Int = property("field.from.x", 0),
9 | field_to_x:Int = property("field.to.x", try {RendererLib.windowWidth} catch {case e:Exception => 800}),
10 | field_from_y:Int = property("field.from.y", 0),
11 | field_to_y:Int = property("field.to.y", try {RendererLib.windowHeight} catch {case e:Exception => 600}),
12 | init_h_x:Int = property("field.h_x", 0),
13 | init_h_y:Int = property("field.h_y", 0),
14 | init_N_x:Int = if(property("field.h_x", 0) == 0) property("field.N_x", (try {RendererLib.windowWidth} catch {case e:Exception => 800})/50) else 0,
15 | init_N_y:Int = if(property("field.h_y", 0) == 0) property("field.N_y", (try {RendererLib.windowHeight} catch {case e:Exception => 600})/50) else 0,
16 | solid_edges:Boolean = property("field.solid_edges", true))
17 | extends ScageTracer[T](field_from_x,field_to_x,field_from_y,field_to_y,init_h_x,init_h_y,init_N_x,init_N_y,solid_edges) {
18 | private val log = MySimpleLogger(this.getClass.getName)
19 |
20 | override def addTrace(coord:Vec, trace:T):T = {
21 | if(isCoordOnArea(coord)) {
22 | trace._location = coord
23 | val p = point(coord)
24 | point_matrix(p.ix)(p.iy) += trace
25 | traces_by_ids += trace.id -> trace
26 | traces_list += trace
27 | log.debug("added new trace #"+trace.id+" in coord "+trace.location)
28 | } else log.warn("failed to add trace: coord "+trace.location+" is out of area")
29 | trace
30 | }
31 |
32 | override protected def _removeTrace(trace_id:Int, show_warn:Boolean) {
33 | traces_by_ids.get(trace_id) match {
34 | case Some(trace) =>
35 | val trace_point = point(trace.location)
36 | point_matrix(trace_point.ix)(trace_point.iy) -= trace
37 | traces_by_ids -= trace.id
38 | traces_list -= trace
39 | log.debug("removed trace #"+trace.id)
40 | case None => if(show_warn) log.warn("trace #"+trace_id+" not found")
41 | }
42 | }
43 |
44 | def tracesNearCoord(coord:Vec, xrange:Range, yrange:Range, condition:T => Boolean):IndexedSeq[T] = {
45 | val p = point(coord)
46 | super.tracesNearPoint(p, xrange, yrange, condition)
47 | }
48 | def tracesNearCoord(coord:Vec, xrange:Range, condition:T => Boolean):IndexedSeq[T] = tracesNearCoord(coord, xrange, xrange, condition)
49 | def tracesNearCoord(coord:Vec, xrange:Range, yrange:Range):IndexedSeq[T] = {
50 | val p = point(coord)
51 | super.tracesNearPoint(p, xrange, yrange)
52 | }
53 | def tracesNearCoord(coord:Vec, xrange:Range):IndexedSeq[T] = tracesNearCoord(coord:Vec, xrange:Range, xrange:Range)
54 |
55 | override def updateLocation(trace_id:Int, new_coord:Vec):Int = { // TODO: maybe return tuple (new_location, operation_status)
56 | traces_by_ids.get(trace_id) match {
57 | case Some(trace) =>
58 | val old_coord = trace.location
59 | val old_point = point(old_coord)
60 | val new_coord_edges_affected = outsideCoord(new_coord)
61 | if(isCoordOnArea(new_coord_edges_affected)) {
62 | val new_point_edges_affected = point(new_coord_edges_affected)
63 | if(old_coord != new_coord_edges_affected) {
64 | point_matrix(old_point.ix)(old_point.iy) -= trace
65 | point_matrix(new_point_edges_affected.ix)(new_point_edges_affected.iy) += trace
66 | trace._location = new_coord_edges_affected
67 | LOCATION_UPDATED
68 | } else {
69 | //log.warn("didn't update trace "+trace.id+": new point is the same as the old one") // don'tknow exactly if I need such debug message
70 | SAME_LOCATION
71 | }
72 | } else {
73 | log.debug("failed to update trace "+trace_id+": new point is out of area")
74 | OUT_OF_AREA
75 | }
76 | case None =>
77 | log.warn("trace with id "+trace_id+" not found")
78 | TRACE_NOT_FOUND
79 | }
80 | }
81 |
82 | def outsideCoord(coord:Vec):Vec = {
83 | def checkC(c:Float, from:Float, to:Float):Float = {
84 | val dist = to - from
85 | if(c >= to) checkC(c - dist, from, to)
86 | else if(c < from) checkC(c + dist, from, to)
87 | else c
88 | }
89 |
90 | if(solid_edges) coord else {
91 | val x = checkC(coord.x, field_from_x, field_to_x)
92 | val y = checkC(coord.y, field_from_y, field_to_y)
93 |
94 | Vec(x, y)
95 | }
96 | }
97 |
98 | def isCoordOnArea(coord:Vec):Boolean = {
99 | coord.x >= field_from_x && coord.x < field_to_x && coord.y >= field_from_y && coord.y < field_to_y
100 | }
101 |
102 | def hasCollisions(target_trace_id:Int, tested_coord:Vec, min_dist:Float, condition:T => Boolean = (trace) => true):Boolean = {
103 | if(solid_edges && !isCoordOnArea(tested_coord)) true
104 | else {
105 | val tested_coord_edges_affected = outsideCoord(tested_coord)
106 | val min_dist2 = min_dist*min_dist
107 | val modified_condition = (trace:T) => trace.id != target_trace_id && condition(trace)
108 |
109 | val xrange = (2*min_dist/h_x).toInt + 1
110 | val yrange = (2*min_dist/h_y).toInt + 1
111 | tracesNearCoord(tested_coord_edges_affected, -xrange to xrange, -yrange to yrange, modified_condition)
112 | .exists(trace => (trace.location dist2 tested_coord_edges_affected) < min_dist2)
113 | }
114 | }
115 | }
116 |
117 | object CoordTracer {
118 | def apply(field_from_x:Int = property("field.from.x", 0),
119 | field_to_x:Int = property("field.to.x", try {RendererLib.windowWidth} catch {case e:Exception => 800}),
120 | field_from_y:Int = property("field.from.y", 0),
121 | field_to_y:Int = property("field.to.y", try {RendererLib.windowHeight} catch {case e:Exception => 600}),
122 | init_h_x:Int = property("field.h_x", 0),
123 | init_h_y:Int = property("field.h_y", 0),
124 | init_N_x:Int = if(property("field.h_x", 0) == 0) property("field.N_x", (try {RendererLib.windowWidth} catch {case e:Exception => 800})/50) else 0,
125 | init_N_y:Int = if(property("field.h_y", 0) == 0) property("field.N_y", (try {RendererLib.windowHeight} catch {case e:Exception => 600})/50) else 0,
126 | solid_edges:Boolean = property("field.solid_edges", true)) = {
127 | new CoordTracer[Trace](field_from_x,field_to_x,field_from_y,field_to_y,init_h_x,init_h_y,init_N_x,init_N_y,solid_edges) {
128 | def addTrace(coord:Vec):Trace = {addTrace(coord, Trace())}
129 | }
130 | }
131 |
132 | // maybe some other name for this factory method (like 'newTracer', etc)
133 | def create[T <: TraceTrait](field_from_x:Int = property("field.from.x", 0),
134 | field_to_x:Int = property("field.to.x", try {RendererLib.windowWidth} catch {case e:Exception => 800}),
135 | field_from_y:Int = property("field.from.y", 0),
136 | field_to_y:Int = property("field.to.y", try {RendererLib.windowHeight} catch {case e:Exception => 600}),
137 | init_h_x:Int = property("field.h_x", 0),
138 | init_h_y:Int = property("field.h_y", 0),
139 | init_N_x:Int = if(property("field.h_x", 0) == 0) property("field.N_x", (try {RendererLib.windowWidth} catch {case e:Exception => 800})/50) else 0,
140 | init_N_y:Int = if(property("field.h_y", 0) == 0) property("field.N_y", (try {RendererLib.windowHeight} catch {case e:Exception => 600})/50) else 0,
141 | solid_edges:Boolean = property("field.solid_edges", true)) = {
142 | new CoordTracer[T](field_from_x,field_to_x,field_from_y,field_to_y,init_h_x,init_h_y,init_N_x,init_N_y,solid_edges)
143 | }
144 | }
--------------------------------------------------------------------------------
/src/main/scala/com/github/dunnololda/scage/support/messages/unicode/GlyphPage.java:
--------------------------------------------------------------------------------
1 | package com.github.dunnololda.scage.support.messages.unicode;
2 |
3 | import org.lwjgl.opengl.GL11;
4 | import org.lwjgl.opengl.GL12;
5 | import org.newdawn.slick.Color;
6 | import org.newdawn.slick.Image;
7 | import org.newdawn.slick.SlickException;
8 | import org.newdawn.slick.opengl.TextureImpl;
9 | import org.newdawn.slick.opengl.renderer.Renderer;
10 | import org.newdawn.slick.opengl.renderer.SGL;
11 |
12 | import java.awt.*;
13 | import java.awt.font.FontRenderContext;
14 | import java.awt.image.BufferedImage;
15 | import java.awt.image.WritableRaster;
16 | import java.nio.ByteBuffer;
17 | import java.nio.ByteOrder;
18 | import java.nio.IntBuffer;
19 | import java.util.ArrayList;
20 | import java.util.Iterator;
21 | import java.util.List;
22 | import java.util.ListIterator;
23 |
24 | /**
25 | * Stores a number of glyphs on a single texture.
26 | *
27 | * @author Nathan Sweet
28 | */
29 | public class GlyphPage {
30 | /** The interface to OpenGL */
31 | private static final SGL GL = Renderer.get();
32 |
33 | /** The maxium size of an individual glyph */
34 | public static final int MAX_GLYPH_SIZE = 256;
35 |
36 | /** A temporary working buffer */
37 | private static ByteBuffer scratchByteBuffer = ByteBuffer.allocateDirect(MAX_GLYPH_SIZE * MAX_GLYPH_SIZE * 4);
38 |
39 | static {
40 | scratchByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
41 | }
42 |
43 | /** A temporary working buffer */
44 | private static IntBuffer scratchIntBuffer = scratchByteBuffer.asIntBuffer();
45 |
46 |
47 | /** A temporary image used to generate the glyph page */
48 | private static BufferedImage scratchImage = new BufferedImage(MAX_GLYPH_SIZE, MAX_GLYPH_SIZE, BufferedImage.TYPE_INT_ARGB);
49 | /** The graphics context form the temporary image */
50 | private static Graphics2D scratchGraphics = (Graphics2D)scratchImage.getGraphics();
51 |
52 | static {
53 | scratchGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
54 | scratchGraphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
55 | scratchGraphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
56 | }
57 |
58 | /** The render context in which the glyphs will be generated */
59 | public static FontRenderContext renderContext = scratchGraphics.getFontRenderContext();
60 |
61 | /**
62 | * Get the scratch graphics used to generate the page of glyphs
63 | *
64 | * @return The scratch graphics used to build the page
65 | */
66 | public static Graphics2D getScratchGraphics() {
67 | return scratchGraphics;
68 | }
69 |
70 | /** The font this page is part of */
71 | private final UnicodeFont unicodeFont;
72 | /** The width of this page's image */
73 | private final int pageWidth;
74 | /** The height of this page's image */
75 | private final int pageHeight;
76 | /** The image containing the glyphs */
77 | private final MyImage pageImage;
78 | /** The x position of the page */
79 | private int pageX;
80 | /** The y position of the page */
81 | private int pageY;
82 | /** The height of the last row on the page */
83 | private int rowHeight;
84 | /** True if the glyphs are ordered */
85 | private boolean orderAscending;
86 | /** The list of glyphs on this page */
87 | private final List pageGlyphs = new ArrayList(32);
88 |
89 | /**
90 | * Create a new page of glyphs
91 | *
92 | * @param unicodeFont The font this page forms part of
93 | * @param pageWidth The width of the backing texture.
94 | * @param pageHeight The height of the backing texture.
95 | * @throws SlickException if the backing texture could not be created.
96 | */
97 | public GlyphPage(UnicodeFont unicodeFont, int pageWidth, int pageHeight) throws SlickException {
98 | this.unicodeFont = unicodeFont;
99 | this.pageWidth = pageWidth;
100 | this.pageHeight = pageHeight;
101 |
102 | pageImage = new MyImage(pageWidth, pageHeight);
103 | }
104 |
105 | /**
106 | * Loads glyphs to the backing texture and sets the image on each loaded glyph. Loaded glyphs are removed from the list.
107 | *
108 | * If this page already has glyphs and maxGlyphsToLoad is -1, then this method will return 0 if all the new glyphs don't fit.
109 | * This reduces texture binds when drawing since glyphs loaded at once are typically displayed together.
110 | * @param glyphs The glyphs to load.
111 | * @param maxGlyphsToLoad This is the maximum number of glyphs to load from the list. Set to -1 to attempt to load all the
112 | * glyphs.
113 | * @return The number of glyphs that were actually loaded.
114 | * @throws SlickException if the glyph could not be rendered.
115 | */
116 | public int loadGlyphs (List glyphs, int maxGlyphsToLoad) throws SlickException {
117 | if (rowHeight != 0 && maxGlyphsToLoad == -1) {
118 | // If this page has glyphs and we are not loading incrementally, return zero if any of the glyphs don't fit.
119 | int testX = pageX;
120 | int testY = pageY;
121 | int testRowHeight = rowHeight;
122 | for (Iterator iter = getIterator(glyphs); iter.hasNext();) {
123 | Glyph glyph = (Glyph)iter.next();
124 | int width = glyph.getWidth();
125 | int height = glyph.getHeight();
126 | if (testX + width >= pageWidth) {
127 | testX = 0;
128 | testY += testRowHeight;
129 | testRowHeight = height;
130 | } else if (height > testRowHeight) {
131 | testRowHeight = height;
132 | }
133 | if (testY + testRowHeight >= pageWidth) return 0;
134 | testX += width;
135 | }
136 | }
137 |
138 | Color.white.bind();
139 | pageImage.bind();
140 |
141 | int i = 0;
142 | for (Iterator iter = getIterator(glyphs); iter.hasNext();) {
143 | Glyph glyph = (Glyph)iter.next();
144 | int width = Math.min(MAX_GLYPH_SIZE, glyph.getWidth());
145 | int height = Math.min(MAX_GLYPH_SIZE, glyph.getHeight());
146 |
147 | if (rowHeight == 0) {
148 | // The first glyph always fits.
149 | rowHeight = height;
150 | } else {
151 | // Wrap to the next line if needed, or break if no more fit.
152 | if (pageX + width >= pageWidth) {
153 | if (pageY + rowHeight + height >= pageHeight) break;
154 | pageX = 0;
155 | pageY += rowHeight;
156 | rowHeight = height;
157 | } else if (height > rowHeight) {
158 | if (pageY + height >= pageHeight) break;
159 | rowHeight = height;
160 | }
161 | }
162 |
163 | renderGlyph(glyph, width, height);
164 | pageGlyphs.add(glyph);
165 |
166 | pageX += width;
167 |
168 | iter.remove();
169 | i++;
170 | if (i == maxGlyphsToLoad) {
171 | // If loading incrementally, flip orderAscending so it won't change, since we'll probably load the rest next time.
172 | orderAscending = !orderAscending;
173 | break;
174 | }
175 | }
176 |
177 | TextureImpl.bindNone();
178 |
179 | // Every other batch of glyphs added to a page are sorted the opposite way to attempt to keep same size glyps together.
180 | orderAscending = !orderAscending;
181 |
182 | return i;
183 | }
184 |
185 | /**
186 | * Loads a single glyph to the backing texture, if it fits.
187 | *
188 | * @param glyph The glyph to be rendered
189 | * @param width The expected width of the glyph
190 | * @param height The expected height of the glyph
191 | * @throws SlickException if the glyph could not be rendered.
192 | */
193 | private void renderGlyph(Glyph glyph, int width, int height) throws SlickException {
194 | // Draw the glyph to the scratch image using Java2D.
195 | scratchGraphics.setComposite(AlphaComposite.Clear);
196 | scratchGraphics.fillRect(0, 0, MAX_GLYPH_SIZE, MAX_GLYPH_SIZE);
197 | scratchGraphics.setComposite(AlphaComposite.SrcOver);
198 | scratchGraphics.setColor(java.awt.Color.white);
199 | for (Object o : unicodeFont.getEffects()) ((Effect) o).draw(scratchImage, scratchGraphics, unicodeFont, glyph);
200 | glyph.setShape(null); // The shape will never be needed again.
201 |
202 | WritableRaster raster = scratchImage.getRaster();
203 | int[] row = new int[width];
204 | for (int y = 0; y < height; y++) {
205 | raster.getDataElements(0, y, width, 1, row);
206 | scratchIntBuffer.put(row);
207 | }
208 | GL11.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, pageX, pageY, width, height, GL12.GL_BGRA, GL11.GL_UNSIGNED_BYTE,
209 | scratchByteBuffer);
210 | scratchIntBuffer.clear();
211 |
212 | glyph.setImage(pageImage.getSubImage(pageX, pageY, width, height));
213 | }
214 |
215 | /**
216 | * Returns an iterator for the specified glyphs, sorted either ascending or descending.
217 | *
218 | * @param glyphs The glyphs to return if present
219 | * @return An iterator of the sorted list of glyphs
220 | */
221 | private Iterator getIterator(List glyphs) {
222 | if (orderAscending) return glyphs.iterator();
223 | final ListIterator iter = glyphs.listIterator(glyphs.size());
224 | return new Iterator() {
225 | public boolean hasNext () {
226 | return iter.hasPrevious();
227 | }
228 |
229 | public Object next () {
230 | return iter.previous();
231 | }
232 |
233 | public void remove () {
234 | iter.remove();
235 | }
236 | };
237 | }
238 |
239 | /**
240 | * Returns the glyphs stored on this page.
241 | *
242 | * @return A list of {@link Glyph} elements on this page
243 | */
244 | public List getGlyphs () {
245 | return pageGlyphs;
246 | }
247 |
248 | /**
249 | * Returns the backing texture for this page.
250 | *
251 | * @return The image of this page of glyphs
252 | */
253 | public Image getImage () {
254 | return pageImage;
255 | }
256 | }
257 |
258 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/dunnololda/scage/handlers/controller2/SingleController.scala:
--------------------------------------------------------------------------------
1 | package com.github.dunnololda.scage.handlers.controller2
2 |
3 | import org.lwjgl.input.{Keyboard, Mouse}
4 | import collection.mutable
5 | import com.github.dunnololda.scage.support.Vec
6 |
7 | case class SingleKeyEvent(key_code:Int, repeat_time: () => Long, onKeyDown: () => Any, onKeyUp: () => Any)
8 | case class SingleMouseButtonEvent(button_code:Int, repeat_time: () => Long, onButtonDown: Vec => Any, onButtonUp: Vec => Any)
9 | case class WindowButtonEvent(area:List[Vec], button_code:Int, repeat_time: () => Long, onButtonDown: Vec => Any, onButtonUp: Vec => Any)
10 |
11 | trait SingleController extends ScageController {
12 | private val keyboard_key_events = mutable.HashMap[Int, SingleKeyEvent]() // was_pressed, last_pressed_time, repeat_time, onKeyDown, onKeyUp
13 | private var anykey: () => Any = () => {}
14 | private val mouse_button_events = mutable.HashMap[Int, SingleMouseButtonEvent]()
15 | private var on_mouse_motion: Vec => Any = v => {}
16 | private val on_mouse_drag_motion = mutable.HashMap[Int, Vec => Any]()
17 | private var on_mouse_wheel_up: Vec => Any = v => {}
18 | private var on_mouse_wheel_down: Vec => Any = v => {}
19 |
20 | protected def mappedKeyboardKeys:scala.collection.Set[Int] = keyboard_key_events.keySet
21 | protected def mappedMouseButtons:scala.collection.Set[Int] = mouse_button_events.keySet
22 |
23 | def key(key_code:Int, repeat_time: => Long = 0, onKeyDown: => Any, onKeyUp: => Any = {}) = {
24 | keyboard_key_events(key_code) = SingleKeyEvent(key_code, () => repeat_time, () => if(!on_pause) onKeyDown, () => if(!on_pause) onKeyUp)
25 | control_deletion_operations.addOp(() => keyboard_key_events -= key_code, 0)
26 | }
27 | def keyIgnorePause(key_code:Int, repeat_time: => Long = 0, onKeyDown: => Any, onKeyUp: => Any = {}) = {
28 | keyboard_key_events(key_code) = SingleKeyEvent(key_code, () => repeat_time, () => onKeyDown, () => onKeyUp)
29 | control_deletion_operations.addOp(() => keyboard_key_events -= key_code, 0)
30 | }
31 | def keyOnPause(key_code:Int, repeat_time: => Long = 0, onKeyDown: => Any, onKeyUp: => Any = {}):Int = {
32 | keyboard_key_events(key_code) = SingleKeyEvent(key_code, () => repeat_time, () => if(on_pause) onKeyDown, () => if(on_pause) onKeyUp)
33 | control_deletion_operations.addOp(() => keyboard_key_events -= key_code, 0)
34 | }
35 |
36 | def anykey(onKeyDown: => Any) = {
37 | anykey = () => if(!on_pause) onKeyDown
38 | control_deletion_operations.addOp(() => anykey = () => {}, 0)
39 | }
40 | def anykeyIgnorePause(onKeyDown: => Any) = {
41 | anykey = () => onKeyDown
42 | control_deletion_operations.addOp(() => anykey = () => {}, 0)
43 | }
44 | def anykeyOnPause(onKeyDown: => Any) = {
45 | anykey = () => if(on_pause) onKeyDown
46 | control_deletion_operations.addOp(() => anykey = () => {}, 0)
47 | }
48 |
49 | def mouseCoord = Vec(Mouse.getX, Mouse.getY)
50 | def isMouseMoved = Mouse.getDX != 0 || Mouse.getDY != 0
51 | private def mouseButton(button_code:Int, repeat_time: => Long = 0, onButtonDown: Vec => Any, onButtonUp: Vec => Any = Vec => {}) = {
52 | mouse_button_events(button_code) = SingleMouseButtonEvent(button_code, () => repeat_time, onButtonDown, onButtonUp)
53 | control_deletion_operations.addOp(() => mouse_button_events -= button_code, 0)
54 | }
55 |
56 | def leftMouse(repeat_time: => Long = 0, onBtnDown: Vec => Any, onBtnUp: Vec => Any = Vec => {}) = {
57 | mouseButton(0, repeat_time, mouse_coord => if(!on_pause) onBtnDown(mouse_coord), mouse_coord => if(!on_pause) onBtnUp(mouse_coord))
58 | }
59 | def leftMouseIgnorePause(repeat_time: => Long = 0, onBtnDown: Vec => Any, onBtnUp: Vec => Any = Vec => {}) = {
60 | mouseButton(0, repeat_time, onBtnDown, onBtnUp)
61 | }
62 | def leftMouseOnPause(repeat_time: => Long = 0, onBtnDown: Vec => Any, onBtnUp: Vec => Any = Vec => {}) = {
63 | mouseButton(0, repeat_time, mouse_coord => if(on_pause) onBtnDown(mouse_coord), mouse_coord => if(on_pause) onBtnUp(mouse_coord))
64 | }
65 |
66 | def rightMouse(repeat_time: => Long = 0, onBtnDown: Vec => Any, onBtnUp: Vec => Any = Vec => {}) = {
67 | mouseButton(1, repeat_time, mouse_coord => if(!on_pause) onBtnDown(mouse_coord), mouse_coord => if(!on_pause) onBtnUp(mouse_coord))
68 | }
69 | def rightMouseIgnorePause(repeat_time: => Long = 0, onBtnDown: Vec => Any, onBtnUp: Vec => Any = Vec => {}) = {
70 | mouseButton(1, repeat_time, onBtnDown, onBtnUp)
71 | }
72 | def rightMouseOnPause(repeat_time: => Long = 0, onBtnDown: Vec => Any, onBtnUp: Vec => Any = Vec => {}) = {
73 | mouseButton(1, repeat_time, mouse_coord => if(on_pause) onBtnDown(mouse_coord), mouse_coord => if(on_pause) onBtnUp(mouse_coord))
74 | }
75 |
76 | def mouseMotion(onMotion: Vec => Any) = {
77 | on_mouse_motion = mouse_coord => if(!on_pause) onMotion(mouse_coord)
78 | control_deletion_operations.addOp(() => on_mouse_motion = v => {}, 0)
79 | }
80 | def mouseMotionIgnorePause(onMotion: Vec => Any) = {
81 | on_mouse_motion = onMotion
82 | control_deletion_operations.addOp(() => on_mouse_motion = v => {}, 0)
83 | }
84 | def mouseMotionOnPause(onMotion: Vec => Any) = {
85 | on_mouse_motion = mouse_coord => if(on_pause) onMotion(mouse_coord)
86 | control_deletion_operations.addOp(() => on_mouse_motion = v => {}, 0)
87 | }
88 |
89 | private def mouseDrag(button_code:Int, onDrag: Vec => Any) = {
90 | on_mouse_drag_motion(button_code) = onDrag
91 | control_deletion_operations.addOp(() => on_mouse_drag_motion -= button_code, 0)
92 | }
93 |
94 | def leftMouseDrag(onDrag: Vec => Any) = {
95 | mouseDrag(0, mouse_coord => if(!on_pause) onDrag(mouse_coord))
96 | }
97 | def leftMouseDragIgnorePause(onDrag: Vec => Any) = {
98 | mouseDrag(0, onDrag)
99 | }
100 | def leftMouseDragOnPause(onDrag: Vec => Any) = {
101 | mouseDrag(0, mouse_coord => if(on_pause) onDrag(mouse_coord))
102 | }
103 |
104 | def rightMouseDrag(onDrag: Vec => Any) = {
105 | mouseDrag(1, mouse_coord => if(!on_pause) onDrag(mouse_coord))
106 | }
107 | def rightMouseDragIgnorePause(onDrag: Vec => Any) = {
108 | mouseDrag(1, onDrag)
109 | }
110 | def rightMouseDragOnPause(onDrag: Vec => Any) = {
111 | mouseDrag(1, mouse_coord => if(on_pause) onDrag(mouse_coord))
112 | }
113 |
114 | def mouseWheelUp(onWheelUp: Vec => Any) = {
115 | on_mouse_wheel_up = mouse_coord => if(!on_pause) onWheelUp(mouse_coord)
116 | control_deletion_operations.addOp(() => on_mouse_wheel_up = v => {}, 0)
117 | }
118 | def mouseWheelUpIgnorePause(onWheelUp: Vec => Any) = {
119 | on_mouse_wheel_up = onWheelUp
120 | control_deletion_operations.addOp(() => on_mouse_wheel_up = v => {}, 0)
121 | }
122 | def mouseWheelUpOnPause(onWheelUp: Vec => Any) = {
123 | on_mouse_wheel_up = mouse_coord => if(on_pause) onWheelUp(mouse_coord)
124 | control_deletion_operations.addOp(() => on_mouse_wheel_up = v => {}, 0)
125 | }
126 |
127 | def mouseWheelDown(onWheelDown: Vec => Any) = {
128 | on_mouse_wheel_down = mouse_coord => if(!on_pause) onWheelDown(mouse_coord)
129 | control_deletion_operations.addOp(() => on_mouse_wheel_down = v => {}, 0)
130 | }
131 | def mouseWheelDownIgnorePause(onWheelDown: Vec => Any) = {
132 | on_mouse_wheel_down = onWheelDown
133 | control_deletion_operations.addOp(() => on_mouse_wheel_down = v => {}, 0)
134 | }
135 | def mouseWheelDownOnPause(onWheelDown: Vec => Any) = {
136 | on_mouse_wheel_down = mouse_coord => if(on_pause) onWheelDown(mouse_coord)
137 | control_deletion_operations.addOp(() => on_mouse_wheel_down = v => {}, 0)
138 | }
139 |
140 | def checkControls() {
141 | for {
142 | (key, key_data) <- keyboard_key_events
143 | SingleKeyEvent(_, repeat_time_func, onKeyDown, onKeyUp) = key_data
144 | key_press @ KeyPress(_, was_pressed, _, last_pressed_time) <- innerKeyPress(key)
145 | } {
146 | if(Keyboard.isKeyDown(key)) {
147 | val repeat_time = repeat_time_func()
148 | val is_repeatable = repeat_time > 0
149 | if(!was_pressed || (is_repeatable && System.currentTimeMillis() - last_pressed_time > repeat_time)) {
150 | if(!key_press.was_pressed) key_press.pressed_start_time = System.currentTimeMillis()
151 | key_press.was_pressed = true
152 | key_press.updateLastPressedTime(System.currentTimeMillis())
153 | onKeyDown()
154 | }
155 | } else if(was_pressed) {
156 | key_press.was_pressed = false
157 | onKeyUp()
158 | }
159 | }
160 |
161 | if(Keyboard.next && Keyboard.getEventKeyState) anykey()
162 |
163 | val mouse_coord = mouseCoord
164 | val is_mouse_moved = isMouseMoved
165 | if(is_mouse_moved) on_mouse_motion(mouse_coord)
166 |
167 | for {
168 | (button, button_data) <- mouse_button_events
169 | SingleMouseButtonEvent(_, repeat_time_func, onButtonDown, onButtonUp) = button_data
170 | mouse_button_press @ MouseButtonPress(_, was_pressed, _, last_pressed_time) <- innerMouseButtonPress(button)
171 | } {
172 | if(Mouse.isButtonDown(button)) {
173 | val repeat_time = repeat_time_func()
174 | val is_repeatable = repeat_time > 0
175 | if(!was_pressed || (is_repeatable && System.currentTimeMillis() - last_pressed_time > repeat_time)) {
176 | if(!mouse_button_press.was_pressed) mouse_button_press.pressed_start_time = System.currentTimeMillis()
177 | mouse_button_press.was_pressed = true
178 | mouse_button_press.updateLastPressedTime(System.currentTimeMillis())
179 | onButtonDown(mouse_coord)
180 | }
181 | } else if(was_pressed) {
182 | mouse_button_press.was_pressed = false
183 | onButtonUp(mouse_coord)
184 | }
185 | }
186 |
187 | if(is_mouse_moved) {
188 | for {
189 | (button, onDragMotion) <- on_mouse_drag_motion
190 | if Mouse.isButtonDown(button)
191 | } onDragMotion(mouse_coord)
192 | }
193 |
194 | Mouse.getDWheel match {
195 | case x if x > 0 => on_mouse_wheel_up(mouse_coord)
196 | case x if x < 0 => on_mouse_wheel_down(mouse_coord)
197 | case _ =>
198 | }
199 | }
200 | }
--------------------------------------------------------------------------------
/src/main/scala/com/github/dunnololda/scage/support/ScageColor.scala:
--------------------------------------------------------------------------------
1 | package com.github.dunnololda.scage.support
2 |
3 | import com.github.dunnololda.mysimplelogger.MySimpleLogger
4 |
5 | import collection.mutable
6 |
7 |
8 | class ScageColor(val name:String, r:Float, g:Float, b:Float) {
9 | def this(r:Float, g:Float, b:Float) {this("Color", r, g, b)}
10 | def this(c:org.newdawn.slick.Color) {this(c.r, c.g, c.b)}
11 | val red:Float = if(r >= 0 && r <= 1) r else if(r > 1 && r < 256) r/256 else -1
12 | val green:Float = if(g >= 0 && g <= 1) g else if(g > 1 && g < 256) g/256 else -1
13 | val blue:Float = if(b >= 0 && b <= 1) b else if(b > 1 && b < 256) b/256 else -1
14 |
15 | override def equals(other:Any):Boolean = other match {
16 | case that:ScageColor => (that canEqual this) && this.red == that.red && this.green == that.green && this.blue == that.blue
17 | case _ => false
18 | }
19 | override val hashCode:Int = (41*(41*(41 + red) + green) + blue).toInt
20 | def canEqual(other: Any) = other.isInstanceOf[ScageColor]
21 |
22 | def toSlickColor = new org.newdawn.slick.Color(red, green, blue)
23 |
24 | override def toString = name+"(red="+red+" green="+green+" blue="+blue+")"
25 | }
26 |
27 | object ScageColor extends ScageColorTrait {
28 | private val log = MySimpleLogger(this.getClass.getName)
29 |
30 | def apply(name:String, r:Float, g:Float, b:Float) = new ScageColor(name, r, g, b)
31 | def apply(r:Float, g:Float, b:Float) = new ScageColor(r, g, b)
32 |
33 | def unapply(data:Any):Option[(String, Float, Float, Float)] = data match {
34 | case c:ScageColor => Some((c.name, c.red, c.green, c.blue))
35 | case _ => None
36 | }
37 |
38 | }
39 |
40 | trait ScageColorTrait {
41 | val DEFAULT_COLOR = new ScageColor("Default_Color", -1, -1, -1)
42 |
43 | val RED: ScageColor = new ScageColor("Red", 1, 0, 0)
44 | val GREEN: ScageColor = new ScageColor("Green", 0, 1, 0)
45 | val BLUE: ScageColor = new ScageColor("Blue", 0, 0, 1)
46 | val CORNFLOWER: ScageColor = new ScageColor("Cornflower", 0.39f, 0.58f, 0.93f)
47 | val CYAN: ScageColor = new ScageColor("Cyan", 0, 1, 1)
48 | val YELLOW: ScageColor = new ScageColor("Yellow", 1, 1, 0)
49 | val WHITE: ScageColor = new ScageColor("White", 1, 1, 1)
50 | val GRAY: ScageColor = new ScageColor("Gray", 0x80, 0x80, 0x80)
51 | val BLACK: ScageColor = new ScageColor("Black", 0, 0, 0)
52 |
53 | val SNOW: ScageColor = new ScageColor("Snow", 0xFF, 0xFA, 0xFA)
54 | val GHOSTWHITE: ScageColor = new ScageColor("Ghostwhite", 0xF8, 0xF8, 0xFF)
55 | val ANTIQUE_WHITE: ScageColor = new ScageColor("Antique_White", 0xFA, 0xEB, 0xD7)
56 | val CREAM: ScageColor = new ScageColor("Cream", 0xFF, 0xFB, 0xF0)
57 | val PEACHPUFF: ScageColor = new ScageColor("Peachpuff", 0xFF, 0xDA, 0xB9)
58 | val NAVAJO_WHITE: ScageColor = new ScageColor("Navajo_White", 0xFF, 0xDE, 0xAD)
59 | val CORNSILK: ScageColor = new ScageColor("Cornsilk", 0xFF, 0xF8, 0xDC)
60 | val IVORY: ScageColor = new ScageColor("Ivory", 0xFF, 0xFF, 0xF0)
61 | val LEMON_CHIFFON: ScageColor = new ScageColor("Lemon_Chiffon", 0xFF, 0xFA, 0xCD)
62 | val SEASHELL: ScageColor = new ScageColor("Seashell", 0xFF, 0xF5, 0xEE)
63 | val HONEYDEW: ScageColor = new ScageColor("Honeydew", 0xF0, 0xFF, 0xF0)
64 | val AZURE: ScageColor = new ScageColor("Azure", 0xF0, 0xFF, 0xFF)
65 | val LAVENDER: ScageColor = new ScageColor("Lavender", 0xE6, 0xE6, 0xFA)
66 | val LAVENDER_BLUSH: ScageColor = new ScageColor("Lavender_Blush", 0xFF, 0xF0, 0xF5)
67 | val MISTY_ROSE: ScageColor = new ScageColor("Misty_Rose", 0xFF, 0xE4, 0xE1)
68 | val DIM_GRAY: ScageColor = new ScageColor("Dim_Gray", 0x69, 0x69, 0x69)
69 | val SLATE_GRAY: ScageColor = new ScageColor("Slate_Gray", 0x70, 0x80, 0x90)
70 | val LIGHT_SLATE_GRAY: ScageColor = new ScageColor("Light_Slate_Gray", 0x77, 0x88, 0x99)
71 | val LIGHT_GRAY: ScageColor = new ScageColor("Light_Gray", 0xC0, 0xC0, 0xC0)
72 | val MEDIUM_GRAY: ScageColor = new ScageColor("Medium_Gray", 0xA0, 0xA0, 0xA4)
73 | val DARK_GRAY: ScageColor = new ScageColor("Dark_Gray", 0.3f, 0.3f, 0.3f)
74 | val MIDNIGHT_BLUE: ScageColor = new ScageColor("Midnight_Blue", 0x19, 0x19, 0x70)
75 | val NAVY: ScageColor = new ScageColor("Navy", 0x00, 0x00, 0x80)
76 | val SLATE_BLUE: ScageColor = new ScageColor("Slate_Blue", 0x6A, 0x5A, 0xCD)
77 | val LIGHT_SLATE_BLUE: ScageColor = new ScageColor("Light_Slate_Blue", 0x84, 0x70, 0xFF)
78 | val ROYAL_BLUE: ScageColor = new ScageColor("Royal_Blue", 0x41, 0x69, 0xE1)
79 | val SKY_BLUE: ScageColor = new ScageColor("Sky_Blue", 0x87, 0xCE, 0xEB)
80 | val LIGHT_SKY_BLUE: ScageColor = new ScageColor("Light_Sky_Blue", 0x87, 0xCE, 0xFA)
81 | val STEEL_BLUE: ScageColor = new ScageColor("Steel_Blue", 0x46, 0x82, 0xB4)
82 | val LIGHT_STEEL_BLUE: ScageColor = new ScageColor("Light_Steel_Blue", 0xB0, 0xC4, 0xDE)
83 | val LIGHT_BLUE: ScageColor = new ScageColor("Light_Blue", 0xA6, 0xCA, 0xF0)
84 | val POWDER_BLUE: ScageColor = new ScageColor("Powder_Blue", 0xB0, 0xE0, 0xE6)
85 | val PALE_TURQUOISE: ScageColor = new ScageColor("Pale_Turquoise", 0xAF, 0xEE, 0xEE)
86 | val TURQUOISE: ScageColor = new ScageColor("Turquoise", 0x40, 0xE0, 0xD0)
87 | val LIGHT_CYAN: ScageColor = new ScageColor("Light_Cyan", 0xE0, 0xFF, 0xFF)
88 | val DARK_CYAN: ScageColor = new ScageColor("Dark_Cyan", 0x00, 0x80, 0x80)
89 | val CADET_BLUE: ScageColor = new ScageColor("Cadet_Blue", 0x5F, 0x9E, 0xA0)
90 | val AQUAMARINE: ScageColor = new ScageColor("Aquamarine", 0x7F, 0xFF, 0xD4)
91 | val SEAGREEN: ScageColor = new ScageColor("Seagreen", 0x54, 0xFF, 0x9F)
92 | val LIGHT_SEAGREEN: ScageColor = new ScageColor("Light_Seagreen", 0x20, 0xB2, 0xAA)
93 | val PALE_GREEN: ScageColor = new ScageColor("PaleGreen", 0x98, 0xFB, 0x98)
94 | val SPRING_GREEN: ScageColor = new ScageColor("Spring_Green", 0x00, 0xFF, 0x7F)
95 | val LAWN_GREEN: ScageColor = new ScageColor("Lawn_Green", 0x7C, 0xFC, 0x00)
96 | val MEDIUM_GREEN: ScageColor = new ScageColor("Medium_Green", 0xC0, 0xDC, 0xC0)
97 | val DARK_GREEN: ScageColor = new ScageColor("Dark_Green", 0x00, 0x80, 0x00)
98 | val CHARTREUSE: ScageColor = new ScageColor("Chartreuse", 0x7F, 0xFF, 0x00)
99 | val GREEN_YELLOW: ScageColor = new ScageColor("Green_Yellow", 0xAD, 0xFF, 0x2F)
100 | val LIME_GREEN: ScageColor = new ScageColor("Lime_Green", 0x32, 0xCD, 0x32)
101 | val YELLOW_GREEN: ScageColor = new ScageColor("Yellow_Green", 0x9A, 0xCD, 0x32)
102 | val FOREST_GREEN: ScageColor = new ScageColor("Forest_Green", 0x22, 0x8B, 0x22)
103 | val HAKI: ScageColor = new ScageColor("Haki", 0xF0, 0xE6, 0x8C)
104 | val PALE_GOLDENROD: ScageColor = new ScageColor("Pale_Goldenrod", 0xEE, 0xE8, 0xAA)
105 | val LIGHT_GOLDENROD_YELLOW: ScageColor = new ScageColor("Light_Goldenrod_Yellow", 0xFA, 0xFA, 0xD2)
106 | val LIGHT_YELLOW: ScageColor = new ScageColor("Light_Yellow", 0xFF, 0xFF, 0xE0)
107 | val DARK_YELLOW: ScageColor = new ScageColor("Dark_Yellow", 0x80, 0x80, 0x00)
108 | val GOLD: ScageColor = new ScageColor("Gold", 0xFF, 0xD7, 0x00)
109 | val LIGHT_GOLDENROD: ScageColor = new ScageColor("Light_Goldenrod", 0xFF, 0xEC, 0x8B)
110 | val GOLDEN_ROD: ScageColor = new ScageColor("Golden_Rod", 0xDA, 0xA5, 0x20)
111 | val BURLY_WOOD: ScageColor = new ScageColor("Burly_Wood", 0xDE, 0xB8, 0x87)
112 | val ROSY_BROWN: ScageColor = new ScageColor("Rosy_Brown", 0xBC, 0x8F, 0x8F)
113 | val SADDLE_BROWN: ScageColor = new ScageColor("Saddle_Brown", 0x8B, 0x45, 0x13)
114 | val SIENNA: ScageColor = new ScageColor("Sienna", 0xA0, 0x52, 0x2D)
115 | val BEIGE: ScageColor = new ScageColor("Beige", 0xF5, 0xF5, 0xDC)
116 | val WHEAT: ScageColor = new ScageColor("Wheat", 0xF5, 0xDE, 0xB3)
117 | val TAN: ScageColor = new ScageColor("Tan", 0xD2, 0xB4, 0x8C)
118 | val CHOCOLATE: ScageColor = new ScageColor("Chocolate", 0xD2, 0x69, 0x1E)
119 | val FIREBRICK: ScageColor = new ScageColor("Firebrick", 0xB2, 0x22, 0x22)
120 | val BROWN: ScageColor = new ScageColor("Brown", 0xA5, 0x2A, 0x2A)
121 | val SALMON: ScageColor = new ScageColor("Salmon", 0xFA, 0x80, 0x72)
122 | val LIGHT_SALMON: ScageColor = new ScageColor("Light_Salmon", 0xFF, 0xA0, 0x7A)
123 | val ORANGE: ScageColor = new ScageColor("Orange", 0xFF, 0xA5, 0x00)
124 | val CORAL: ScageColor = new ScageColor("Coral", 0xFF, 0x7F, 0x50)
125 | val LIGHT_CORAL: ScageColor = new ScageColor("Light_Coral", 0xF0, 0x80, 0x80)
126 | val ORANGE_RED: ScageColor = new ScageColor("Orange_Red", 0xFF, 0x45, 0x00)
127 | val DARK_RED: ScageColor = new ScageColor("Dark_Red", 0x80, 0x00, 0x00)
128 | val HOT_PINK: ScageColor = new ScageColor("Hot_Pink", 0xFF, 0x69, 0xB4)
129 | val PINK: ScageColor = new ScageColor("Pink", 0xFF, 0xC0, 0xCB)
130 | val LIGHT_PINK: ScageColor = new ScageColor("Light_Pink", 0xFF, 0xB6, 0xC1)
131 | val PALE_VIOLET_RED: ScageColor = new ScageColor("Pale_Violet_Red", 0xDB, 0x70, 0x93)
132 | val MAROON: ScageColor = new ScageColor("Maroon", 0xB0, 0x30, 0x60)
133 | val VIOLET_RED: ScageColor = new ScageColor("Violet_Red", 0xD0, 0x20, 0x90)
134 | val MAGENTA: ScageColor = new ScageColor("Magenta", 0xFF, 0x00, 0xFF)
135 | val DARK_MAGENTA: ScageColor = new ScageColor("Dark_Magenta", 0x80, 0x00, 0x80)
136 | val VIOLET: ScageColor = new ScageColor("Violet", 0xEE, 0x82, 0xEE)
137 | val PLUM: ScageColor = new ScageColor("Plum", 0xDD, 0xA0, 0xDD)
138 | val ORCHID: ScageColor = new ScageColor("Orchid", 0xDA, 0x70, 0xD6)
139 | val BLUE_VIOLET: ScageColor = new ScageColor("Blue_Violet", 0x8A, 0x2B, 0xE2)
140 | val PURPLE: ScageColor = new ScageColor("Purple", 0xA0, 0x20, 0xF0)
141 |
142 | private val colors = new mutable.HashMap[String, ScageColor]()
143 | this.getClass.getDeclaredFields.foreach(field => {
144 | field.setAccessible(true)
145 | val color = try{field.get(ScageColor).asInstanceOf[ScageColor]}
146 | catch {
147 | case ex:Exception =>
148 | //log.error("failed to create color with name "+field.getName+": "+ex.getLocalizedMessage)
149 | DEFAULT_COLOR
150 | }
151 | colors += (field.getName.toUpperCase -> color)
152 | field.setAccessible(false)
153 | })
154 | def fromString(color_str:String) = colors.get(color_str.trim().toUpperCase)
155 | def fromStringOrDefault(color_str:String, default:ScageColor = DEFAULT_COLOR) =
156 | colors.get(color_str.trim().toUpperCase) match {
157 | case Some(c:ScageColor) => c
158 | case None => default
159 | }
160 |
161 | def randomColor = new ScageColor(math.random.toFloat, math.random.toFloat, math.random.toFloat)
162 | }
--------------------------------------------------------------------------------
/src/main/scala/com/github/dunnololda/scage/support/messages/ScageMessageD.scala:
--------------------------------------------------------------------------------
1 | package com.github.dunnololda.scage.support.messages
2 |
3 | import com.github.dunnololda.scage.handlers.RendererLibD.currentColor
4 | import com.github.dunnololda.cli.AppProperties._
5 | import com.github.dunnololda.scage.support.messages.unicode.UnicodeFont
6 | import com.github.dunnololda.mysimplelogger.MySimpleLogger
7 | import com.github.dunnololda.scage.support.{DVec, ScageColor}
8 | import com.github.dunnololda.scage.support.ScageColor._
9 | import org.lwjgl.opengl.GL11
10 |
11 | trait ScageMessageTraitD {
12 | def max_font_size:Double
13 |
14 | // cannot replace it with method with default arguments as its way more inconvinient for a client apps,
15 | // also I have an error: two overloaded methods define default arguments (x:Double, y:Double and coord:DVec)
16 | def print(message:Any, x:Double, y:Double, size:Double, color:ScageColor, align:String)
17 |
18 | def print(message:Any, x:Double, y:Double, size:Double, color:ScageColor) {print(message, x, y, size, color, "default")}
19 | def print(message:Any, x:Double, y:Double, size:Double, align:String) {print(message, x, y, size, DEFAULT_COLOR, align)}
20 | def print(message:Any, x:Double, y:Double, color:ScageColor, align:String) {print(message, x, y, max_font_size, color, align)}
21 | def print(message:Any, x:Double, y:Double, align:String) {print(message, x, y, max_font_size, DEFAULT_COLOR, align)}
22 | def print(message:Any, x:Double, y:Double, size:Double) {print(message, x, y, size, DEFAULT_COLOR, "default")}
23 | def print(message:Any, x:Double, y:Double, color:ScageColor) {print(message, x, y, max_font_size, color, "default")}
24 | def print(message:Any, x:Double, y:Double) {print(message, x, y, max_font_size, currentColor, "default")}
25 |
26 | def print(message:Any, coord:DVec, size:Double, color:ScageColor, align:String) {print(message, coord.x, coord.y, size, color, align)}
27 | def print(message:Any, coord:DVec, size:Double, color:ScageColor) {print(message, coord.x, coord.y, size, color, "default")}
28 | def print(message:Any, coord:DVec, size:Double, align:String) {print(message, coord.x, coord.y, size, DEFAULT_COLOR, align)}
29 | def print(message:Any, coord:DVec, color:ScageColor, align:String) {print(message, coord.x, coord.y, max_font_size, color, align)}
30 | def print(message:Any, coord:DVec, align:String) {print(message, coord.x, coord.y, max_font_size, DEFAULT_COLOR, align)}
31 | def print(message:Any, coord:DVec, size:Double) {print(message, coord.x, coord.y, size, DEFAULT_COLOR, "default")}
32 | def print(message:Any, coord:DVec, color:ScageColor) {print(message, coord.x, coord.y, max_font_size, color, "default")}
33 | def print(message:Any, coord:DVec) {print(message, coord.x, coord.y, max_font_size, DEFAULT_COLOR, "default")}
34 |
35 | def messageBounds(message:Any, size:Double):DVec
36 | def areaForMessage(message:Any, coord:DVec, size:Double, align:String):Seq[DVec]
37 |
38 | def printCentered(message:Any, x:Double, y:Double, size:Double, color:ScageColor) {
39 | print(message, x, y, size, color, align = "center")
40 | }
41 | def printCentered(message:Any, x:Double, y:Double, size:Double) {printCentered(message:Any, x, y, size, DEFAULT_COLOR)}
42 | def printCentered(message:Any, x:Double, y:Double, color:ScageColor) {printCentered(message, x, y, max_font_size, color)}
43 | def printCentered(message:Any, x:Double, y:Double) {printCentered(message, x, y, max_font_size, currentColor)}
44 | def printCentered(message:Any, coord:DVec, size:Double, color:ScageColor) {printCentered(message, coord.x, coord.y, size, color)}
45 | def printCentered(message:Any, coord:DVec, color:ScageColor) {printCentered(message, coord, max_font_size, color)}
46 | def printCentered(message:Any, coord:DVec, size:Double) {printCentered(message, coord, size, DEFAULT_COLOR)}
47 | def printCentered(message:Any, coord:DVec) {printCentered(message, coord, max_font_size, DEFAULT_COLOR)}
48 |
49 | def printStrings(messages:TraversableOnce[Any], x:Double, y:Double, x_interval:Double = 0, y_interval:Double = -20, color:ScageColor = DEFAULT_COLOR) {
50 | var x_pos = x
51 | var y_pos = y
52 | for(message <- messages) {
53 | print(message, x_pos, y_pos, color)
54 | x_pos += x_interval
55 | y_pos += y_interval
56 | }
57 | }
58 |
59 | def printInterface(messages:TraversableOnce[MessageData], parameters:Any*) {
60 | for(MessageData(message, message_x, message_y, message_color) <- messages) {
61 | print(message, message_x, message_y, color = message_color)
62 | }
63 | }
64 | }
65 |
66 | class ScageMessageD(
67 | val fonts_base:String = property("fonts.base", "resources/fonts/"),
68 | val font_file:String = property("font.file", "DroidSans.ttf"),
69 | val max_font_size:Double = property("font.max_size", 18),
70 | val glyph_from:Int = property("font.glyph.from", 1024),
71 | val glyph_to:Int = property("font.glyph.to", 1279),
72 | val glyph_symbols:String = property("font.glyph.symbols", "")
73 | ) extends ScageMessageTraitD {
74 | private val log = MySimpleLogger(this.getClass.getName)
75 |
76 | private var _font:UnicodeFont = _
77 | private def font = {
78 | if (_font == null) {
79 | reloadFont()
80 | }
81 | _font
82 | }
83 |
84 | def reloadFont() {
85 | _font = try {
86 | log.info("loading font "+fonts_base+font_file+"...")
87 | new UnicodeFont(fonts_base+font_file, max_font_size.toFloat, glyph_from, glyph_to, glyph_symbols)
88 | } catch {
89 | case e:Exception =>
90 | log.error("failed to create font: "+e.getLocalizedMessage)
91 | log.error("please provide the path to some unicode ttf font")
92 | System.exit(1)
93 | null
94 | }
95 | }
96 |
97 | // DEFAULT_COLOR support because client programs CAN pass DEFAULT_COLOR
98 | // align is one of those: top-left, top-center, top-right, center-left, center, center-right, bottom-left, bottom-center, bottom-right, default
99 | def print(message:Any, x:Double, y:Double, size:Double, color:ScageColor, align:String) {
100 | val print_color = if(color != DEFAULT_COLOR) color.toSlickColor else currentColor.toSlickColor
101 | val bounds = messageBounds(message, size)
102 | val num_lines = message.toString.filter(_ == '\n').length + 1
103 | val line_height = bounds.y/num_lines
104 | val (new_x, new_y) = align match {
105 | case "top-left" =>
106 | val x_offset = x
107 | val y_offset = y - line_height
108 | (x_offset, y_offset)
109 | case "top-center" =>
110 | val x_offset = x - bounds.ix/2
111 | val y_offset = y - line_height
112 | (x_offset, y_offset)
113 | case "top-right" =>
114 | val x_offset = x - bounds.ix
115 | val y_offset = y - line_height
116 | (x_offset, y_offset)
117 | case "center-left" =>
118 | val x_offset = x
119 | val y_offset = y + (bounds.y/2 - line_height)
120 | (x_offset, y_offset)
121 | case "center" =>
122 | val x_offset = x - bounds.ix/2
123 | val y_offset = y - bounds.iy/2 + (bounds.y - line_height)
124 | (x_offset, y_offset)
125 | case "center-right" =>
126 | val x_offset = x - bounds.ix
127 | val y_offset = y - bounds.iy/2 + (bounds.y - line_height)
128 | (x_offset, y_offset)
129 | case "bottom-left" =>
130 | val x_offset = x
131 | val y_offset = y + (bounds.y - line_height)
132 | (x_offset, y_offset)
133 | case "bottom-center" =>
134 | val x_offset = x - bounds.ix/2
135 | val y_offset = y + (bounds.y - line_height)
136 | (x_offset, y_offset)
137 | case "bottom-right" =>
138 | val x_offset = x - bounds.ix
139 | val y_offset = y + (bounds.y - line_height)
140 | (x_offset, y_offset)
141 | case "default" => (x, y)
142 | case a =>
143 | log.warn("unknow text align: "+a)
144 | (x, y)
145 | }
146 |
147 | GL11.glPushMatrix()
148 | font.drawString(new_x.toInt, new_y.toInt, size.toFloat, message.toString, print_color)
149 | GL11.glPopMatrix()
150 | }
151 |
152 | def messageBounds(message:Any, size:Double = max_font_size):DVec = {
153 | val msg_str = new ColoredString(message.toString, DEFAULT_COLOR).text
154 | DVec(font.getWidth(msg_str), font.getHeight(msg_str))*(size/max_font_size)
155 | }
156 |
157 | // align is one of those: top-left, top-center, top-right, center-left, center, center-right, bottom-left, bottom-center, bottom-right, default
158 | def areaForMessage(message:Any, coord:DVec, size:Double = max_font_size, align:String = "center"):Seq[DVec] = {
159 | val DVec(w, h) = messageBounds(message, size)
160 | align match {
161 | case "top-left" =>
162 | List(coord, coord + DVec(w, 0), coord + DVec(w, -h), coord + DVec(0, -h))
163 | case "top-center" =>
164 | List(coord + DVec(-w/2, 0), coord + DVec(w/2, 0), coord + DVec(w/2, -h), coord + DVec(-w/2, -h))
165 | case "top-right" =>
166 | List(coord + DVec(-w, 0), coord, coord + DVec(0, -h), coord + DVec(-w, -h))
167 | case "center-left" =>
168 | List(coord + DVec(0, h/2), coord + DVec(w, h/2), coord + DVec(w, -h/2), coord + DVec(0, -h/2))
169 | case "center" =>
170 | List(coord + DVec(-w/2, h/2), coord + DVec(w/2, h/2), coord + DVec(w/2, -h/2), coord + DVec(-w/2, -h/2))
171 | case "center-right" =>
172 | List(coord + DVec(-w, h/2), coord + DVec(0, h/2), coord + DVec(0, -h/2), coord + DVec(-w, -h/2))
173 | case "bottom-left" =>
174 | List(coord + DVec(0, h), coord + DVec(w, h), coord + DVec(w, 0), coord)
175 | case "bottom-center" =>
176 | List(coord + DVec(-w/2, h), coord + DVec(w/2, h), coord + DVec(w/2, 0), coord + DVec(-w/2, 0))
177 | case "bottom-right" =>
178 | List(coord + DVec(-w, h), coord + DVec(0, h), coord, coord + DVec(-w, 0))
179 | case "default" =>
180 | val num_lines = message.toString.filter(_ == '\n').length + 1
181 | val lh = h/num_lines
182 | List(coord + DVec(0, h/2-lh), coord + DVec(w, h/2-lh), coord + DVec(w, -h/2-lh), coord + DVec(0, -h/2-lh))
183 | case a =>
184 | log.warn("unknown text align: "+a)
185 | List(coord + DVec(-w/2, h/2), coord + DVec(w/2, h/2), coord + DVec(w/2, -h/2), coord + DVec(-w/2, -h/2))
186 | }
187 | }
188 | }
189 |
190 | object ScageMessageD extends ScageMessageD (
191 | fonts_base = property("fonts.base", "resources/fonts/"),
192 | font_file = property("font.file", "DroidSans.ttf"),
193 | max_font_size = property("font.max_size", 18),
194 | glyph_from = property("font.glyph.from", 1024),
195 | glyph_to = property("font.glyph.to", 1279),
196 | glyph_symbols = property("font.glyph.symbols", "")
197 | )
--------------------------------------------------------------------------------
/src/main/scala/com/github/dunnololda/scage/support/messages/ScageXML.scala:
--------------------------------------------------------------------------------
1 | package com.github.dunnololda.scage.support.messages
2 |
3 | import com.github.dunnololda.cli.AppProperties._
4 | import com.github.dunnololda.cli.FormulaParser
5 | import com.github.dunnololda.mysimplelogger.MySimpleLogger
6 | import com.github.dunnololda.scage.support.ScageColor
7 | import com.github.dunnololda.scage.support.ScageColor._
8 | import org.newdawn.slick.util.ResourceLoader
9 |
10 | import scala.collection.mutable
11 | import scala.xml.XML
12 |
13 | case class InterfaceData(interface_id:String, x:Int = -1, y:Int = -1, xinterval:Int = 0, yinterval:Int = 0, rows:Array[RowData], color:ScageColor = DEFAULT_COLOR)
14 | case class RowData(message_id:String, x:Int = -1, y:Int = -1, placeholders_before:Int = 0, placeholders_in_row:Int = 0, color:ScageColor = DEFAULT_COLOR)
15 | case class MessageData(message:String, x:Int = -1, y:Int = -1, color:ScageColor = DEFAULT_COLOR)
16 |
17 | trait ScageXMLTrait {
18 | def lang:String
19 | def lang_=(new_lang:String)
20 |
21 | def messagesBase:String
22 | def messagesBase_=(new_base:String)
23 |
24 | def interfacesBase:String
25 | def interfacesBase_=(new_base:String)
26 |
27 | def messagesFile:String
28 | def interfacesFile:String
29 |
30 | def xml(message_id:String, parameters:Any*):String
31 | def xmlOrDefault(message_id:String, parameters:Any*):String
32 | def xmlInterface(interface_id:String, parameters:Any*):Array[MessageData]
33 | def xmlInterfaceStrings(interface_id:String, parameters:Any*):Array[String]
34 | }
35 |
36 | class ScageXML(private var _lang:String = property("xml.lang", "en"),
37 | private var messages_base:String = property("xml.strings.base", "resources/strings/" +appName+"_strings"),
38 | private var interfaces_base:String = property("xml.interfaces.base", "resources/interfaces/"+appName+"_interfaces")
39 | ) extends ScageXMLTrait {
40 | private val log = MySimpleLogger(this.getClass.getName)
41 |
42 | def messagesBase = messages_base
43 | def messagesBase_=(new_base:String) {messages_base = new_base}
44 |
45 | def interfacesBase = interfaces_base
46 | def interfacesBase_=(new_base:String) {interfaces_base = new_base}
47 |
48 | def lang = _lang
49 | def lang_=(new_lang:String) {
50 | _lang = new_lang
51 | messages_file = messages_base + "_" + _lang + ".xml"
52 | xml_messages = null
53 |
54 | interfaces_file = interfaces_base + "_" + _lang + ".xml"
55 | xml_interfaces = null
56 | }
57 |
58 | private var messages_file = messages_base + "_" + _lang + ".xml"
59 | def messagesFile = messages_file
60 |
61 | private var xml_messages:mutable.HashMap[String, String] = _
62 | private def xmlMessages = xml_messages match {
63 | case null =>
64 | xml_messages = try {
65 | log.debug("parsing xml messages from file "+messages_file)
66 | XML.load(ResourceLoader.getResourceAsStream(messages_file)) match {
67 | case {messages_list @ _*} =>
68 | mutable.HashMap((for {
69 | message @ {_*} <- messages_list
70 | message_id = (message \ "@id").text
71 | if message_id != ""
72 | message_text = message.text.trim
73 | } yield {
74 | log.debug("added message "+message_id)
75 | (message_id, message_text)
76 | }):_*)
77 | case _ => mutable.HashMap[String, String]() // TODO: log messages
78 | }
79 | } catch {
80 | case ex:Exception => mutable.HashMap[String, String]() // TODO: log messages
81 | }
82 | xml_messages
83 | case _ => xml_messages
84 | }
85 |
86 | private def mergeMessage(xml_message:String, parameters:Any*) = {
87 | parameters.zipWithIndex.foldLeft(xml_message)((message, param) => {
88 | message.replaceAll("\\$"+param._2, param._1.toString)
89 | })
90 | }
91 |
92 | def xml(message_id:String, parameters:Any*):String = {
93 | if(message_id == "") ""
94 | else {
95 | xmlMessages.get(message_id) match {
96 | case Some(message) => mergeMessage(message, parameters:_*)
97 | case None =>
98 | log.warn("failed to find string with id "+message_id)
99 | xmlMessages += (message_id -> message_id)
100 | message_id
101 | }
102 | }
103 | }
104 |
105 | def xmlOrDefault(message_id:String, parameters:Any*):String = {
106 | xmlMessages.get(message_id) match {
107 | case Some(message) => mergeMessage(message, parameters.tail:_*)
108 | case None if parameters.size > 0 =>
109 | log.info("default value for string id "+message_id+" is "+{
110 | if("" == parameters.head) "empty string" else parameters.head
111 | })
112 | xmlMessages += (message_id -> parameters.head.toString)
113 | mergeMessage(parameters.head.toString, parameters.tail:_*)
114 | case _ =>
115 | log.warn("failed to find default message for the id "+message_id)
116 | xmlMessages += (message_id -> message_id)
117 | message_id
118 | }
119 | }
120 |
121 | private def placeholdersAmount(message:String) = {
122 | message.foldLeft(0)((sum, char) => sum + (if(char == '$') 1 else 0))
123 | }
124 |
125 | private lazy val formula_parser = new FormulaParser( // maybe use formula_parser from ScageProperties
126 | constants = mutable.HashMap("window_width" -> property("screen.width", 800),
127 | "window_height" -> property("screen.height", 600))
128 | )
129 |
130 | private var interfaces_file = interfaces_base + "_" + _lang + ".xml"
131 | def interfacesFile = interfaces_file
132 |
133 | private var xml_interfaces:mutable.HashMap[String, InterfaceData] = null
134 | private def xmlInterfaces = xml_interfaces match {
135 | case null =>
136 | xml_interfaces = try {
137 | log.debug("parsing xml interfaces from file "+interfaces_file)
138 | XML.load(ResourceLoader.getResourceAsStream(interfaces_file)) match {
139 | case {interfaces_list @ _*} =>
140 | mutable.HashMap((for {
141 | interface @ {rows_list @ _*} <- interfaces_list
142 | interface_id = (interface \ "@id").text
143 | if interface_id != ""
144 | interface_x = try{formula_parser.calculate((interface \ "@x").text).toInt} catch {case ex:Exception => -1}
145 | interface_y = try{formula_parser.calculate((interface \ "@y").text).toInt} catch {case ex:Exception => -1}
146 | interface_xinterval = try{formula_parser.calculate((interface \ "@xinterval").text).toInt} catch {case ex:Exception => 0}
147 | interface_yinterval = try{formula_parser.calculate((interface \ "@yinterval").text).toInt} catch {case ex:Exception => 0}
148 | interface_color = fromStringOrDefault((interface \ "@color").text)
149 | } yield {
150 | var placeholders_before = 0
151 | var xpos = interface_x
152 | var ypos = interface_y
153 | var curcolor = interface_color
154 | val messages = (for {
155 | row @ {_*}
<- rows_list
156 | message_id = (row \ "@id").text
157 | message = row.text
158 | message_x = try{formula_parser.calculate((row \ "@x").text).toInt} catch {case ex:Exception => -1}
159 | message_y = try{formula_parser.calculate((row \ "@y").text).toInt} catch {case ex:Exception => -1}
160 | placeholders_in_row = placeholdersAmount(message)
161 | message_color = fromStringOrDefault((row \ "@color").text)
162 | } yield {
163 | if(message != "") {
164 | xmlMessages += (message_id -> message)
165 | log.debug("added message "+message_id)
166 | }
167 | val to_yield_x = if(message_x != -1) message_x else xpos // priority to coords and color in tag row
168 | val to_yield_y = if(message_y != -1) message_y else ypos
169 | if(message_color != DEFAULT_COLOR) curcolor = message_color
170 | val to_yield = RowData(message_id, to_yield_x, to_yield_y, placeholders_before, placeholders_in_row, curcolor)
171 | placeholders_before += placeholders_in_row
172 | if(message_x == -1) xpos += interface_xinterval
173 | if(message_y == -1) ypos += interface_yinterval
174 | to_yield
175 | }).toArray
176 | log.debug("added interfaces "+interface_id)
177 | (interface_id, InterfaceData(interface_id, interface_x, interface_y, interface_xinterval, interface_yinterval, messages, interface_color))
178 | }):_*)
179 | case _ => mutable.HashMap[String, InterfaceData]() // TODO: log messages
180 | }
181 | } catch {
182 | case ex:Exception => mutable.HashMap[String, InterfaceData]() // TODO: log messages
183 | }
184 | xml_interfaces
185 | case _ => xml_interfaces
186 | }
187 |
188 | def xmlInterface(interface_id:String, parameters:Any*):Array[MessageData] = {
189 | xmlInterfaces.get(interface_id) match {
190 | case Some(InterfaceData(_, interface_x, interface_y, interface_xinterval, interface_yinterval, rows, interface_color)) =>
191 | for {
192 | RowData(message_id, message_x, message_y, params_from, params_take, message_color) <- rows
193 | } yield {
194 | val to_yield = MessageData(xml(message_id, parameters.drop(params_from).take(params_take): _*), message_x, message_y, message_color)
195 | to_yield
196 | }
197 | case None =>
198 | log.warn("failed to find interfaces with id "+interface_id)
199 | xmlInterfaces += (interface_id -> InterfaceData(interface_id, rows = Array(/*RowData(interface_id)*/)))
200 | xmlMessages += (interface_id -> interface_id)
201 | Array(MessageData(interface_id))
202 | }
203 | }
204 |
205 | def xmlInterfaceStrings(interface_id:String, parameters:Any*):Array[String] = {
206 | xmlInterfaces.get(interface_id) match {
207 | case Some(InterfaceData(_, _, _, _, _, rows, _)) =>
208 | (for {
209 | RowData(message_id, _, _, params_from, params_take, _) <- rows
210 | } yield xml(message_id, parameters.drop(params_from).take(params_take):_*)).toArray
211 | case None =>
212 | log.warn("failed to find interfaces with id "+interface_id)
213 | xmlInterfaces += (interface_id -> InterfaceData(interface_id, rows = Array(RowData(interface_id))))
214 | xmlMessages += (interface_id -> interface_id)
215 | Array(interface_id)
216 | }
217 | }
218 | }
219 |
220 | object ScageXML extends ScageXML(
221 | _lang = property("xml.lang", "en"),
222 | messages_base = property("xml.strings.base", "resources/strings/" +stringProperty("app.name").toLowerCase+"_strings"),
223 | interfaces_base = property("xml.interfaces.base", "resources/interfaces/"+stringProperty("app.name").toLowerCase+"_interfaces")
224 | )
--------------------------------------------------------------------------------
/src/main/scala/com/github/dunnololda/scage/support/messages/ScageMessage.scala:
--------------------------------------------------------------------------------
1 | package com.github.dunnololda.scage.support.messages
2 |
3 | import com.github.dunnololda.scage.handlers.RendererLib.currentColor
4 | import com.github.dunnololda.cli.AppProperties._
5 | import com.github.dunnololda.scage.support.messages.unicode.UnicodeFont
6 | import com.github.dunnololda.mysimplelogger.MySimpleLogger
7 | import com.github.dunnololda.scage.support.{Vec, ScageColor}
8 | import com.github.dunnololda.scage.support.ScageColor._
9 | import org.lwjgl.opengl.GL11
10 |
11 | trait ScageMessageTrait {
12 | def max_font_size:Float
13 |
14 | // cannot replace it with method with default arguments as its way more inconvinient for a client apps,
15 | // also I have an error: two overloaded methods define default arguments (x:Float, y:Float and coord:Vec)
16 | def print(message:Any, x:Float, y:Float, size:Float, color:ScageColor, align:String)
17 |
18 | def print(message:Any, x:Float, y:Float, size:Float, color:ScageColor) {print(message, x, y, size, color, "default")}
19 | def print(message:Any, x:Float, y:Float, size:Float, align:String) {print(message, x, y, size, DEFAULT_COLOR, align)}
20 | def print(message:Any, x:Float, y:Float, color:ScageColor, align:String) {print(message, x, y, max_font_size, color, align)}
21 | def print(message:Any, x:Float, y:Float, align:String) {print(message, x, y, max_font_size, DEFAULT_COLOR, align)}
22 | def print(message:Any, x:Float, y:Float, size:Float) {print(message, x, y, size, DEFAULT_COLOR, "default")}
23 | def print(message:Any, x:Float, y:Float, color:ScageColor) {print(message, x, y, max_font_size, color, "default")}
24 | def print(message:Any, x:Float, y:Float) {print(message, x, y, max_font_size, currentColor, "default")}
25 |
26 | def print(message:Any, coord:Vec, size:Float, color:ScageColor, align:String) {print(message, coord.x, coord.y, size, color, align)}
27 | def print(message:Any, coord:Vec, size:Float, color:ScageColor) {print(message, coord.x, coord.y, size, color, "default")}
28 | def print(message:Any, coord:Vec, size:Float, align:String) {print(message, coord.x, coord.y, size, DEFAULT_COLOR, align)}
29 | def print(message:Any, coord:Vec, color:ScageColor, align:String) {print(message, coord.x, coord.y, max_font_size, color, align)}
30 | def print(message:Any, coord:Vec, align:String) {print(message, coord.x, coord.y, max_font_size, DEFAULT_COLOR, align)}
31 | def print(message:Any, coord:Vec, size:Float) {print(message, coord.x, coord.y, size, DEFAULT_COLOR, "default")}
32 | def print(message:Any, coord:Vec, color:ScageColor) {print(message, coord.x, coord.y, max_font_size, color, "default")}
33 | def print(message:Any, coord:Vec) {print(message, coord.x, coord.y, max_font_size, DEFAULT_COLOR, "default")}
34 |
35 | def messageBounds(message:Any, size:Float):Vec
36 | def areaForMessage(message:Any, coord:Vec, size:Float, align:String):Seq[Vec]
37 |
38 | def printCentered(message:Any, x:Float, y:Float, size:Float, color:ScageColor) {
39 | print(message, x, y, size, color, align = "center")
40 | }
41 | def printCentered(message:Any, x:Float, y:Float, size:Float) {printCentered(message:Any, x, y, size, DEFAULT_COLOR)}
42 | def printCentered(message:Any, x:Float, y:Float, color:ScageColor) {printCentered(message, x, y, max_font_size, color)}
43 | def printCentered(message:Any, x:Float, y:Float) {printCentered(message, x, y, max_font_size, currentColor)}
44 | def printCentered(message:Any, coord:Vec, size:Float, color:ScageColor) {printCentered(message, coord.x, coord.y, size, color)}
45 | def printCentered(message:Any, coord:Vec, color:ScageColor) {printCentered(message, coord, max_font_size, color)}
46 | def printCentered(message:Any, coord:Vec, size:Float) {printCentered(message, coord, size, DEFAULT_COLOR)}
47 | def printCentered(message:Any, coord:Vec) {printCentered(message, coord, max_font_size, DEFAULT_COLOR)}
48 |
49 | def printStrings(messages:TraversableOnce[Any], x:Float, y:Float, x_interval:Float = 0, y_interval:Float = -20, color:ScageColor = DEFAULT_COLOR) {
50 | var x_pos = x
51 | var y_pos = y
52 | for(message <- messages) {
53 | print(message, x_pos, y_pos, color)
54 | x_pos += x_interval
55 | y_pos += y_interval
56 | }
57 | }
58 |
59 | def printInterface(messages:TraversableOnce[MessageData], parameters:Any*) {
60 | for(MessageData(message, message_x, message_y, message_color) <- messages) {
61 | print(message, message_x, message_y, color = message_color)
62 | }
63 | }
64 |
65 | def addGlyphs(from:Int, to:Int): Unit
66 | def addGlyphs(text:String): Unit
67 | }
68 |
69 | class ScageMessage(
70 | val fonts_base:String = property("fonts.base", "resources/fonts/"),
71 | val font_file:String = property("font.file", "DroidSans.ttf"),
72 | val max_font_size:Float = property("font.max_size", 18),
73 | val glyph_from:Int = property("font.glyph.from", 1024),
74 | val glyph_to:Int = property("font.glyph.to", 1279),
75 | val glyph_symbols:String = property("font.glyph.symbols", "")
76 | ) extends ScageMessageTrait {
77 | private val log = MySimpleLogger(this.getClass.getName)
78 |
79 | private var _font:UnicodeFont = _
80 | private def font = {
81 | if (_font == null) {
82 | reloadFont()
83 | }
84 | _font
85 | }
86 |
87 | def addGlyphs(from:Int, to:Int) {
88 | font.addGlyphs(from ,to)
89 | font.loadGlyphs()
90 | }
91 |
92 | def addGlyphs(text:String) {
93 | font.addGlyphs(text)
94 | font.loadGlyphs()
95 | }
96 |
97 | def reloadFont() {
98 | _font = try {
99 | log.info("loading font "+fonts_base+font_file+"...")
100 | new UnicodeFont(fonts_base+font_file, max_font_size, glyph_from, glyph_to, glyph_symbols)
101 | } catch {
102 | case e:Exception =>
103 | log.error("failed to create font: "+e.getLocalizedMessage)
104 | log.error("please provide the path to some unicode ttf font")
105 | System.exit(1)
106 | null
107 | }
108 | }
109 |
110 | // DEFAULT_COLOR support because client programs CAN pass DEFAULT_COLOR
111 | // align is one of those: top-left, top-center, top-right, center-left, center, center-right, bottom-left, bottom-center, bottom-right, default
112 | def print(message:Any, x:Float, y:Float, size:Float, color:ScageColor, align:String) {
113 | val print_color = if(color != DEFAULT_COLOR) color.toSlickColor else currentColor.toSlickColor
114 | val bounds = messageBounds(message, size)
115 | val num_lines = message.toString.filter(_ == '\n').length + 1
116 | val line_height = bounds.y/num_lines
117 | val (new_x, new_y) = align match {
118 | case "top-left" =>
119 | val x_offset = x
120 | val y_offset = y - line_height
121 | (x_offset, y_offset)
122 | case "top-center" =>
123 | val x_offset = x - bounds.x/2
124 | val y_offset = y - line_height
125 | (x_offset, y_offset)
126 | case "top-right" =>
127 | val x_offset = x - bounds.x
128 | val y_offset = y - line_height
129 | (x_offset, y_offset)
130 | case "center-left" =>
131 | val x_offset = x
132 | val y_offset = y + (bounds.y/2 - line_height)
133 | (x_offset, y_offset)
134 | case "center" =>
135 | val x_offset = x - bounds.x/2
136 | val y_offset = y - bounds.y/2 + (bounds.y - line_height)
137 | (x_offset, y_offset)
138 | case "center-right" =>
139 | val x_offset = x - bounds.x
140 | val y_offset = y - bounds.y/2 + (bounds.y - line_height)
141 | (x_offset, y_offset)
142 | case "bottom-left" =>
143 | val x_offset = x
144 | val y_offset = y + (bounds.y - line_height)
145 | (x_offset, y_offset)
146 | case "bottom-center" =>
147 | val x_offset = x - bounds.x/2
148 | val y_offset = y + (bounds.y - line_height)
149 | (x_offset, y_offset)
150 | case "bottom-right" =>
151 | val x_offset = x - bounds.x
152 | val y_offset = y + (bounds.y - line_height)
153 | (x_offset, y_offset)
154 | case "default" => (x, y)
155 | case a =>
156 | log.warn("unknow text align: "+a)
157 | (x, y)
158 | }
159 |
160 | GL11.glPushMatrix()
161 | font.drawString(new_x.toInt, new_y.toInt, size, message.toString, print_color)
162 | GL11.glPopMatrix()
163 | }
164 |
165 | def messageBounds(message:Any, size:Float = max_font_size) = {
166 | val msg_str = new ColoredString(message.toString, DEFAULT_COLOR).text
167 | Vec(font.getWidth(msg_str), font.getHeight(msg_str))*(size/max_font_size)
168 | }
169 |
170 | // align is one of those: top-left, top-center, top-right, center-left, center, center-right, bottom-left, bottom-center, bottom-right, default
171 | def areaForMessage(message:Any, coord:Vec, size:Float = max_font_size, align:String = "center"):Seq[Vec] = {
172 | val Vec(w, h) = messageBounds(message, size)
173 | align match {
174 | case "top-left" =>
175 | List(coord, coord + Vec(w, 0), coord + Vec(w, -h), coord + Vec(0, -h))
176 | case "top-center" =>
177 | List(coord + Vec(-w/2, 0), coord + Vec(w/2, 0), coord + Vec(w/2, -h), coord + Vec(-w/2, -h))
178 | case "top-right" =>
179 | List(coord + Vec(-w, 0), coord, coord + Vec(0, -h), coord + Vec(-w, -h))
180 | case "center-left" =>
181 | List(coord + Vec(0, h/2), coord + Vec(w, h/2), coord + Vec(w, -h/2), coord + Vec(0, -h/2))
182 | case "center" =>
183 | List(coord + Vec(-w/2, h/2), coord + Vec(w/2, h/2), coord + Vec(w/2, -h/2), coord + Vec(-w/2, -h/2))
184 | case "center-right" =>
185 | List(coord + Vec(-w, h/2), coord + Vec(0, h/2), coord + Vec(0, -h/2), coord + Vec(-w, -h/2))
186 | case "bottom-left" =>
187 | List(coord + Vec(0, h), coord + Vec(w, h), coord + Vec(w, 0), coord)
188 | case "bottom-center" =>
189 | List(coord + Vec(-w/2, h), coord + Vec(w/2, h), coord + Vec(w/2, 0), coord + Vec(-w/2, 0))
190 | case "bottom-right" =>
191 | List(coord + Vec(-w, h), coord + Vec(0, h), coord, coord + Vec(-w, 0))
192 | case "default" =>
193 | val num_lines = message.toString.filter(_ == '\n').length + 1
194 | val lh = h/num_lines
195 | List(coord + Vec(0, h/2-lh), coord + Vec(w, h/2-lh), coord + Vec(w, -h/2-lh), coord + Vec(0, -h/2-lh))
196 | case a =>
197 | log.warn("unknown text align: "+a)
198 | List(coord + Vec(-w/2, h/2), coord + Vec(w/2, h/2), coord + Vec(w/2, -h/2), coord + Vec(-w/2, -h/2))
199 | }
200 | }
201 | }
202 |
203 | object ScageMessage extends ScageMessage (
204 | fonts_base = property("fonts.base", "resources/fonts/"),
205 | font_file = property("font.file", "DroidSans.ttf"),
206 | max_font_size = property("font.max_size", 18),
207 | glyph_from = property("font.glyph.from", 1024),
208 | glyph_to = property("font.glyph.to", 1279),
209 | glyph_symbols = property("font.glyph.symbols", "")
210 | )
--------------------------------------------------------------------------------
/src/test/scala/com/github/dunnololda/scage/test/ScageTest.scala:
--------------------------------------------------------------------------------
1 | package com.github.dunnololda.scage.test
2 |
3 | import javax.swing.JOptionPane
4 |
5 | import com.github.dunnololda.scage.ScageLib.{CoordTracer, DynaBall, MultiController, ScageColor, ScageMessage, ScagePhysics, StaticPolygon, Trace, Vec, appVersion, print, property, stopApp, xml, _}
6 | import junit.framework.Assert._
7 | import junit.framework._
8 |
9 | import scala.collection.mutable.ListBuffer
10 | import scala.concurrent.ExecutionContext.Implicits.global
11 | import scala.concurrent.Future
12 | import scala.language.reflectiveCalls
13 |
14 | object ScageTest {
15 | def suite: Test = {
16 | val suite = new TestSuite(classOf[ScageTest])
17 | suite
18 | }
19 |
20 | def main(args : Array[String]) {
21 | junit.textui.TestRunner.run(suite)
22 | }
23 | }
24 |
25 | /**
26 | * Unit test for simple App.
27 | */
28 | class ScageTest extends TestCase("app") {
29 |
30 | /**
31 | * Rigourous Tests :-)
32 | */
33 | def testOK() {
34 | new ScreenApp("Hello World", 1024, 768) with MultiController {
35 | windowTitle += " - "+appVersion
36 | /*scage_log.info("starting main unit "+unit_name+"...")
37 | ScageProperties.properties = properties*/
38 |
39 | def str2color(str:String):ScageColor = {
40 | str match {
41 | case s if s.equalsIgnoreCase("green")=> GREEN
42 | case _ => RED
43 | }
44 | }
45 |
46 | val rect_color = str2color(property("rect.color", "RED"))
47 | val tracer = CoordTracer()
48 |
49 | val trace = tracer.addTrace(Vec(windowWidth/2, windowHeight/2))
50 | render(10) {
51 | drawTraceLocations(tracer, GREEN, 1)
52 | }
53 |
54 | val another_trace = tracer.addTrace(Vec(windowWidth/4, windowHeight/2))
55 |
56 | def moveIfFreeLocation(trace:Trace, delta:Vec) {
57 | val new_location = trace.location + delta
58 | if(!tracer.hasCollisions(trace.id, new_location, 20)) // test collisions using tracer
59 | tracer.updateLocation(trace.id, new_location)
60 | }
61 |
62 | anykey(onKeyDown = println("any key pressed =)")) // test special method to obtain "press any key" event
63 |
64 | private var wanna_go_up = false
65 | key(KEY_W, onKeyDown = wanna_go_up = true, onKeyUp = wanna_go_up = false)
66 | private var wanna_go_left = false
67 | key(KEY_A, onKeyDown = wanna_go_left = true, onKeyUp = wanna_go_left = false)
68 | private var wanna_go_down = false
69 | key(KEY_S, onKeyDown = wanna_go_down = true, onKeyUp = wanna_go_down = false)
70 | private var wanna_go_right = false
71 | key(KEY_D, onKeyDown = wanna_go_right = true, onKeyUp = wanna_go_right = false)
72 |
73 | var dir = Vec.zero
74 | action {
75 | dir = Vec.zero
76 | if(wanna_go_up) dir += Vec(0, 1)
77 | if(wanna_go_down) dir += Vec(0, -1)
78 | if(wanna_go_right) dir += Vec(1, 0)
79 | if(wanna_go_left) dir += Vec(-1, 0)
80 | moveIfFreeLocation(trace, dir.n)
81 | }
82 |
83 | key(KEY_W, onKeyDown = println("also, W was pressed :3")) // test for multiple functions on one key
84 |
85 | private var input_text = ""
86 | key(KEY_F1, onKeyDown = Future {
87 | input_text = JOptionPane.showInputDialog("Input text here")
88 | })
89 | keyIgnorePause(KEY_Q, onKeyDown = {if(keyPressed(KEY_LCONTROL)) stopApp()})
90 |
91 | /*leftMouse(onBtnDown = {
92 | mouse_coord => tracer.updateLocation(trace, mouse_coord)
93 | })*/
94 |
95 | val physics = new ScagePhysics
96 | val poly_physical = physics.addPhysical(new StaticPolygon(Vec(100, 300), Vec(150, 250), Vec(300, 300), Vec(300, 450), Vec(200, 400)))
97 | val poly_render = displayList { // test special method to save any opengl code as display list
98 | drawPolygon(poly_physical.points, CYAN)
99 | }
100 |
101 | val stars = displayList { // I like "starry sky" since high school =)
102 | for(i <- 1 to 100) {
103 | drawPoint(Vec(math.random.toFloat*windowWidth, math.random.toFloat*windowHeight), randomColor)
104 | }
105 | }
106 |
107 | private var target_point = trace.location
108 | mouseMotion { // test mouse motion event
109 | mouse_coord =>
110 | target_point = (absCoord(mouse_coord) - trace.location).n * 20
111 | }
112 | private var x = 0.0f
113 | def period = {
114 | x += 0.01f
115 | if(x > 2*math.Pi) x = 0
116 | (125 * 0.25f* math.sin(x) + 1).toLong
117 | }
118 | actionDynamicPeriod(period) { // test actions with non-static period defined as function
119 | physics.step()
120 | }
121 | leftMouse(onBtnDown = { // test left mouse event
122 | mouse_coord => physics.addPhysical(new DynaBall(trace.location + target_point, 2) {
123 | val ball_trace = tracer.addTrace(trace.location + target_point)
124 | val action_id:Int = action {
125 | tracer.updateLocation(ball_trace.id, coord)
126 | coord = ball_trace.location
127 | /*if(!tracer.isCoordOnArea(coord)) {
128 | isActive = false
129 | delActionOperation(action_id)
130 | }*/
131 | }
132 |
133 | velocity = target_point.n*10
134 | render {
135 | /*if(physics.containsPhysical(this)) */drawFilledCircle(coord, 2, YELLOW)
136 | }
137 | })
138 | })
139 |
140 | backgroundColor = fromStringOrDefault("BLACK", BLACK) // test method to obtain color by name
141 | val another_font = new ScageMessage(max_font_size = 15, font_file = "comic.ttf") // test using two different fonts in one app
142 | interface {
143 | another_font.print(xml("hello.world"), windowWidth/2, windowHeight/2+20, WHITE)
144 | }
145 |
146 | interfaceFromXml("scagetest.help", Array(trace.location, tracer.point(trace.location), fps, input_text))
147 |
148 | private var touches:ListBuffer[(Vec, Long)] = ListBuffer() // test obtaining touching points for physical objects
149 | action {
150 | for {
151 | (point, _) <- poly_physical.touchingPoints
152 | if !touches.exists(_._1 == point)
153 | } touches += ((point, System.currentTimeMillis))
154 |
155 | for {
156 | t <- touches
157 | if System.currentTimeMillis - t._2 > 5000
158 | } touches -= t
159 | }
160 |
161 | render {
162 | def pew(v:Vec) {
163 | drawFilledRect(Vec(30, 30)+v, 60, 20, rect_color)
164 | drawDisplayList(stars,v)
165 | drawFilledCircle(trace.location+v, 10, RED)
166 | drawLine(trace.location+v, trace.location+v + target_point+v)
167 | drawCircle(another_trace.location+v, 10, GREEN)
168 | drawDisplayList(poly_render, v)
169 | //physics.physicals.foreach(p => drawFilledCircle(p.coord+v, 2, YELLOW))
170 | for((point, _) <- touches) drawFilledCircle(point+v, 3, RED)
171 | }
172 | pew(Vec.zero)
173 | if(globalScale > 1) {
174 | pew(Vec(0, windowHeight - 40))
175 | pew(Vec(0, -windowHeight + 40))
176 | pew(Vec(windowWidth - 80, 0))
177 | pew(Vec(-windowWidth + 80, 0))
178 |
179 | pew(Vec(windowWidth - 80, windowHeight - 40))
180 | pew(Vec(windowWidth - 80, -windowHeight + 40))
181 | pew(Vec(-windowWidth + 80, windowHeight - 40))
182 | pew(Vec(-windowWidth + 80, -windowHeight + 40))
183 | }
184 | }
185 | render(10) {
186 | def pew(v:Vec) {drawFilledRect(Vec(100, 30)+v, 60, 20, YELLOW)}
187 | pew(Vec.zero)
188 | if(globalScale > 1) {
189 | pew(Vec(0, windowHeight - 40))
190 | pew(Vec(0, -windowHeight + 40))
191 | pew(Vec(windowWidth - 80, 0))
192 | pew(Vec(-windowWidth + 80, 0))
193 |
194 | pew(Vec(windowWidth - 80, windowHeight - 40))
195 | pew(Vec(windowWidth - 80, -windowHeight + 40))
196 | pew(Vec(-windowWidth + 80, windowHeight - 40))
197 | pew(Vec(-windowWidth + 80, -windowHeight + 40))
198 | }
199 | }
200 | render(-10) {
201 | def pew(v:Vec) {drawLines(tracer.trace_grid.map(_ + v), DARK_GRAY)}
202 | pew(Vec.zero)
203 | if(globalScale > 1) {
204 | pew(Vec(0, windowHeight - 40))
205 | pew(Vec(0, -windowHeight + 40))
206 | pew(Vec(windowWidth - 80, 0))
207 | pew(Vec(-windowWidth + 80, 0))
208 |
209 | pew(Vec(windowWidth - 80, windowHeight - 40))
210 | pew(Vec(windowWidth - 80, -windowHeight + 40))
211 | pew(Vec(-windowWidth + 80, windowHeight - 40))
212 | pew(Vec(-windowWidth + 80, -windowHeight + 40))
213 | }
214 | }
215 |
216 | // scaling test
217 | mouseWheelUp(onWheelUp = m => globalScale += 1)
218 | mouseWheelDown(onWheelDown = m => if(globalScale > 1) globalScale -= 1)
219 | center = if(globalScale > 1) trace.location else windowCenter
220 |
221 | // test network features: server and client in one app. Client sends 2d vectors to server and server sends back normalized vector
222 | /*NetServer.startServer(
223 | onNewConnection = {
224 | client => client.send(State(("hello" -> "send me vec and I send you back its n!")))
225 | (true, "")
226 | },
227 | onClientDataReceived = {
228 | (client, received_data) => received_data.neededKeys {
229 | case ("vec", vec:Vec) => {
230 | client.send(State(("n" -> vec.n)))
231 | }
232 | }
233 | },
234 | onClientQuestion = {(client, question) =>
235 | val answer = State()
236 | question.neededKeys {
237 | case ("Are u ready?", true) => answer.add("yes")
238 | }
239 | answer
240 | }
241 | )
242 |
243 | action {
244 | if(NetServer.connectionPort != 0) {
245 | NetClient.startClient(
246 | server_url = "localhost",
247 | port = NetServer.connectionPort,
248 | onServerDataReceived = {
249 | received_data => received_data.neededKeys {
250 | case ("hello", hello_msg) =>
251 | val random_vec = Vec((math.random*100).toInt, (math.random*100).toInt)
252 | NetClient.send(State(("vec" -> random_vec)))
253 | case ("n", n:Vec) =>
254 | println("received n: "+n)
255 | println("waiting 5 sec...")
256 | Thread.sleep(5000)
257 | println("asking if the server is ready to receive another vec")
258 | val answer = NetClient.askServer(State("Are u ready?"))
259 | if(answer.contains("yes")) {
260 | val random_vec = Vec((math.random*100).toInt, (math.random*100).toInt)
261 | NetClient.send(State(("vec" -> random_vec)))
262 | }
263 | }
264 | }
265 | )
266 | deleteSelf()
267 | }
268 | }
269 |
270 | dispose {
271 | NetServer.stopServer()
272 | NetClient.stopClient()
273 | }*/
274 |
275 | render {
276 | openglMove(windowCenter)
277 | openglRotate((msecsFromInit % 3600).toFloat/10)
278 | drawFilledPolygon(Array(Vec(-20, -5), Vec(20, -5), Vec(0, 20)), GREEN)
279 | }
280 |
281 | // window button test
282 | val window_button_area = List(windowCenter + windowCenter/2 + Vec(-40, -40),
283 | windowCenter + windowCenter/2 + Vec(40, -40),
284 | windowCenter + windowCenter/2 + Vec(0, 40))
285 | val window_button = leftMouseOnArea(area = window_button_area,
286 | onBtnDown = m => {println("window button pressed")},
287 | onBtnUp = m => {println("window button released")})
288 | render {
289 | val window_button_pressed = leftMousePressed && mouseOnArea(window_button_area)
290 | currentColor = if(window_button_pressed) RED else WHITE
291 | drawPolygon(window_button_area)
292 | if(!window_button_pressed) print("Press Me", windowCenter + windowCenter/2, align = "center")
293 | else print("Release Me", windowCenter + windowCenter/2, align = "center")
294 | }
295 |
296 | }.main(Array[String]())
297 | assertTrue(true)
298 | }
299 | }
300 |
301 | /*test*/
302 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/dunnololda/scage/support/tracer3/ScageTracer.scala:
--------------------------------------------------------------------------------
1 | package com.github.dunnololda.scage.support.tracer3
2 |
3 | import com.github.dunnololda.scage.support.Vec
4 | import com.github.dunnololda.cli.AppProperties._
5 | import com.github.dunnololda.mysimplelogger.MySimpleLogger
6 | import com.github.dunnololda.scage.handlers.RendererLib
7 | import collection.mutable.ArrayBuffer
8 | import collection.mutable
9 | import scala.language.implicitConversions
10 |
11 | class ScageTracer[T <: TraceTrait](val field_from_x:Int = property("field.from.x", 0),
12 | val field_to_x:Int = property("field.to.x", try {RendererLib.windowWidth} catch {case e:Exception => 800}),
13 | val field_from_y:Int = property("field.from.y", 0),
14 | val field_to_y:Int = property("field.to.y", try {RendererLib.windowHeight} catch {case e:Exception => 600}),
15 | init_h_x:Int = property("field.h_x", 0),
16 | init_h_y:Int = property("field.h_y", 0),
17 | init_N_x:Int = if(property("field.h_x", 0) == 0) property("field.N_x", (try {RendererLib.windowWidth} catch {case e:Exception => 800})/50) else 0,
18 | init_N_y:Int = if(property("field.h_y", 0) == 0) property("field.N_y", (try {RendererLib.windowHeight} catch {case e:Exception => 800})/50) else 0,
19 | val solid_edges:Boolean = property("field.solid_edges", true)) {
20 | private val log = MySimpleLogger(this.getClass.getName)
21 |
22 | val field_width = field_to_x - field_from_x
23 | val field_height = field_to_y - field_from_y
24 |
25 | val N_x = if(init_h_x != 0) field_width/init_h_x else init_N_x
26 | val N_y = if(init_h_y != 0) field_height/init_h_y else init_N_y
27 |
28 | val h_x = if(init_h_x == 0) field_width/init_N_x else init_h_x
29 | val h_y = if(init_h_y == 0) field_height/init_N_y else init_h_y
30 |
31 | log.info("creating tracer "+this.getClass.getName+": h_x="+h_x+" h_y="+h_y+" N_x="+N_x+" N_y="+N_y)
32 |
33 | // for client classes - children of ScageTracer
34 | protected def setTraceLocation(trace:T, new_location:Vec) {trace._location = new_location}
35 | protected implicit def trace2updateable(trace:T): Object {def __location_=(new_location: Vec): Unit; def __location: Vec} = new {
36 | def __location = trace._location
37 | def __location_=(new_location:Vec) {trace._location = new_location}
38 | }
39 |
40 | def isPointOnArea(x:Float, y:Float):Boolean = x >= 0 && x < N_x && y >= 0 && y < N_y
41 | def isPointOnArea(point:Vec):Boolean = isPointOnArea(point.x, point.y)
42 | def outsidePoint(point:Vec):Vec = {
43 | def checkC(c:Float, dist:Int):Float = {
44 | if(c >= dist) checkC(c - dist, dist)
45 | else if(c < 0) checkC(c + dist, dist)
46 | else c
47 | }
48 | if(solid_edges) point
49 | else Vec(checkC(point.x, N_x), checkC(point.y, N_y))
50 | }
51 |
52 | def point(x:Float, y:Float):Vec = Vec(((x - field_from_x)/field_width*N_x).toInt,
53 | ((y - field_from_y)/field_height*N_y).toInt)
54 | def point(p:Vec):Vec = point(p.x, p.y)
55 | def pointCenter(x:Float, y:Float):Vec = Vec(field_from_x + x*h_x + h_x/2, field_from_y + y*h_y + h_y/2)
56 | def pointCenter(p:Vec):Vec = pointCenter(p.x, p.y)
57 |
58 | protected def initMatrix(matrix:Array[Array[ArrayBuffer[T]]]) {
59 | for(i <- 0 until matrix.length; j <- 0 until matrix.head.length) {matrix(i)(j) = ArrayBuffer[T]()}
60 | }
61 | protected def clearMatrix(matrix:Array[Array[ArrayBuffer[T]]]) {
62 | for(i <- 0 until matrix.length; j <- 0 until matrix.head.length) {matrix(i)(j).clear()}
63 | }
64 |
65 | // it is very critical for the structures below to be changed only inside ScageTracer
66 | // but for convenience I keep them protected, so client classes - children of ScageTracer can read them
67 | protected val point_matrix = Array.ofDim[ArrayBuffer[T]](N_x, N_y)
68 | initMatrix(point_matrix)
69 | protected val traces_by_ids = mutable.HashMap[Int, T]()
70 | protected var traces_list = ArrayBuffer[T]()
71 | def tracesList = traces_list.toList
72 |
73 | def addTrace(point:Vec, trace:T) = {
74 | if(isPointOnArea(point)) {
75 | trace._location = point
76 | point_matrix(point.ix)(point.iy) += trace
77 | traces_by_ids += trace.id -> trace
78 | traces_list += trace
79 | log.debug("added new trace #"+trace.id+" in point ("+trace.location+")")
80 | } else log.warn("failed to add trace: point ("+point+") is out of area")
81 | trace
82 | }
83 | def addTraces(traces_in_locations:(Vec, T)*) = {
84 | for {
85 | (location, trace) <- traces_in_locations
86 | } yield addTrace(location, trace)
87 | }
88 |
89 | def containsTrace(trace_id:Int) = traces_by_ids.contains(trace_id)
90 | def containsTrace(trace:T) = traces_by_ids.contains(trace.id)
91 |
92 | protected def _removeTrace(trace_id:Int, show_warn:Boolean) {
93 | traces_by_ids.get(trace_id) match {
94 | case Some(trace) =>
95 | point_matrix(trace.location.ix)(trace.location.iy) -= trace
96 | traces_by_ids -= trace.id
97 | traces_list -= trace
98 | log.debug("removed trace #"+trace.id)
99 | case None => if(show_warn) log.warn("trace #"+trace_id+" not found")
100 | }
101 | }
102 |
103 | def removeTrace(trace:T) {_removeTrace(trace.id, show_warn = true)}
104 | def removeTraceNoWarn(trace:T) {_removeTrace(trace.id, show_warn = false)}
105 | def removeTraceById(trace_id:Int) {_removeTrace(trace_id, show_warn = true)}
106 | def removeTraceByIdNoWarn(trace_id:Int) {_removeTrace(trace_id, show_warn = false)}
107 |
108 | // WARNING: its very important not to directly pass contents of point_matrix(trace.location.ix)(trace.location.iy),
109 | // traces_by_ids or traces_list as it cannot remove from itself properly, causing NullPointerException!
110 | // maybe return result (true/false)
111 | // maybe call toList before foreach to copy the list
112 | def removeTraces(traces_to_remove:T*) {traces_to_remove.foreach(t => _removeTrace(t.id, show_warn = true))}
113 | def removeTracesNoWarn(traces_to_remove:T*) {traces_to_remove.foreach(t => _removeTrace(t.id, show_warn = false))}
114 | def removeTracesById(trace_ids:Int*) {trace_ids.foreach(t_id => _removeTrace(t_id, show_warn = true))}
115 | def removeTracesByIdNoWarn(trace_ids:Int*) {trace_ids.foreach(t_id => _removeTrace(t_id, show_warn = false))}
116 | def removeAllTraces() {
117 | clearMatrix(point_matrix)
118 | traces_by_ids.clear()
119 | traces_list.clear()
120 | log.info("deleted all traces")
121 | }
122 | def removeTracesInPoint(point:Vec) {removeTraces(tracesInPoint(point):_*)}
123 | def removeTracesInPoint(x:Int, y:Int) {removeTraces(tracesInPoint(x, y):_*)}
124 |
125 | def tracesInPoint(point:Vec, condition:T => Boolean):List[T] = tracesInPoint(point.ix, point.iy, condition)
126 | def tracesInPoint(x:Int, y:Int, condition:T => Boolean) = {
127 | if(!isPointOnArea(x, y)) Nil
128 | else {
129 | (for {
130 | trace <- point_matrix(x)(y)
131 | if condition(trace)
132 | } yield trace).toList
133 | }
134 | }
135 |
136 | def tracesInPoint(point:Vec):List[T] = tracesInPoint(point.ix, point.iy)
137 | def tracesInPoint(x:Int, y:Int) = {
138 | if(!isPointOnArea(x, y)) Nil
139 | else point_matrix(x)(y).toList
140 | }
141 |
142 | def tracesInPointRange(xrange:Range, yrange:Range, condition:T => Boolean):IndexedSeq[T] = tracesNearPoint(Vec.zero, xrange, yrange, condition)
143 | def tracesInPointRange(xrange:Range, condition:T => Boolean):IndexedSeq[T] = tracesNearPoint(Vec.zero, xrange, xrange, condition)
144 | def tracesInPointRange(xrange:Range, yrange:Range):IndexedSeq[T] = tracesNearPoint(Vec.zero, xrange, yrange)
145 | def tracesInPointRange(xrange:Range):IndexedSeq[T] = tracesNearPoint(Vec.zero, xrange, xrange)
146 |
147 | def tracesNearPoint(point:Vec, xrange:Range, yrange:Range, condition:T => Boolean):IndexedSeq[T] = {
148 | for {
149 | i <- xrange
150 | j <- yrange
151 | near_point = outsidePoint(point + Vec(i, j))
152 | if isPointOnArea(near_point)
153 | trace <- tracesInPoint(near_point, condition)
154 | } yield trace
155 | }
156 | def tracesNearPoint(point:Vec, xrange:Range, condition:T => Boolean):IndexedSeq[T] = tracesNearPoint(point, xrange, xrange, condition)
157 | def tracesNearPoint(point:Vec, xrange:Range, yrange:Range):IndexedSeq[T] = {
158 | for {
159 | i <- xrange
160 | j <- yrange
161 | near_point = outsidePoint(point + Vec(i, j))
162 | if isPointOnArea(near_point)
163 | trace <- tracesInPoint(near_point)
164 | } yield trace
165 | }
166 | def tracesNearPoint(point:Vec, xrange:Range):IndexedSeq[T] = tracesNearPoint(point, xrange, xrange)
167 |
168 | val LOCATION_UPDATED = 0
169 | val SAME_LOCATION = 1
170 | val OUT_OF_AREA = 2
171 | val TRACE_NOT_FOUND = 3
172 | def updateLocation(trace_id:Int, new_point:Vec):Int = { // TODO: maybe return tuple (new_location, operation_status), maybe rename
173 | traces_by_ids.get(trace_id) match {
174 | case Some(updateable_trace) =>
175 | val old_point = updateable_trace.location
176 | val new_point_edges_affected = outsidePoint(new_point)
177 | if(isPointOnArea(new_point_edges_affected)) {
178 | if(old_point != new_point_edges_affected) {
179 | point_matrix(old_point.ix)(old_point.iy) -= updateable_trace
180 | point_matrix(new_point_edges_affected.ix)(new_point_edges_affected.iy) += updateable_trace
181 | updateable_trace._location = new_point_edges_affected
182 | LOCATION_UPDATED
183 | } else {
184 | //log.warn("didn't update trace "+trace.id+": new point is the same as the old one") // don'tknow exactly if I need such debug message
185 | SAME_LOCATION
186 | }
187 | } else {
188 | log.debug("failed to update trace "+trace_id+": new point is out of area")
189 | OUT_OF_AREA
190 | }
191 | case None =>
192 | log.warn("trace with id "+trace_id+" not found")
193 | TRACE_NOT_FOUND
194 | }
195 | }
196 | def updateLocation(trace:T, new_point:Vec):Int = updateLocation(trace.id, new_point) // TODO: maybe rename
197 |
198 | def moveTrace(trace:T, delta:Vec) = {
199 | updateLocation(trace.id, trace.location + delta)
200 | }
201 |
202 | def randomPoint(leftup_x:Int = 0, leftup_y:Int = N_y-1, width:Int = N_x, height:Int = N_y) = {
203 | val x = leftup_x + (math.random*width).toInt
204 | val y = leftup_y - (math.random*height).toInt
205 | Vec(x,y)
206 | }
207 | def randomPointWithCondition(leftup_x:Int = 0,
208 | leftup_y:Int = N_y-1,
209 | width:Int = N_x,
210 | height:Int = N_y,
211 | condition:Vec => Boolean,
212 | num_tries:Int = 10):Option[Vec] = {
213 | (0 until num_tries).view.map(i => randomPoint()).find(p => condition(p))
214 | }
215 | def randomCoord(leftup_x:Int = field_from_x, leftup_y:Int = field_to_y-1, width:Int = field_to_x - field_from_x, height:Int = field_to_y - field_from_y) = {
216 | pointCenter(point(randomPoint(leftup_x, leftup_y, width, height)))
217 | }
218 | def randomCoordWithCondition(leftup_x:Int = field_from_x,
219 | leftup_y:Int = field_to_y-1,
220 | width:Int = field_to_x - field_from_x,
221 | height:Int = field_to_y - field_from_y,
222 | condition:Vec => Boolean,
223 | num_tries:Int = 10):Option[Vec] = {
224 | (0 until num_tries).view.map(i => randomCoord()).find(coord => condition(coord))
225 | }
226 |
227 | lazy val trace_grid = {
228 | val x_lines = (field_from_x to field_to_x by h_x).foldLeft(List[Vec]())((lines, x) => Vec(x, field_from_y) :: Vec(x, field_to_y) :: lines)
229 | val y_lines = (field_from_y to field_to_y by h_y).foldLeft(List[Vec]())((lines, y) => Vec(field_from_x, y) :: Vec(field_to_x, y) :: lines)
230 | x_lines ::: y_lines
231 | }
232 | }
233 |
234 | object ScageTracer {
235 | def apply(field_from_x:Int = property("field.from.x", 0),
236 | field_to_x:Int = property("field.to.x", try {RendererLib.windowWidth} catch {case e:Exception => 800}),
237 | field_from_y:Int = property("field.from.y", 0),
238 | field_to_y:Int = property("field.to.y", try {RendererLib.windowHeight} catch {case e:Exception => 600}),
239 | init_h_x:Int = property("field.h_x", 0),
240 | init_h_y:Int = property("field.h_y", 0),
241 | init_N_x:Int = if(property("field.h_x", 0) == 0) property("field.N_x", (try {RendererLib.windowWidth} catch {case e:Exception => 800})/50) else 0,
242 | init_N_y:Int = if(property("field.h_y", 0) == 0) property("field.N_y", (try {RendererLib.windowHeight} catch {case e:Exception => 600})/50) else 0,
243 | solid_edges:Boolean = property("field.solid_edges", true)) = {
244 | new ScageTracer[Trace](field_from_x,field_to_x,field_from_y,field_to_y,init_h_x,init_h_y,init_N_x,init_N_y,solid_edges) {
245 | def addTrace(point:Vec):Trace = {addTrace(point, Trace())}
246 | }
247 | }
248 |
249 | // maybe some other name for this factory method (like 'newTracer', etc)
250 | def create[T <: TraceTrait](field_from_x:Int = property("field.from.x", 0),
251 | field_to_x:Int = property("field.to.x", try {RendererLib.windowWidth} catch {case e:Exception => 800}),
252 | field_from_y:Int = property("field.from.y", 0),
253 | field_to_y:Int = property("field.to.y", try {RendererLib.windowHeight} catch {case e:Exception => 600}),
254 | init_h_x:Int = property("field.h_x", 0),
255 | init_h_y:Int = property("field.h_y", 0),
256 | init_N_x:Int = if(property("field.h_x", 0) == 0) property("field.N_x", (try {RendererLib.windowWidth} catch {case e:Exception => 800})/50) else 0,
257 | init_N_y:Int = if(property("field.h_y", 0) == 0) property("field.N_y", (try {RendererLib.windowHeight} catch {case e:Exception => 600})/50) else 0,
258 | solid_edges:Boolean = property("field.solid_edges", true)) = {
259 | new ScageTracer[T](field_from_x,field_to_x,field_from_y,field_to_y,init_h_x,init_h_y,init_N_x,init_N_y,solid_edges)
260 | }
261 | }
--------------------------------------------------------------------------------
/src/main/scala/com/github/dunnololda/scage/ScageScreen.scala:
--------------------------------------------------------------------------------
1 | package com.github.dunnololda.scage
2 |
3 | import java.applet.Applet
4 | import java.awt.{BorderLayout, Canvas}
5 | import com.github.dunnololda.cli.Imports._
6 | import com.github.dunnololda.mysimplelogger.MySimpleLogger
7 | import com.github.dunnololda.scage.handlers._
8 | import com.github.dunnololda.scage.handlers.controller2.{ScageController, SingleController}
9 | import com.github.dunnololda.scage.handlers.controller3.{ActorSingleController, ControllerActorSystem}
10 | import com.github.dunnololda.scage.support.XInitThreadsCaller.xInitThreads
11 | import org.lwjgl.opengl.Display
12 |
13 | // abstract classes instead of traits to make it easy to use with MultiController
14 | abstract class Screen(val unit_name: String = "Scage Screen") extends Scage with Renderer with ScageController {
15 | private val log = MySimpleLogger(this.getClass.getName)
16 |
17 | override def run(): Unit = {
18 | log.info("starting screen " + unit_name + "...")
19 | executePreinits()
20 | executeInits()
21 | is_running = true
22 | prepareRendering()
23 | log.info(unit_name + ": run")
24 | while (is_running && Scage.isAppRunning) {
25 | checkControlsAndExecuteActions()
26 | performRendering()
27 | }
28 | executeClears()
29 | executeDisposes()
30 | scage_log.info(unit_name + " was stopped")
31 | }
32 | }
33 |
34 | abstract class ScreenD(val unit_name: String = "Scage Screen") extends Scage with RendererD with ScageController {
35 | private val log = MySimpleLogger(this.getClass.getName)
36 |
37 | override def run(): Unit = {
38 | log.info("starting screen " + unit_name + "...")
39 | executePreinits()
40 | executeInits()
41 | is_running = true
42 | prepareRendering()
43 | log.info(unit_name + ": run")
44 | while (is_running && Scage.isAppRunning) {
45 | checkControlsAndExecuteActions()
46 | performRendering()
47 | }
48 | executeClears()
49 | executeDisposes()
50 | scage_log.info(unit_name + " was stopped")
51 | }
52 | }
53 |
54 | abstract class ScreenApp(
55 | title: String = property("app.name", "Scage App"),
56 | width: Int = property("screen.width", 800),
57 | height: Int = property("screen.height", 600)
58 | ) extends Screen(title) with Cli {
59 | val app_start_moment = System.currentTimeMillis()
60 |
61 | def msecsFromAppStart: Long = System.currentTimeMillis() - app_start_moment
62 |
63 | override def run(): Unit = {
64 | executePreinits()
65 | executeInits()
66 | is_running = true
67 | prepareRendering()
68 | scage_log.info(unit_name + ": run")
69 | while (is_running && Scage.isAppRunning) {
70 | checkControlsAndExecuteActions()
71 | performRendering()
72 | }
73 | RendererLib.renderExitMessage()
74 | executeClears()
75 | executeDisposes()
76 | }
77 |
78 | override def main(args: Array[String]) {
79 | scage_log.info("starting main screen " + title + "...")
80 | RendererLib.initgl(width, height, title)
81 | RendererLib.drawWelcomeMessages()
82 | super.main(args)
83 | run()
84 | RendererLib.destroygl()
85 | scage_log.info(title + " was stopped")
86 | System.exit(0) // need explicit exit for the app's utilizing NetServer/NetClient as they have actors
87 | }
88 | }
89 |
90 | abstract class ScreenAppD(
91 | title: String = property("app.name", "Scage App"),
92 | width: Int = property("screen.width", 800),
93 | height: Int = property("screen.height", 600)
94 | ) extends ScreenD(title) with Cli {
95 | val app_start_moment = System.currentTimeMillis()
96 |
97 | def msecsFromAppStart: Long = System.currentTimeMillis() - app_start_moment
98 |
99 | override def run(): Unit = {
100 | executePreinits()
101 | executeInits()
102 | is_running = true
103 | prepareRendering()
104 | scage_log.info(unit_name + ": run")
105 | while (is_running && Scage.isAppRunning) {
106 | checkControlsAndExecuteActions()
107 | performRendering()
108 | }
109 | RendererLibD.renderExitMessage()
110 | executeClears()
111 | executeDisposes()
112 | }
113 |
114 | override def main(args: Array[String]) {
115 | scage_log.info("starting main screen " + title + "...")
116 | RendererLibD.initgl(width, height, title)
117 | RendererLibD.drawWelcomeMessages()
118 | super.main(args)
119 | run()
120 | RendererLibD.destroygl()
121 | scage_log.info(title + " was stopped")
122 | System.exit(0) // need explicit exit for the app's utilizing NetServer/NetClient as they have actors
123 | }
124 | }
125 |
126 | abstract class ScreenAppMT(
127 | title: String = property("app.name", "Scage App"),
128 | width: Int = property("screen.width", 800),
129 | height: Int = property("screen.height", 600)
130 | ) extends Screen(title) with Cli {
131 | val app_start_moment = System.currentTimeMillis()
132 |
133 | def msecsFromAppStart: Long = System.currentTimeMillis() - app_start_moment
134 |
135 | override def run(): Unit = {
136 | executePreinits()
137 | executeInits()
138 | is_running = true
139 | prepareRendering()
140 | scage_log.info(unit_name + ": run")
141 | while (is_running && Scage.isAppRunning) {
142 | checkControlsAndExecuteActions()
143 | performRendering()
144 | }
145 | RendererLib.renderExitMessage()
146 | executeClears()
147 | executeDisposes()
148 | }
149 |
150 | override def main(args: Array[String]) {
151 | scage_log.info("starting main screen " + title + "...")
152 | xInitThreads(scage_log)
153 | ControllerActorSystem.createControllerActor()
154 | ControllerActorSystem.initGLAndReleaseContext(width, height, title)
155 | Display.makeCurrent()
156 | RendererLib.drawWelcomeMessages()
157 | ControllerActorSystem.startCheckControls()
158 | super.main(args)
159 | run()
160 | ControllerActorSystem.shutDownAndAwaitTermination()
161 | RendererLib.destroygl()
162 | scage_log.info(title + " was stopped")
163 | //System.exit(0) // need explicit exit for the app's utilizing NetServer/NetClient as they have actors
164 | }
165 | }
166 |
167 | abstract class ScreenAppDMT(
168 | title: String = property("app.name", "Scage App"),
169 | width: Int = property("screen.width", 800),
170 | height: Int = property("screen.height", 600)
171 | ) extends ScreenD(title) with Cli {
172 | val app_start_moment = System.currentTimeMillis()
173 |
174 | def msecsFromAppStart: Long = System.currentTimeMillis() - app_start_moment
175 |
176 | override def run(): Unit = {
177 | executePreinits()
178 | executeInits()
179 | is_running = true
180 | prepareRendering()
181 | scage_log.info(unit_name + ": run")
182 | while (is_running && Scage.isAppRunning) {
183 | checkControlsAndExecuteActions()
184 | performRendering()
185 | }
186 | RendererLibD.renderExitMessage()
187 | executeClears()
188 | executeDisposes()
189 | }
190 |
191 | override def main(args: Array[String]) {
192 | scage_log.info("starting main screen " + title + "...")
193 | //xInitThreads(scage_log)
194 | ControllerActorSystem.createControllerActor()
195 | ControllerActorSystem.initGLAndReleaseContext(width, height, title)
196 | Display.makeCurrent()
197 | RendererLib.drawWelcomeMessages()
198 | ControllerActorSystem.startCheckControls()
199 | super.main(args)
200 | run()
201 | ControllerActorSystem.shutDownAndAwaitTermination()
202 | RendererLibD.destroygl()
203 | scage_log.info(title + " was stopped")
204 | System.exit(0) // need explicit exit for the app's utilizing NetServer/NetClient as they have actors
205 | }
206 | }
207 |
208 |
209 | class ScageScreen(unit_name: String = "Scage Screen") extends Screen(unit_name) with SingleController
210 |
211 | class ScageScreenD(unit_name: String = "Scage Screen") extends ScreenD(unit_name) with SingleController
212 |
213 | class ScageScreenMT(unit_name: String = "Scage Screen") extends Screen(unit_name) with ActorSingleController
214 |
215 | class ScageScreenDMT(unit_name: String = "Scage Screen") extends ScreenD(unit_name) with ActorSingleController
216 |
217 | class ScageScreenApp(title: String = property("app.name", "Scage App"),
218 | width: Int = property("screen.width", 800),
219 | height: Int = property("screen.height", 600)) extends ScreenApp(title, width, height) with SingleController
220 |
221 | class ScageScreenAppD(title: String = property("app.name", "Scage App"),
222 | width: Int = property("screen.width", 800),
223 | height: Int = property("screen.height", 600)) extends ScreenAppD(title, width, height) with SingleController
224 |
225 | class ScageScreenAppMT(title: String = property("app.name", "Scage App"),
226 | width: Int = property("screen.width", 800),
227 | height: Int = property("screen.height", 600)) extends ScreenAppMT(title, width, height) with ActorSingleController
228 |
229 | class ScageScreenAppDMT(title: String = property("app.name", "Scage App"),
230 | width: Int = property("screen.width", 800),
231 | height: Int = property("screen.height", 600)) extends ScreenAppDMT(title, width, height) with ActorSingleController
232 |
233 | abstract class ScageApplet extends Applet {
234 | def screen: ScageScreenApp
235 |
236 | /** The Canvas where the LWJGL Display is added */
237 | private var display_parent: Canvas = _
238 |
239 | /** Thread which runs the main game loop */
240 | private var gameThread: Thread = _
241 |
242 | /**
243 | * Once the Canvas is created its add notify method will call this method to
244 | * start the LWJGL Display and game loop in another thread.
245 | */
246 | private def startScage(): Unit = {
247 | gameThread = new Thread {
248 | override def run(): Unit = {
249 | Display.setParent(display_parent)
250 | screen.main(Array[String]())
251 | }
252 | }
253 | gameThread.start()
254 | }
255 |
256 | /**
257 | * Tell game loop to stop running, after which the LWJGL Display will be destoryed.
258 | * The main thread will wait for the Display.destroy() to complete
259 | */
260 | private def stopScage(): Unit = {
261 | screen.stop()
262 | try {
263 | gameThread.join()
264 | } catch {
265 | case e: InterruptedException =>
266 | e.printStackTrace()
267 | }
268 | }
269 |
270 | /**
271 | * Applet Destroy method will remove the canvas, before canvas is destroyed it will notify
272 | * stopLWJGL() to stop main game loop and to destroy the Display
273 | */
274 | override def destroy(): Unit = {
275 | remove(display_parent)
276 | super.destroy()
277 | }
278 |
279 | /**
280 | * initialise applet by adding a canvas to it, this canvas will start the LWJGL Display and game loop
281 | * in another thread. It will also stop the game loop and destroy the display on canvas removal when
282 | * applet is destroyed.
283 | */
284 | override def init(): Unit = {
285 | setLayout(new BorderLayout())
286 | try {
287 | display_parent = new Canvas() {
288 | override def addNotify(): Unit = {
289 | super.addNotify()
290 | startScage()
291 | }
292 |
293 | override def removeNotify(): Unit = {
294 | stopScage()
295 | super.removeNotify()
296 | }
297 | }
298 | display_parent.setSize(getWidth, getHeight)
299 | add(display_parent)
300 | display_parent.setFocusable(true)
301 | display_parent.requestFocus()
302 | display_parent.setIgnoreRepaint(true)
303 | setVisible(true)
304 | } catch {
305 | case e: Exception =>
306 | System.err.println(e)
307 | throw new RuntimeException("Unable to create display")
308 | }
309 | }
310 | }
311 |
312 | abstract class ScageAppletD extends Applet {
313 | def screen: ScageScreenAppD
314 |
315 | /** The Canvas where the LWJGL Display is added */
316 | private var display_parent: Canvas = _
317 |
318 | /** Thread which runs the main game loop */
319 | private var gameThread: Thread = _
320 |
321 | /**
322 | * Once the Canvas is created its add notify method will call this method to
323 | * start the LWJGL Display and game loop in another thread.
324 | */
325 | private def startScage(): Unit = {
326 | gameThread = new Thread {
327 | override def run(): Unit = {
328 | Display.setParent(display_parent)
329 | screen.main(Array[String]())
330 | }
331 | }
332 | gameThread.start()
333 | }
334 |
335 | /**
336 | * Tell game loop to stop running, after which the LWJGL Display will be destoryed.
337 | * The main thread will wait for the Display.destroy() to complete
338 | */
339 | private def stopScage(): Unit = {
340 | screen.stop()
341 | try {
342 | gameThread.join()
343 | } catch {
344 | case e: InterruptedException =>
345 | e.printStackTrace()
346 | }
347 | }
348 |
349 | /**
350 | * Applet Destroy method will remove the canvas, before canvas is destroyed it will notify
351 | * stopLWJGL() to stop main game loop and to destroy the Display
352 | */
353 | override def destroy(): Unit = {
354 | remove(display_parent)
355 | super.destroy()
356 | }
357 |
358 | /**
359 | * initialise applet by adding a canvas to it, this canvas will start the LWJGL Display and game loop
360 | * in another thread. It will also stop the game loop and destroy the display on canvas removal when
361 | * applet is destroyed.
362 | */
363 | override def init(): Unit = {
364 | setLayout(new BorderLayout())
365 | try {
366 | display_parent = new Canvas() {
367 | override def addNotify(): Unit = {
368 | super.addNotify()
369 | startScage()
370 | }
371 |
372 | override def removeNotify(): Unit = {
373 | stopScage()
374 | super.removeNotify()
375 | }
376 | }
377 | display_parent.setSize(getWidth, getHeight)
378 | add(display_parent)
379 | display_parent.setFocusable(true)
380 | display_parent.requestFocus()
381 | display_parent.setIgnoreRepaint(true)
382 | setVisible(true)
383 | } catch {
384 | case e: Exception =>
385 | System.err.println(e)
386 | throw new RuntimeException("Unable to create display")
387 | }
388 | }
389 | }
390 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/dunnololda/scage/ScageLib.scala:
--------------------------------------------------------------------------------
1 | package com.github.dunnololda.scage
2 |
3 | import handlers.RendererLib
4 | import support._
5 | import support.messages._
6 | import com.github.dunnololda.cli.AppProperties
7 | import net.phys2d.math.ROVector2f
8 | import scala.language.implicitConversions
9 |
10 | object ScageLib extends ScageMessageTrait with ScageXMLTrait with RendererLib with LWJGLKeyboard with ScageColorTrait with ScageIdTrait with EventsTrait {
11 | def property[A: Manifest](key: String, default: => A): A = AppProperties.property(key, default)
12 |
13 | def optProperty[A: Manifest](key: String): Option[A] = AppProperties.optProperty(key)
14 |
15 | def reqProperty[A: Manifest](key: String): A = AppProperties.reqProperty(key)
16 |
17 | def property[A: Manifest](key: String, default: => A, condition: (A => (Boolean, String))): A = AppProperties.property(key, default, condition)
18 |
19 | def appName = AppProperties.appName
20 |
21 | def appVersion = AppProperties.appVersion
22 |
23 | def print(message: Any, x: Float, y: Float, size: Float, color: ScageColor, align: String) {
24 | ScageMessage.print(message, x, y, size, color, align)
25 | }
26 |
27 | def messageBounds(message: Any, size: Float = max_font_size): Vec = ScageMessage.messageBounds(message, size)
28 |
29 | def areaForMessage(message: Any, coord: Vec, size: Float = max_font_size, align: String = "center"): Seq[Vec] = ScageMessage.areaForMessage(message, coord, size, align)
30 |
31 | def addGlyphs(from: Int, to: Int) {
32 | ScageMessage.addGlyphs(from, to)
33 | }
34 |
35 | def addGlyphs(text: String) {
36 | ScageMessage.addGlyphs(text)
37 | }
38 |
39 | def lang = ScageXML.lang
40 |
41 | def lang_=(new_lang: String) {
42 | ScageXML.lang = new_lang
43 | }
44 |
45 | def messagesBase = ScageXML.messagesBase
46 |
47 | def messagesBase_=(new_base: String) {
48 | ScageXML.messagesBase = new_base
49 | }
50 |
51 | def interfacesBase = ScageXML.interfacesBase
52 |
53 | def interfacesBase_=(new_base: String) {
54 | ScageXML.interfacesBase = new_base
55 | }
56 |
57 | def messagesFile: String = ScageXML.messagesFile
58 |
59 | def interfacesFile: String = ScageXML.interfacesFile
60 |
61 | def xml(message_id: String, parameters: Any*): String = ScageXML.xml(message_id, parameters: _*)
62 |
63 | def xmlOrDefault(message_id: String, parameters: Any*): String = ScageXML.xmlOrDefault(message_id, parameters: _*)
64 |
65 | def xmlInterface(interface_id: String, parameters: Any*): Array[MessageData] = ScageXML.xmlInterface(interface_id, parameters: _*)
66 |
67 | def xmlInterfaceStrings(interface_id: String, parameters: Any*): Array[String] = ScageXML.xmlInterfaceStrings(interface_id, parameters: _*)
68 |
69 | def onEventWithArguments(event_name: String)(event_action: PartialFunction[Any, Unit]): (String, Int) = Events.onEventWithArguments(event_name)(event_action)
70 |
71 | def onEvent(event_name: String)(event_action: => Unit): (String, Int) = Events.onEvent(event_name)(event_action)
72 |
73 | def callEvent(event_name: String, arg: Any) {
74 | Events.callEvent(event_name, arg)
75 | }
76 |
77 | def callEvent(event_name: String) {
78 | Events.callEvent(event_name)
79 | }
80 |
81 | def delEvents(event_ids: (String, Int)*) {
82 | Events.delEvents(event_ids: _*)
83 | }
84 |
85 | def isAppRunning: Boolean = Scage.isAppRunning
86 |
87 | def stopApp() {
88 | Scage.stopApp()
89 | }
90 |
91 | def nextId = ScageId.nextId
92 |
93 | // implicits
94 |
95 | implicit class Int2Vecrich(k: Int) {
96 | def *(v: Vec) = v * k
97 |
98 | def /(v: Vec) = v / k
99 |
100 | def */(v: Vec) = Vec(-v.y * k, v.x * k)
101 |
102 | def toRad: Float = k / 180f * math.Pi.toFloat
103 |
104 | def toDeg: Float = k / math.Pi.toFloat * 180f
105 | }
106 |
107 | implicit class Long2Vecrich(k: Long) {
108 | def *(v: Vec) = v * k
109 |
110 | def /(v: Vec) = v / k
111 |
112 | def */(v: Vec) = Vec(-v.y * k, v.x * k)
113 |
114 | def toRad: Float = k / 180f * math.Pi.toFloat
115 |
116 | def toDeg: Float = k / math.Pi.toFloat * 180f
117 | }
118 |
119 | implicit class Float2Vecrich(k: Float) {
120 | def *(v: Vec) = v * k
121 |
122 | def /(v: Vec) = v / k
123 |
124 | def toRad: Float = k / 180f * math.Pi.toFloat
125 |
126 | def toDeg: Float = k / math.Pi.toFloat * 180f
127 | }
128 |
129 | implicit class Double2Vecrich(k: Double) {
130 | def *(v: Vec) = v * k
131 |
132 | def /(v: Vec) = v / k
133 |
134 | def */(v: Vec) = Vec(-v.y * k, v.x * k)
135 |
136 | def toRad: Double = k / 180.0 * math.Pi
137 |
138 | def toDeg: Double = k / math.Pi * 180.0
139 | }
140 |
141 | implicit class Int2DVecrich(k: Int) {
142 | def *(v: DVec) = v * k
143 |
144 | def /(v: DVec) = v / k
145 |
146 | def */(v: DVec) = DVec(-v.y * k, v.x * k)
147 | }
148 |
149 | implicit class Long2DVecrich(k: Long) {
150 | def *(v: DVec) = v * k
151 |
152 | def /(v: DVec) = v / k
153 |
154 | def */(v: DVec) = DVec(-v.y * k, v.x * k)
155 | }
156 |
157 | implicit class Float2DVecrich(k: Float) {
158 | def *(v: DVec) = v * k
159 |
160 | def /(v: DVec) = v / k
161 |
162 | def */(v: DVec) = DVec(-v.y * k, v.x * k)
163 | }
164 |
165 | implicit class Double2DVecrich(k: Double) {
166 | def *(v: DVec) = v * k
167 |
168 | def /(v: DVec) = v / k
169 |
170 | def */(v: DVec) = DVec(-v.y * k, v.x * k)
171 | }
172 |
173 | implicit class Phys2dVec2Vec(pv: ROVector2f) {
174 | def toVec: Vec = Vec(pv.getX, pv.getY)
175 |
176 | def toDVec: DVec = DVec(pv.getX, pv.getY)
177 | }
178 |
179 | implicit def Vec2dvec(v: Vec): DVec = v.toDVec
180 |
181 | implicit def DVec2Vec(dv: Vec): Vec = dv.toVec
182 |
183 | implicit val NumericVec = new Numeric[Vec] {
184 | def plus(x: ScageLib.Vec, y: ScageLib.Vec): ScageLib.Vec = x + y
185 |
186 | def minus(x: ScageLib.Vec, y: ScageLib.Vec): ScageLib.Vec = x - y
187 |
188 | def times(x: ScageLib.Vec, y: ScageLib.Vec): ScageLib.Vec = ???
189 |
190 | def negate(x: ScageLib.Vec): ScageLib.Vec = x * (-1)
191 |
192 | def fromInt(x: Int): ScageLib.Vec = Vec(x, x)
193 |
194 | def toInt(x: ScageLib.Vec): Int = x.ix
195 |
196 | def toLong(x: ScageLib.Vec): Long = x.ix
197 |
198 | def toFloat(x: ScageLib.Vec): Float = x.x
199 |
200 | def toDouble(x: ScageLib.Vec): Double = x.x
201 |
202 | def compare(x: ScageLib.Vec, y: ScageLib.Vec): Int = ???
203 | }
204 |
205 | implicit val NumericDVec = new Numeric[DVec] {
206 | def plus(x: ScageLib.DVec, y: ScageLib.DVec): ScageLib.DVec = x + y
207 |
208 | def minus(x: ScageLib.DVec, y: ScageLib.DVec): ScageLib.DVec = x - y
209 |
210 | def times(x: ScageLib.DVec, y: ScageLib.DVec): ScageLib.DVec = ???
211 |
212 | def negate(x: ScageLib.DVec): ScageLib.DVec = x * (-1)
213 |
214 | def fromInt(x: Int): ScageLib.DVec = DVec(x, x)
215 |
216 | def toInt(x: ScageLib.DVec): Int = x.ix
217 |
218 | def toLong(x: ScageLib.DVec): Long = x.ix
219 |
220 | def toFloat(x: ScageLib.DVec): Float = x.x.toFloat
221 |
222 | def toDouble(x: ScageLib.DVec): Double = x.x
223 |
224 | def compare(x: ScageLib.DVec, y: ScageLib.DVec): Int = ???
225 | }
226 |
227 | implicit class MinOption[A](s: Seq[A]) {
228 | def minOption(implicit o: Ordering[A]): Option[A] = if (s.isEmpty) None else Some(s.min(o))
229 |
230 | def minOptionBy[B](f: A => B)(implicit o: Ordering[B]): Option[A] = if (s.isEmpty) None else Some(s.minBy(f)(o))
231 | }
232 |
233 | // support
234 |
235 | def msecs = System.currentTimeMillis()
236 |
237 | def msecsFrom(moment: Long) = System.currentTimeMillis() - moment
238 |
239 | // Vec/DVec helper methods
240 |
241 | def areLinesIntersect(p: Vec, p2: Vec, q: Vec, q2: Vec): Boolean = {
242 | // http://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect
243 | val r = p2 - p
244 | val s = q2 - q
245 | val r_cross_s = r */ s
246 | if (r_cross_s == 0) {
247 | val q_min_p_cross_r = (q - p) */ s
248 | if (q_min_p_cross_r == 0) {
249 | // два отрезка коллинеарны
250 | val t0 = (q - p) * r / (r * r)
251 | val t1 = t0 + (s * r) / (r * r)
252 | (t0 <= 0 && t1 >= 0) || (t0 >= 0 && t0 <= 1)
253 | } else {
254 | // два отрезка параллельны, не пересекаются
255 | false
256 | }
257 | } else {
258 | val t = ((q - p) */ s) / (r */ s)
259 | val u = ((q - p) */ r) / (r */ s)
260 | -1E-14 <= t && t <= 1.00000000000001 && -1E-14 <= u && u <= 1.00000000000001
261 | }
262 |
263 | /*val common = (a2.x - a1.x) * (b2.y - b1.y) - (a2.y - a1.y) * (b2.x - b1.x)
264 | common != 0 && {
265 | val rH = (a1.y - b1.y) * (b2.x - b1.x) - (a1.x - b1.x) * (b2.y - b1.y)
266 | val sH = (a1.y - b1.y) * (a2.x - a1.x) - (a1.x - b1.x) * (a2.y - a1.y)
267 |
268 | val r = rH / common
269 | val s = sH / common
270 |
271 | r >= 0 && r <= 1 && s >= 0 && s <= 1
272 | }*/
273 | }
274 |
275 | def areLinesIntersect(p: DVec, p2: DVec, q: DVec, q2: DVec): Boolean = {
276 | // http://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect
277 | val r = p2 - p
278 | val s = q2 - q
279 | val r_cross_s = r */ s
280 | if (r_cross_s == 0) {
281 | val q_min_p_cross_r = (q - p) */ s
282 | if (q_min_p_cross_r == 0) {
283 | // два отрезка коллинеарны
284 | val t0 = (q - p) * r / (r * r)
285 | val t1 = t0 + (s * r) / (r * r)
286 | (t0 <= 0 && t1 >= 0) || (t0 >= 0 && t0 <= 1)
287 | } else {
288 | // два отрезка параллельны, не пересекаются
289 | false
290 | }
291 | } else {
292 | val t = ((q - p) */ s) / (r */ s)
293 | val u = ((q - p) */ r) / (r */ s)
294 | -1E-14 <= t && t <= 1.00000000000001 && -1E-14 <= u && u <= 1.00000000000001
295 | }
296 |
297 | /*val common = (a2.x - a1.x) * (b2.y - b1.y) - (a2.y - a1.y) * (b2.x - b1.x)
298 | common != 0 && {
299 | val rH = (a1.y - b1.y) * (b2.x - b1.x) - (a1.x - b1.x) * (b2.y - b1.y)
300 | val sH = (a1.y - b1.y) * (a2.x - a1.x) - (a1.x - b1.x) * (a2.y - a1.y)
301 |
302 | val r = rH / common
303 | val s = sH / common
304 |
305 | r >= 0 && r <= 1 && s >= 0 && s <= 1
306 | }*/
307 | }
308 |
309 | def coordOnRect(coord: Vec, leftup: Vec, width: Float, height: Float): Boolean = {
310 | coord.x >= leftup.x && coord.x < leftup.x + width &&
311 | coord.y >= leftup.y - height && coord.y < leftup.y
312 | }
313 |
314 | def coordOnRectCentered(coord: Vec, center: Vec, width: Float, height: Float): Boolean = {
315 | coord.x >= (center.x - width / 2) && coord.x < (center.x + width / 2) &&
316 | coord.y >= (center.y - height / 2) && coord.y < (center.y + height / 2)
317 | }
318 |
319 | def coordOnArea(coord: Vec, area: Seq[Vec]): Boolean = {
320 | if (area.length < 2) false
321 | else {
322 | val (leftup, width, height) = {
323 | val (min_x, max_x, min_y, max_y) = area.foldLeft((Float.MaxValue, 0f, Float.MaxValue, 0f)) {
324 | case ((minx, maxx, miny, maxy), Vec(x, y)) =>
325 | (if (x < minx) x else minx,
326 | if (x > maxx) x else maxx,
327 | if (y < miny) y else miny,
328 | if (y > maxy) y else maxy)
329 | }
330 | val l = Vec(min_x, max_y)
331 | val w = max_x - min_x
332 | val h = max_y - min_y
333 | (l, w, h)
334 | }
335 | if (!coordOnRect(coord, leftup, width, height)) false
336 | else {
337 | val a1 = coord
338 | val a2 = Vec(Integer.MAX_VALUE, coord.y)
339 | val intersections = (Seq(area.last) ++ area.init).zip(area).foldLeft(0) {
340 | case (result, (b1, b2)) => if (areLinesIntersect(a1, a2, b1, b2)) result + 1 else result
341 | }
342 | intersections % 2 != 0
343 | }
344 | }
345 | }
346 |
347 | def coordOnRect(coord: DVec, leftup: DVec, width: Double, height: Double): Boolean = {
348 | coord.x >= leftup.x && coord.x < leftup.x + width &&
349 | coord.y >= leftup.y - height && coord.y < leftup.y
350 | }
351 |
352 | def coordOnRectCentered(coord: DVec, center: DVec, width: Float, height: Float): Boolean = {
353 | coord.x >= (center.x - width / 2) && coord.x < (center.x + width / 2) &&
354 | coord.y >= (center.y - height / 2) && coord.y < (center.y + height / 2)
355 | }
356 |
357 | def coordOnArea(coord: DVec, area: Seq[DVec]): Boolean = {
358 | if (area.length < 2) false
359 | else {
360 | val (leftup, width, height) = {
361 | val (min_x, max_x, min_y, max_y) = area.foldLeft((Double.MaxValue, 0.0, Double.MaxValue, 0.0)) {
362 | case ((minx, maxx, miny, maxy), DVec(x, y)) =>
363 | (if (x < minx) x else minx,
364 | if (x > maxx) x else maxx,
365 | if (y < miny) y else miny,
366 | if (y > maxy) y else maxy)
367 | }
368 | val l = DVec(min_x, max_y)
369 | val w = max_x - min_x
370 | val h = max_y - min_y
371 | (l, w, h)
372 | }
373 | if (!coordOnRect(coord, leftup, width, height)) false
374 | else {
375 | val a1 = coord
376 | val a2 = DVec(Integer.MAX_VALUE, coord.y)
377 | val intersections = (Seq(area.last) ++ area.init).zip(area).foldLeft(0) {
378 | case (result, (b1, b2)) => if (areLinesIntersect(a1, a2, b1, b2)) result + 1 else result
379 | }
380 | intersections % 2 != 0
381 | }
382 | }
383 | }
384 |
385 | // types
386 | type ScageApp = com.github.dunnololda.scage.ScageApp
387 | type Scage = com.github.dunnololda.scage.Scage
388 | type ScageScreenApp = com.github.dunnololda.scage.ScageScreenApp
389 | type ScageScreen = com.github.dunnololda.scage.ScageScreen
390 | type ScreenApp = com.github.dunnololda.scage.ScreenApp
391 | type Screen = com.github.dunnololda.scage.Screen
392 | type ScreenAppMT = com.github.dunnololda.scage.ScreenAppMT
393 | type ScageScreenMT = com.github.dunnololda.scage.ScageScreenMT
394 | type ScageScreenAppMT = com.github.dunnololda.scage.ScageScreenAppMT
395 | type ScageApplet = com.github.dunnololda.scage.ScageApplet
396 | type Vec = com.github.dunnololda.scage.support.Vec
397 | type DVec = com.github.dunnololda.scage.support.DVec
398 | type SingleController = com.github.dunnololda.scage.handlers.controller2.SingleController
399 | type MultiController = com.github.dunnololda.scage.handlers.controller2.MultiController
400 | type ActorSingleController = com.github.dunnololda.scage.handlers.controller3.ActorSingleController
401 | type ScageColor = com.github.dunnololda.scage.support.ScageColor
402 | type DefaultTrace = com.github.dunnololda.scage.support.tracer3.DefaultTrace
403 | type State = com.github.dunnololda.state.State
404 | type Trace = com.github.dunnololda.scage.support.tracer3.Trace
405 | type TraceTrait = com.github.dunnololda.scage.support.tracer3.TraceTrait
406 | type ScageMessage = com.github.dunnololda.scage.support.messages.ScageMessage
407 | type PathFinder = com.github.dunnololda.scage.support.PathFinder
408 |
409 | type CoordTracer[A <: com.github.dunnololda.scage.support.tracer3.TraceTrait] = com.github.dunnololda.scage.support.tracer3.CoordTracer[A]
410 | type ScageTracer[A <: com.github.dunnololda.scage.support.tracer3.TraceTrait] = com.github.dunnololda.scage.support.tracer3.ScageTracer[A]
411 |
412 | type ScagePhysics = com.github.dunnololda.scage.support.physics.ScagePhysics
413 |
414 | type Physical = com.github.dunnololda.scage.support.physics.Physical
415 | type StaticLine = com.github.dunnololda.scage.support.physics.objects.StaticLine
416 | type StaticPolygon = com.github.dunnololda.scage.support.physics.objects.StaticPolygon
417 | type StaticBox = com.github.dunnololda.scage.support.physics.objects.StaticBox
418 | type StaticBall = com.github.dunnololda.scage.support.physics.objects.StaticBall
419 | type DynaBox = com.github.dunnololda.scage.support.physics.objects.DynaBox
420 | type DynaBall = com.github.dunnololda.scage.support.physics.objects.DynaBall
421 |
422 | val Scage = com.github.dunnololda.scage.Scage
423 | val Vec = com.github.dunnololda.scage.support.Vec
424 | val DVec = com.github.dunnololda.scage.support.DVec
425 | val CoordTracer = com.github.dunnololda.scage.support.tracer3.CoordTracer
426 | val ScageTracer = com.github.dunnololda.scage.support.tracer3.ScageTracer
427 | val ScagePhysics = com.github.dunnololda.scage.support.physics.ScagePhysics
428 | val State = com.github.dunnololda.state.State
429 | val Trace = com.github.dunnololda.scage.support.tracer3.Trace
430 | val ScageColor = com.github.dunnololda.scage.support.ScageColor
431 | val ScageMessage = com.github.dunnololda.scage.support.messages.ScageMessage
432 | val max_font_size = ScageMessage.max_font_size
433 | val PathFinder = com.github.dunnololda.scage.support.PathFinder
434 | }
435 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/dunnololda/scage/handlers/controller3/ActorSingleController.scala:
--------------------------------------------------------------------------------
1 | package com.github.dunnololda.scage.handlers.controller3
2 |
3 | import akka.actor.{Actor, ActorSystem, Props}
4 | import akka.pattern.ask
5 | import akka.util.Timeout
6 | import com.github.dunnololda.mysimplelogger.MySimpleLogger
7 | import com.github.dunnololda.scage.Scage
8 | import com.github.dunnololda.scage.handlers.RendererLib
9 | import com.github.dunnololda.scage.handlers.controller2._
10 | import com.github.dunnololda.scage.support.Vec
11 | import com.typesafe.config.ConfigFactory
12 | import org.lwjgl.input.{Keyboard, Mouse}
13 | import org.lwjgl.opengl.Display
14 |
15 | import scala.collection.mutable
16 | import scala.concurrent.Await
17 | import scala.concurrent.ExecutionContext.Implicits.global
18 | import scala.concurrent.duration._
19 |
20 | object ControllerActorSystem {
21 | private val controller_system = ActorSystem("controller", ConfigFactory.load("controller-actor-system.conf").getConfig("controller-system"))
22 |
23 | private var controller_actor_created = false
24 |
25 | def createControllerActor(): Unit = {
26 | ControllerActorSystem.controller_system.actorOf(Props(new ControllerActor).withDispatcher("my-pinned-dispatcher"), "controller-actor")
27 | controller_actor_created = true
28 | }
29 |
30 | val controllerActorSelection = {
31 | controller_system.actorSelection("akka://controller/user/controller-actor")
32 | }
33 |
34 | def initGLAndReleaseContext(width: Int, height: Int, title: String) {
35 | Await.result(controllerActorSelection.?(InitGLAndReleaseContext(width, height, title))(Timeout(10000.millis)), 10000.millis)
36 | }
37 |
38 | def startCheckControls(): Unit = {
39 | controllerActorSelection ! StartCheckControls
40 | }
41 |
42 | def stopCheckControls(): Unit = {
43 | Await.result(controllerActorSelection.?(StopCheckControls)(Timeout(10000.millis)), 10000.millis)
44 | }
45 |
46 | def shutdownControllerActor(): Unit = {
47 | Await.result(controllerActorSelection.?(ShutdownControllerActor)(Timeout(10000.millis)), 10000.millis)
48 | }
49 |
50 | def shutDownAndAwaitTermination(): Unit = {
51 | //controllerActorSelection ! ShutdownControllerActor
52 | controller_system.terminate()
53 | Await.result(controller_system.whenTerminated, Duration.Inf)
54 | }
55 |
56 | def monitoredKeysList: List[Int] = {
57 | Await.result(controllerActorSelection.?(GetAllControlledKeys)(Timeout(10000.millis)).mapTo[List[Int]], 10000.millis)
58 | }
59 |
60 | def addKeys(keys: List[Int]): Unit = {
61 | controllerActorSelection ! AddKeys(keys)
62 | }
63 |
64 | def addKey(key_code: Int): Unit = {
65 | controllerActorSelection ! AddKey(key_code)
66 | }
67 |
68 | def removeKey(key_code: Int): Unit = {
69 | controllerActorSelection ! RemoveKey(key_code)
70 | }
71 |
72 | def getInputsHistoryAndReset: List[InputsHistoryMoment] = {
73 | Await.result(controllerActorSelection.?(GetInputsHistoryAndReset)(Timeout(10000.millis)).mapTo[List[InputsHistoryMoment]], 10000.millis)
74 | }
75 | }
76 |
77 | case object CheckControls
78 |
79 | case class AddKey(key_code: Int)
80 |
81 | case class AddKeys(key_codes: List[Int])
82 |
83 | case class RemoveKey(key_code: Int)
84 |
85 | case object GetInputsHistoryAndReset
86 |
87 | case class GetMouseButtonHistoryAndReset(key_code: Int)
88 |
89 | case object ShutdownControllerActor
90 |
91 | case object StartCheckControls
92 |
93 | case object StopCheckControls
94 |
95 | case class InitGLAndReleaseContext(width: Int, height: Int, title: String)
96 |
97 | case object GetAllControlledKeys
98 |
99 | case class InputsHistoryMoment(moment: Long, keys: Map[Int, Boolean], mouse_coord: Vec, is_mouse_moved: Boolean, dwheel: Int, mouse_buttons: Map[Int, Boolean])
100 |
101 | class ControllerActor extends Actor {
102 | private val log = MySimpleLogger(this.getClass.getName)
103 | private val inputs_history = mutable.ArrayBuffer[InputsHistoryMoment]()
104 | private val monitored_keys = mutable.HashSet[Int]()
105 |
106 | private var check_controls = false
107 |
108 | override def preStart() {
109 | log.info(s"starting actor ${self.path}")
110 | }
111 |
112 | override def postStop() {
113 | log.info(s"actor ${self.path} died!")
114 | }
115 |
116 | def receive = {
117 | case InitGLAndReleaseContext(width, height, title) =>
118 | RendererLib.initgl(width, height, title)
119 | Display.releaseContext()
120 | sender ! true
121 | case AddKey(key_code) =>
122 | monitored_keys += key_code
123 | case AddKeys(key_codes) =>
124 | monitored_keys ++= key_codes
125 | case RemoveKey(key_code) =>
126 | monitored_keys -= key_code
127 | case StartCheckControls =>
128 | check_controls = true
129 | self ! CheckControls
130 | case StopCheckControls =>
131 | check_controls = false
132 | sender ! true
133 | case CheckControls =>
134 | if (check_controls) {
135 | Display.processMessages()
136 | val mouse_coord = Vec(Mouse.getX, Mouse.getY)
137 | val is_mouse_moved = Mouse.getDX != 0 || Mouse.getDY != 0
138 | inputs_history += InputsHistoryMoment(System.currentTimeMillis(), monitored_keys.map { key_code =>
139 | (key_code, Keyboard.isKeyDown(key_code))
140 | }.toMap, mouse_coord, is_mouse_moved, Mouse.getDWheel, Map(0 -> Mouse.isButtonDown(0), 1 -> Mouse.isButtonDown(1)))
141 | context.system.scheduler.scheduleOnce(10.milliseconds) {
142 | self ! CheckControls
143 | }
144 | }
145 | case GetInputsHistoryAndReset =>
146 | val history = inputs_history.toList
147 | inputs_history.clear()
148 | sender ! history
149 | case ShutdownControllerActor =>
150 | sender ! true
151 | context.system.stop(self)
152 | case GetAllControlledKeys =>
153 | sender ! monitored_keys.toList
154 | }
155 | }
156 |
157 | import com.github.dunnololda.scage.handlers.controller3.ControllerActorSystem._
158 |
159 | trait ActorSingleController extends ScageController {
160 | private val keyboard_key_events = mutable.HashMap[Int, SingleKeyEvent]()
161 | // was_pressed, last_pressed_time, repeat_time, onKeyDown, onKeyUp
162 | private var anykey: () => Any = () => {}
163 | private val mouse_button_events = mutable.HashMap[Int, SingleMouseButtonEvent]()
164 | private var on_mouse_motion: Vec => Any = v => {}
165 | private val on_mouse_drag_motion = mutable.HashMap[Int, Vec => Any]()
166 | private var on_mouse_wheel_up: Vec => Any = v => {}
167 | private var on_mouse_wheel_down: Vec => Any = v => {}
168 |
169 | protected def mappedKeyboardKeys: scala.collection.Set[Int] = keyboard_key_events.keySet
170 |
171 | protected def mappedMouseButtons: scala.collection.Set[Int] = mouse_button_events.keySet
172 |
173 | def key(key_code: Int, repeat_time: => Long = 0, onKeyDown: => Any, onKeyUp: => Any = {}) = {
174 | keyboard_key_events(key_code) = SingleKeyEvent(key_code, () => repeat_time, () => if (!on_pause) onKeyDown, () => if (!on_pause) onKeyUp)
175 | addKey(key_code)
176 | control_deletion_operations.addOp(() => {
177 | keyboard_key_events -= key_code
178 | removeKey(key_code)
179 | }, 0)
180 | }
181 |
182 | def keyIgnorePause(key_code: Int, repeat_time: => Long = 0, onKeyDown: => Any, onKeyUp: => Any = {}) = {
183 | keyboard_key_events(key_code) = SingleKeyEvent(key_code, () => repeat_time, () => onKeyDown, () => onKeyUp)
184 | addKey(key_code)
185 | control_deletion_operations.addOp(() => {
186 | keyboard_key_events -= key_code
187 | removeKey(key_code)
188 | }, 0)
189 | }
190 |
191 | def keyOnPause(key_code: Int, repeat_time: => Long = 0, onKeyDown: => Any, onKeyUp: => Any = {}): Int = {
192 | keyboard_key_events(key_code) = SingleKeyEvent(key_code, () => repeat_time, () => if (on_pause) onKeyDown, () => if (on_pause) onKeyUp)
193 | addKey(key_code)
194 | control_deletion_operations.addOp(() => {
195 | keyboard_key_events -= key_code
196 | removeKey(key_code)
197 | }, 0)
198 | }
199 |
200 | def anykey(onKeyDown: => Any) = {
201 | anykey = () => if (!on_pause) onKeyDown
202 | control_deletion_operations.addOp(() => anykey = () => {}, 0)
203 | }
204 |
205 | def anykeyIgnorePause(onKeyDown: => Any) = {
206 | anykey = () => onKeyDown
207 | control_deletion_operations.addOp(() => anykey = () => {}, 0)
208 | }
209 |
210 | def anykeyOnPause(onKeyDown: => Any) = {
211 | anykey = () => if (on_pause) onKeyDown
212 | control_deletion_operations.addOp(() => anykey = () => {}, 0)
213 | }
214 |
215 | def mouseCoord = Vec(Mouse.getX, Mouse.getY)
216 |
217 | def isMouseMoved = Mouse.getDX != 0 || Mouse.getDY != 0
218 |
219 | private def mouseButton(button_code: Int, repeat_time: => Long = 0, onButtonDown: Vec => Any, onButtonUp: Vec => Any = Vec => {}) = {
220 | mouse_button_events(button_code) = SingleMouseButtonEvent(button_code, () => repeat_time, onButtonDown, onButtonUp)
221 | control_deletion_operations.addOp(() => mouse_button_events -= button_code, 0)
222 | }
223 |
224 | def leftMouse(repeat_time: => Long = 0, onBtnDown: Vec => Any, onBtnUp: Vec => Any = Vec => {}) = {
225 | mouseButton(0, repeat_time, mouse_coord => if (!on_pause) onBtnDown(mouse_coord), mouse_coord => if (!on_pause) onBtnUp(mouse_coord))
226 | }
227 |
228 | def leftMouseIgnorePause(repeat_time: => Long = 0, onBtnDown: Vec => Any, onBtnUp: Vec => Any = Vec => {}) = {
229 | mouseButton(0, repeat_time, onBtnDown, onBtnUp)
230 | }
231 |
232 | def leftMouseOnPause(repeat_time: => Long = 0, onBtnDown: Vec => Any, onBtnUp: Vec => Any = Vec => {}) = {
233 | mouseButton(0, repeat_time, mouse_coord => if (on_pause) onBtnDown(mouse_coord), mouse_coord => if (on_pause) onBtnUp(mouse_coord))
234 | }
235 |
236 | def rightMouse(repeat_time: => Long = 0, onBtnDown: Vec => Any, onBtnUp: Vec => Any = Vec => {}) = {
237 | mouseButton(1, repeat_time, mouse_coord => if (!on_pause) onBtnDown(mouse_coord), mouse_coord => if (!on_pause) onBtnUp(mouse_coord))
238 | }
239 |
240 | def rightMouseIgnorePause(repeat_time: => Long = 0, onBtnDown: Vec => Any, onBtnUp: Vec => Any = Vec => {}) = {
241 | mouseButton(1, repeat_time, onBtnDown, onBtnUp)
242 | }
243 |
244 | def rightMouseOnPause(repeat_time: => Long = 0, onBtnDown: Vec => Any, onBtnUp: Vec => Any = Vec => {}) = {
245 | mouseButton(1, repeat_time, mouse_coord => if (on_pause) onBtnDown(mouse_coord), mouse_coord => if (on_pause) onBtnUp(mouse_coord))
246 | }
247 |
248 | def mouseMotion(onMotion: Vec => Any) = {
249 | on_mouse_motion = mouse_coord => if (!on_pause) onMotion(mouse_coord)
250 | control_deletion_operations.addOp(() => on_mouse_motion = v => {}, 0)
251 | }
252 |
253 | def mouseMotionIgnorePause(onMotion: Vec => Any) = {
254 | on_mouse_motion = onMotion
255 | control_deletion_operations.addOp(() => on_mouse_motion = v => {}, 0)
256 | }
257 |
258 | def mouseMotionOnPause(onMotion: Vec => Any) = {
259 | on_mouse_motion = mouse_coord => if (on_pause) onMotion(mouse_coord)
260 | control_deletion_operations.addOp(() => on_mouse_motion = v => {}, 0)
261 | }
262 |
263 | private def mouseDrag(button_code: Int, onDrag: Vec => Any) = {
264 | on_mouse_drag_motion(button_code) = onDrag
265 | control_deletion_operations.addOp(() => on_mouse_drag_motion -= button_code, 0)
266 | }
267 |
268 | def leftMouseDrag(onDrag: Vec => Any) = {
269 | mouseDrag(0, mouse_coord => if (!on_pause) onDrag(mouse_coord))
270 | }
271 |
272 | def leftMouseDragIgnorePause(onDrag: Vec => Any) = {
273 | mouseDrag(0, onDrag)
274 | }
275 |
276 | def leftMouseDragOnPause(onDrag: Vec => Any) = {
277 | mouseDrag(0, mouse_coord => if (on_pause) onDrag(mouse_coord))
278 | }
279 |
280 | def rightMouseDrag(onDrag: Vec => Any) = {
281 | mouseDrag(1, mouse_coord => if (!on_pause) onDrag(mouse_coord))
282 | }
283 |
284 | def rightMouseDragIgnorePause(onDrag: Vec => Any) = {
285 | mouseDrag(1, onDrag)
286 | }
287 |
288 | def rightMouseDragOnPause(onDrag: Vec => Any) = {
289 | mouseDrag(1, mouse_coord => if (on_pause) onDrag(mouse_coord))
290 | }
291 |
292 | def mouseWheelUp(onWheelUp: Vec => Any) = {
293 | on_mouse_wheel_up = mouse_coord => if (!on_pause) onWheelUp(mouse_coord)
294 | control_deletion_operations.addOp(() => on_mouse_wheel_up = v => {}, 0)
295 | }
296 |
297 | def mouseWheelUpIgnorePause(onWheelUp: Vec => Any) = {
298 | on_mouse_wheel_up = onWheelUp
299 | control_deletion_operations.addOp(() => on_mouse_wheel_up = v => {}, 0)
300 | }
301 |
302 | def mouseWheelUpOnPause(onWheelUp: Vec => Any) = {
303 | on_mouse_wheel_up = mouse_coord => if (on_pause) onWheelUp(mouse_coord)
304 | control_deletion_operations.addOp(() => on_mouse_wheel_up = v => {}, 0)
305 | }
306 |
307 | def mouseWheelDown(onWheelDown: Vec => Any) = {
308 | on_mouse_wheel_down = mouse_coord => if (!on_pause) onWheelDown(mouse_coord)
309 | control_deletion_operations.addOp(() => on_mouse_wheel_down = v => {}, 0)
310 | }
311 |
312 | def mouseWheelDownIgnorePause(onWheelDown: Vec => Any) = {
313 | on_mouse_wheel_down = onWheelDown
314 | control_deletion_operations.addOp(() => on_mouse_wheel_down = v => {}, 0)
315 | }
316 |
317 | def mouseWheelDownOnPause(onWheelDown: Vec => Any) = {
318 | on_mouse_wheel_down = mouse_coord => if (on_pause) onWheelDown(mouse_coord)
319 | control_deletion_operations.addOp(() => on_mouse_wheel_down = v => {}, 0)
320 | }
321 |
322 | //private val log = MySimpleLogger(this.getClass.getName)
323 |
324 | def checkControls() {
325 | val inputs_history = ControllerActorSystem.getInputsHistoryAndReset
326 | for {
327 | /*(*/ InputsHistoryMoment(moment, keys, mouse_coord, is_mouse_moved, dwheel, mouse_buttons) /*, idx)*/ <- inputs_history /*.zipWithIndex*/
328 | if is_running && Scage.isAppRunning
329 | } {
330 | var any_key_pressed = false
331 |
332 | for {
333 | (key, key_data) <- keyboard_key_events
334 | SingleKeyEvent(_, repeat_time_func, onKeyDown, onKeyUp) = key_data
335 | key_press@KeyPress(key_code, was_pressed, pressed_start_time, last_pressed_time) <- innerKeyPress(key)
336 | if moment > maxLastPressedTime
337 | is_key_pressed <- keys.get(key)
338 | } {
339 | if (is_key_pressed) {
340 | any_key_pressed = true
341 | val repeat_time = repeat_time_func()
342 | val is_repeatable = repeat_time > 0
343 | if (!key_press.was_pressed || (is_repeatable && moment - key_press.lastPressedTime > repeat_time)) {
344 | //log.info(s"$unit_name $key onKeyDown: $idx ${inputs_history.map(_.keys(key))} $key_press")
345 | if (!key_press.was_pressed) key_press.pressed_start_time = moment
346 | key_press.was_pressed = true
347 | key_press.updateLastPressedTime(moment)
348 | onKeyDown()
349 | }
350 | } else if (key_press.was_pressed) {
351 | key_press.was_pressed = false
352 | //log.info(s"$unit_name $key onKeyUp: $idx ${inputs_history.map(_.keys(key))} $key_press")
353 | onKeyUp()
354 | }
355 | }
356 |
357 | if (any_key_pressed) anykey()
358 | if (is_mouse_moved) on_mouse_motion(mouse_coord)
359 |
360 | for {
361 | (button, button_data) <- mouse_button_events
362 | SingleMouseButtonEvent(_, repeat_time_func, onButtonDown, onButtonUp) = button_data
363 | mouse_button_press@MouseButtonPress(_, was_pressed, _, last_pressed_time) <- innerMouseButtonPress(button)
364 | if moment > maxLastPressedTime
365 | is_button_pressed <- mouse_buttons.get(button)
366 | } {
367 | if (is_button_pressed) {
368 | val repeat_time = repeat_time_func()
369 | val is_repeatable = repeat_time > 0
370 | if (!was_pressed || (is_repeatable && moment - last_pressed_time > repeat_time)) {
371 | //log.info(s"$unit_name leftMouse onBtnDown: $idx ${inputs_history.map(_.mouse_buttons(0))} $mouse_button_press")
372 | if (!mouse_button_press.was_pressed) mouse_button_press.pressed_start_time = moment
373 | mouse_button_press.was_pressed = true
374 | mouse_button_press.updateLastPressedTime(moment)
375 | onButtonDown(mouse_coord)
376 | }
377 | } else if (was_pressed) {
378 | mouse_button_press.was_pressed = false
379 | //log.info(s"$unit_name leftMouse onBtnUp: $idx ${inputs_history.map(_.mouse_buttons(0))} $mouse_button_press")
380 | onButtonUp(mouse_coord)
381 | }
382 | }
383 |
384 | if (is_mouse_moved) {
385 | for {
386 | (button, onDragMotion) <- on_mouse_drag_motion
387 | if mouse_buttons.getOrElse(button, false)
388 | } onDragMotion(mouse_coord)
389 | }
390 |
391 | dwheel match {
392 | case x if x > 0 => on_mouse_wheel_up(mouse_coord)
393 | case x if x < 0 => on_mouse_wheel_down(mouse_coord)
394 | case _ =>
395 | }
396 | }
397 | }
398 | }
399 |
--------------------------------------------------------------------------------