Compare commits

...

43 Commits

Author SHA1 Message Date
Ondřej Hruška 4faabc203d improved readme 10 years ago
Ondřej Hruška cf096bcce5 Reflecting engine changes, some init task improvements 10 years ago
Ondřej Hruška e368877b6e refactoring & tmp workaround to allow close. 10 years ago
Ondřej Hruška 60e76ecfcc Adapted to use new gamecore. UGLY. to be done. Also no sounds. 10 years ago
Ondřej Hruška f0401d0184 Changes reflecting refactoring of related projects 10 years ago
Ondřej Hruška 137dbc21da cleanup. 10 years ago
Ondřej Hruška 84789ccf47 Fixed what could be fixed easily 10 years ago
Ondřej Hruška 23fe35db4a Removed LWJGL GameCore Backend and LWJGL from project 10 years ago
Ondřej Hruška da0737a4c6 Removed gamecore, added GameCore project dependency 10 years ago
Ondřej Hruška dda4aa171e Added a warning to readme 10 years ago
Ondřej Hruška ae57315df9 Some more cleaning of modules and App shutdown method. 10 years ago
Ondřej Hruška 3987b234bb Beautiful keys abstraction (decoupled from lwjgl key constants) 10 years ago
Ondřej Hruška a9b38b9417 Base of InputModule refactoring 10 years ago
Ondřej Hruška a4108f2caf cleanup and refactoring, package reorganization 10 years ago
Ondřej Hruška 50cbc9479b reorganization 10 years ago
Ondřej Hruška cafcf78cd2 Reflecting changes in Utils 10 years ago
Ondřej Hruška 87d0c9d500 some shifting around and refactoring. 10 years ago
Ondřej Hruška c5fa77d395 some javadoc fixes 10 years ago
Ondřej Hruška d3f392d689 audio module 10 years ago
Ondřej Hruška 3c0763a4c8 Unfinished huge refactoring towards backend modules system. 10 years ago
Ondřej Hruška eee9ed14dc Some refactoring 10 years ago
Ondřej Hruška ec64ad21fc More refactoring towards backend system 10 years ago
Ondřej Hruška 7024fb4402 Refactoring of config inner classes 10 years ago
Ondřej Hruška 4bc4af553e Renamed save dir to .rogue 10 years ago
Ondřej Hruška f48797feb5 Update README.md 11 years ago
Ondřej Hruška 416fe6773d Merge branch 'master' of https://github.com/MightyPork/rogue.git 11 years ago
Ondřej Hruška 4d2fc55a29 Now using to MightyUtils (reduction diet for the gamecore package) 11 years ago
Ondřej Hruška b2c9eec9fb Update README.md 11 years ago
Ondřej Hruška b384d7a70a Changes reflecting refactoring of the DynMath library. 11 years ago
Ondřej Hruška a3d0c945bc new jardesc, improved readme 11 years ago
Ondřej Hruška 0872a9a221 readme changed 11 years ago
Ondřej Hruška dca4b58b86 Moved constraints to a DynMath project (repo MightyPork/dynmath) 11 years ago
Ondřej Hruška 0f2cb0b985 license change (added MightyPork) 11 years ago
Ondřej Hruška e510ca592b improved @author mark 11 years ago
Ondřej Hruška cba8886ece improved license 11 years ago
Ondřej Hruška f862708ddd Changed @author to real name 11 years ago
Ondřej Hruška 5963ec62a7 added license: BSC 3 clause 11 years ago
Ondřej Hruška b0c0545c2d updated readme. 11 years ago
Ondřej Hruška 7ddaafcffa updated readme. 11 years ago
Ondřej Hruška 03f4d453ba increased verison nr 11 years ago
Ondřej Hruška 25e5275e1e Merge remote-tracking branch 'origin/v5stable' 11 years ago
Ondřej Hruška 1b9c8c6be7 improved cli args 11 years ago
Ondřej Hruška 162bcc9b7e Sounds in rogue & new assets 11 years ago
  1. 33
      .classpath
  2. 2
      .gitignore
  3. 23
      LICENSE.txt
  4. 2
      Makefile
  5. 52
      README.md
  6. 20
      build/export_new.jardesc
  7. BIN
      lib/OpenAL32.dll
  8. BIN
      lib/OpenAL64.dll
  9. BIN
      lib/jinput-dx8.dll
  10. BIN
      lib/jinput-dx8_64.dll
  11. BIN
      lib/jinput-raw.dll
  12. BIN
      lib/jinput-raw_64.dll
  13. BIN
      lib/jinput.jar
  14. BIN
      lib/jogg-0.0.7.jar
  15. BIN
      lib/jorbis-0.0.15.jar
  16. BIN
      lib/libjinput-linux.so
  17. BIN
      lib/libjinput-linux64.so
  18. BIN
      lib/libjinput-osx.jnilib
  19. BIN
      lib/liblwjgl.jnilib
  20. BIN
      lib/liblwjgl.so
  21. BIN
      lib/liblwjgl64.so
  22. BIN
      lib/libopenal.so
  23. BIN
      lib/libopenal64.so
  24. BIN
      lib/lwjgl-source-2.8.4.zip
  25. BIN
      lib/lwjgl.dll
  26. BIN
      lib/lwjgl.jar
  27. BIN
      lib/lwjgl64.dll
  28. BIN
      lib/lwjgl_util.jar
  29. BIN
      lib/slick-util-src.zip
  30. BIN
      lib/slick-util.jar
  31. BIN
      res/audio/effects/break.ogg
  32. BIN
      res/audio/effects/click.ogg
  33. BIN
      res/audio/effects/crate.ogg
  34. BIN
      res/audio/effects/door.ogg
  35. BIN
      res/audio/effects/drop.ogg
  36. BIN
      res/audio/effects/eat.ogg
  37. BIN
      res/audio/effects/hurt.ogg
  38. BIN
      res/audio/effects/lose.ogg
  39. BIN
      res/audio/effects/mouse1.ogg
  40. BIN
      res/audio/effects/mouse2.ogg
  41. BIN
      res/audio/effects/mouse3.ogg
  42. BIN
      res/audio/effects/mouse4.ogg
  43. BIN
      res/audio/effects/mouse5.ogg
  44. BIN
      res/audio/effects/pickup.ogg
  45. 0
      res/audio/effects/shutter.ogg
  46. BIN
      res/audio/effects/win.ogg
  47. BIN
      res/audio/music/8bit_Dungeon_Boss.ogg
  48. BIN
      res/audio/music/8bit_Dungeon_Level.ogg
  49. BIN
      res/audio/music/Home_Base_Groove.ogg
  50. 246
      src/mightypork/gamecore/core/Config.java
  51. 117
      src/mightypork/gamecore/core/WorkDir.java
  52. 37
      src/mightypork/gamecore/core/events/MainLoopRequest.java
  53. 33
      src/mightypork/gamecore/core/events/ShudownRequest.java
  54. 32
      src/mightypork/gamecore/core/events/UserQuitRequest.java
  55. 18
      src/mightypork/gamecore/core/events/UserQuitRequestListener.java
  56. 41
      src/mightypork/gamecore/core/modules/AppAccess.java
  57. 65
      src/mightypork/gamecore/core/modules/AppAccessAdapter.java
  58. 61
      src/mightypork/gamecore/core/modules/AppModule.java
  59. 63
      src/mightypork/gamecore/core/modules/AppSubModule.java
  60. 525
      src/mightypork/gamecore/core/modules/BaseApp.java
  61. 157
      src/mightypork/gamecore/core/modules/MainLoop.java
  62. 16
      src/mightypork/gamecore/eventbus/BusAccess.java
  63. 121
      src/mightypork/gamecore/eventbus/BusEvent.java
  64. 400
      src/mightypork/gamecore/eventbus/EventBus.java
  65. 207
      src/mightypork/gamecore/eventbus/EventChannel.java
  66. 115
      src/mightypork/gamecore/eventbus/clients/BusNode.java
  67. 42
      src/mightypork/gamecore/eventbus/clients/ClientHub.java
  68. 21
      src/mightypork/gamecore/eventbus/clients/ClientList.java
  69. 27
      src/mightypork/gamecore/eventbus/clients/DelegatingClient.java
  70. 51
      src/mightypork/gamecore/eventbus/clients/DelegatingList.java
  71. 45
      src/mightypork/gamecore/eventbus/clients/RootBusNode.java
  72. 16
      src/mightypork/gamecore/eventbus/clients/ToggleableClient.java
  73. 22
      src/mightypork/gamecore/eventbus/event_flags/DelayedEvent.java
  74. 16
      src/mightypork/gamecore/eventbus/event_flags/DirectEvent.java
  75. 21
      src/mightypork/gamecore/eventbus/event_flags/NonConsumableEvent.java
  76. 21
      src/mightypork/gamecore/eventbus/event_flags/NonRejectableEvent.java
  77. 17
      src/mightypork/gamecore/eventbus/event_flags/NotLoggedEvent.java
  78. 16
      src/mightypork/gamecore/eventbus/event_flags/SingleReceiverEvent.java
  79. 24
      src/mightypork/gamecore/eventbus/events/DestroyEvent.java
  80. 15
      src/mightypork/gamecore/eventbus/events/Destroyable.java
  81. 37
      src/mightypork/gamecore/eventbus/events/UpdateEvent.java
  82. 17
      src/mightypork/gamecore/eventbus/events/Updateable.java
  83. 51
      src/mightypork/gamecore/gui/Action.java
  84. 36
      src/mightypork/gamecore/gui/ActionGroup.java
  85. 17
      src/mightypork/gamecore/gui/ActionTrigger.java
  86. 12
      src/mightypork/gamecore/gui/AlignX.java
  87. 12
      src/mightypork/gamecore/gui/AlignY.java
  88. 25
      src/mightypork/gamecore/gui/Enableable.java
  89. 15
      src/mightypork/gamecore/gui/Hideable.java
  90. 169
      src/mightypork/gamecore/gui/components/BaseComponent.java
  91. 95
      src/mightypork/gamecore/gui/components/Component.java
  92. 7
      src/mightypork/gamecore/gui/components/DynamicWidthComponent.java
  93. 15
      src/mightypork/gamecore/gui/components/InputComponent.java
  94. 162
      src/mightypork/gamecore/gui/components/LayoutComponent.java
  95. 78
      src/mightypork/gamecore/gui/components/LinearComponent.java
  96. 28
      src/mightypork/gamecore/gui/components/PluggableRenderable.java
  97. 49
      src/mightypork/gamecore/gui/components/input/ClickableComponent.java
  98. 64
      src/mightypork/gamecore/gui/components/input/ClickableWrapper.java
  99. 78
      src/mightypork/gamecore/gui/components/input/TextButton.java
  100. 45
      src/mightypork/gamecore/gui/components/layout/ColumnLayout.java
  101. Some files were not shown because too many files have changed in this diff Show More

@ -2,35 +2,8 @@
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
<classpathentry kind="lib" path="lib/jinput.jar" sourcepath="lib/lwjgl-source-2.8.4.zip">
<attributes>
<attribute name="org.eclipse.jdt.launching.CLASSPATH_ATTR_LIBRARY_PATH_ENTRY" value="Rogue/lib"/>
</attributes>
</classpathentry>
<classpathentry kind="lib" path="lib/jogg-0.0.7.jar">
<attributes>
<attribute name="org.eclipse.jdt.launching.CLASSPATH_ATTR_LIBRARY_PATH_ENTRY" value="Rogue/lib"/>
</attributes>
</classpathentry>
<classpathentry kind="lib" path="lib/jorbis-0.0.15.jar">
<attributes>
<attribute name="org.eclipse.jdt.launching.CLASSPATH_ATTR_LIBRARY_PATH_ENTRY" value="Rogue/lib"/>
</attributes>
</classpathentry>
<classpathentry kind="lib" path="lib/lwjgl_util.jar" sourcepath="lib/lwjgl-source-2.8.4.zip">
<attributes>
<attribute name="org.eclipse.jdt.launching.CLASSPATH_ATTR_LIBRARY_PATH_ENTRY" value="Rogue/lib"/>
</attributes>
</classpathentry>
<classpathentry kind="lib" path="lib/lwjgl.jar" sourcepath="lib/lwjgl-source-2.8.4.zip">
<attributes>
<attribute name="org.eclipse.jdt.launching.CLASSPATH_ATTR_LIBRARY_PATH_ENTRY" value="Rogue/lib"/>
</attributes>
</classpathentry>
<classpathentry kind="lib" path="lib/slick-util.jar" sourcepath="lib/slick-util-src.zip">
<attributes>
<attribute name="org.eclipse.jdt.launching.CLASSPATH_ATTR_LIBRARY_PATH_ENTRY" value="Rogue/lib"/>
</attributes>
</classpathentry>
<classpathentry combineaccessrules="false" kind="src" path="/MightyUtils"/>
<classpathentry combineaccessrules="false" kind="src" path="/GameCore"/>
<classpathentry combineaccessrules="false" kind="src" path="/GameCore-LWJGL"/>
<classpathentry kind="output" path="bin"/>
</classpath>

2
.gitignore vendored

@ -2,7 +2,7 @@
/target/
/~local/
/.rogue-save/
/build/
/rogue-workdir/
/build/out/*.jar
/build/in/*.jar
*.log

@ -0,0 +1,23 @@
Copyright (c) 2014, Ondřej Hruška (MightyPork), <ondra@ondrovo.com>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

@ -39,7 +39,7 @@ run: $(OUT)
java -jar $(OUT) -w .rogue-save
debug: $(OUT)
java -jar $(OUT) -w .rogue-save --debug
java -jar $(OUT) -w .rogue-save --verbose --debug-bus
stats:
@-echo "Commits:" `git rev-list HEAD --count`

@ -1,6 +1,31 @@
# Rogue - Savage Rats
Manual on [Google Drive](https://docs.google.com/document/d/1Ak9oVOnCKSqWux4Hm_-efDYTj4uewf5MtR_Zmys7goQ)
**Read the [MANUAL](http://goo.gl/AU0IdI) if you want to play the game.**
NOTE: Master has experimental code
----------------------------------
The Master branch holds Rogue based on the new GameCore implementation (see "dependencies" below for link).
The latest **stable** version is in the branch `v5stable`. That branch is stable, debugged and has no dependencies. It's a standalone Eclipse project.
You can use `v5stable` to see the original source and try to build it, but further development of that branch is stopped. The master means the future.
DEPENDENCIES
------------
If you intend to **build it from source**, you will need those Eclipse projects in your workspace:
- [MightyPork/gamecore](https://github.com/MightyPork/gamecore) - The "GameCore" game engine
- [MightyPork/gamecore-lwjgl](https://github.com/MightyPork/gamecore-lwjgl) - LWJGL backend for GameCore
- [MightyPork/mightyutils](https://github.com/MightyPork/mightyutils) - Game utils
---
The following is the original readme, applicable to `v5stable` version.
## Description
@ -34,10 +59,9 @@ Manual on [Google Drive](https://docs.google.com/document/d/1Ak9oVOnCKSqWux4Hm_-
- SlickUtil (texture loader, audio system)
## BUILDING FROM SOURCE
1. Export a jar with the `mightypork.*` packages and the `res` folder into `build/in/build.jar`
1. Export a jar with the compiled sources and /res into `build/in/build.jar`
2. Run `make` to create a stand-alone executable jar in `build/out/release.jar`
3. Use `make run` to execute it
@ -47,24 +71,4 @@ Manual on [Google Drive](https://docs.google.com/document/d/1Ak9oVOnCKSqWux4Hm_-
The game is controlled by mouse and keyboard.
### In-game controls
- *ARROWS* or *ASDW* - walking
- *E* - eat smallest food
- *Z* - Toggle map magnification (zoom)
- *M* - Toggle the minimap
- *I* - Toggle inventory screen (pauses the game)
- *SPACE*, *P*, *PAUSE* - pause / resume the game
- *Left button hold* - walk in the direction
- *Right click* - find path to the tile (works also on Minimap)
### Global controls
- *Ctrl+M* - Jump to main menu
- *Ctrl+Q* - Quit to DOS
- *F2* - Take a screenshot
- *F11* - Toggle fullscreen
See the manual (link at the top) for more detailed info.

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<jardesc>
<jar path="Rogue/build/in/build.jar"/>
<options buildIfNeeded="true" compress="false" descriptionLocation="/Rogue/build/export_new.jardesc" exportErrors="true" exportWarnings="true" includeDirectoryEntries="true" overwrite="true" saveDescription="true" storeRefactorings="false" useSourceFolders="false"/>
<storedRefactorings deprecationInfo="true" structuralOnly="false"/>
<selectedProjects/>
<manifest generateManifest="true" manifestLocation="" manifestVersion="1.0" reuseManifest="false" saveManifest="false" usesManifest="true">
<sealing sealJar="false">
<packagesToSeal/>
<packagesToUnSeal/>
</sealing>
</manifest>
<selectedElements exportClassFiles="true" exportJavaFiles="false" exportOutputFolder="false">
<javaElement handleIdentifier="=DynMath/src"/>
<javaElement handleIdentifier="=Rogue/src"/>
<javaElement handleIdentifier="=Ion/src"/>
<folder path="/Rogue/res"/>
<file path="/Rogue/LICENSE.txt"/>
</selectedElements>
</jardesc>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

@ -1,246 +0,0 @@
package mightypork.gamecore.core;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import mightypork.gamecore.input.KeyStroke;
import mightypork.gamecore.input.Keys;
import mightypork.gamecore.logging.Log;
import mightypork.gamecore.util.files.config.Property;
import mightypork.gamecore.util.files.config.PropertyManager;
/**
* Static application configuration
*
* @author MightyPork
*/
public class Config {
/**
* Config setup. Used to populate the config file.
*/
public static interface ConfigSetup {
void addOptions(PropertyManager prop);
}
/**
* Key configurator access
*/
public static class KeyOpts {
private KeyOpts()
{
}
public void add(String cfgKey, String dataString)
{
add(cfgKey, dataString, null);
}
public void add(String cfgKey, String dataString, String comment)
{
final KeyProperty kprop = new KeyProperty(prefixKey(cfgKey), KeyStroke.createFromDataString(dataString), comment);
strokes.put(prefixKey(cfgKey), kprop);
cfg.putProperty(kprop);
}
}
/**
* Key configurator
*/
public static interface KeySetup {
public void addKeys(KeyOpts keys);
}
/**
* Key property.<br>
* The stored value must be invariant ({@link KeyStroke} is mutable).
*
* @author MightyPork
*/
public static class KeyProperty extends Property<KeyStroke> {
public KeyProperty(String key, KeyStroke defaultValue, String comment)
{
super(key, defaultValue, comment);
}
@Override
public KeyStroke decode(String string, KeyStroke defval)
{
if (string != null) {
// keep it invariant
final int backup_key = getValue().getKey();
final int backup_mod = getValue().getMod();
getValue().fromDataString(string);
if (getValue().getKey() == Keys.NONE) {
getValue().setTo(backup_key, backup_mod);
}
}
return getValue();
}
@Override
public String encode(KeyStroke value)
{
return value.toDataString();
}
@Override
public void setValue(Object value)
{
// keep it invariant
getValue().setTo(((KeyStroke) value).getKey(), ((KeyStroke) value).getMod());
}
}
public static Config.KeyOpts keyOpts = new Config.KeyOpts();
public static Map<String, KeyProperty> strokes = new HashMap<>();
private static PropertyManager cfg;
/**
* Initialize property manger for a file
*
* @param file config file
* @param comment file comment
*/
public static void init(File file, String comment)
{
cfg = new PropertyManager(file, comment);
cfg.cfgNewlineBeforeComments(true);
cfg.cfgSeparateSections(true);
}
/**
* populate config with keys from a key setup
*
* @param keys key setup to add
*/
public static void registerKeys(KeySetup keys)
{
keys.addKeys(Config.keyOpts);
}
/**
* Populate config with options from a config setup
*
* @param conf config setup to add
*/
public static void registerOptions(ConfigSetup conf)
{
conf.addOptions(Config.cfg);
}
/**
* Load config from file
*/
public static void load()
{
cfg.load();
}
/**
* Save config to file
*/
public static void save()
{
Log.f3("Saving config.");
cfg.save();
}
/**
* Get an option for key
*
* @param key
* @return option value
*/
public static <T> T getValue(String key)
{
try {
if (cfg.getProperty(key) == null) {
throw new IllegalArgumentException("No such property: " + key);
}
return cfg.getValue(key);
} catch (final ClassCastException cce) {
throw new RuntimeException("Property of incompatible type: " + key);
}
}
/**
* Set option to a value
*
* @param key option key
* @param value value to set
*/
public static <T> void setValue(String key, T value)
{
if (cfg.getProperty(key) == null) {
throw new IllegalArgumentException("No such property: " + key);
}
cfg.setValue(key, value);
}
private static String prefixKey(String cfgKey)
{
return "key." + cfgKey;
}
/**
* Get keystroke for name
*
* @param cfgKey stroke identifier in config file
* @return the stroke
*/
public static KeyStroke getKey(String cfgKey)
{
final KeyProperty kp = strokes.get(prefixKey(cfgKey));
if (kp == null) {
throw new IllegalArgumentException("No such stroke: " + cfgKey);
}
return kp.getValue();
}
/**
* Set a keystroke for name
*
* @param cfgKey stroke identifier in config file
* @param key stroke key
* @param mod stroke modifiers
*/
public static void setKey(String cfgKey, int key, int mod)
{
final Config.KeyProperty kp = strokes.get(prefixKey(cfgKey));
if (kp == null) {
throw new IllegalArgumentException("No such stroke: " + cfgKey);
}
kp.getValue().setTo(key, mod);
}
}

@ -1,117 +0,0 @@
package mightypork.gamecore.core;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import mightypork.gamecore.logging.Log;
/**
* Static application workdir accessor.
*
* @author MightyPork
*/
public class WorkDir {
/**
* Route configurator.
*/
public static interface RouteSetup {
public void addRoutes(RouteOpts routeOpts);
}
/**
* Route configurator access
*/
public static class RouteOpts {
public void addPath(String alias, String path)
{
WorkDir.addPath(alias, path);
}
}
public static RouteOpts routeOpts = new RouteOpts();
private static File workdir;
private static Map<String, String> namedPaths = new HashMap<>();
public static void init(File workdir)
{
WorkDir.workdir = workdir;
}
/**
* Add a path alias (dir or file)
*
* @param alias path alias
* @param path path relative to workdir
*/
public static void addPath(String alias, String path)
{
namedPaths.put(alias, path);
}
public static void registerRoutes(RouteSetup rs)
{
rs.addRoutes(routeOpts);
}
/**
* Get workdir folder, create if not exists.
*
* @param path dir path relative to workdir
* @return dir file
*/
public static File getDir(String path)
{
if (namedPaths.containsKey(path)) path = namedPaths.get(path);
final File f = new File(workdir, path);
if (!f.exists()) {
if (!f.mkdirs()) {
Log.w("Could not create a directory: " + f + " (path: " + path + ")");
}
}
return f;
}
/**
* Get workdir file, create parent if not exists.
*
* @param path dir path relative to workdir
* @return dir file
*/
public static File getFile(String path)
{
if (namedPaths.containsKey(path)) path = namedPaths.get(path);
final File f = new File(workdir, path);
// create the parent dir
if (!f.getParent().equals(workdir)) {
f.getParentFile().mkdirs();
}
return f;
}
/**
* @return the workdir File
*/
public static File getWorkDir()
{
return workdir;
}
}

@ -1,37 +0,0 @@
package mightypork.gamecore.core.events;
import mightypork.gamecore.core.modules.MainLoop;
import mightypork.gamecore.eventbus.BusEvent;
import mightypork.gamecore.eventbus.event_flags.SingleReceiverEvent;
/**
* Request to execute given {@link Runnable} in main loop.
*
* @author MightyPork
*/
@SingleReceiverEvent
public class MainLoopRequest extends BusEvent<MainLoop> {
private final Runnable task;
private final boolean priority;
/**
* @param task task to run on main thread in rendering context
* @param priority if true, skip other tasks in queue
*/
public MainLoopRequest(Runnable task, boolean priority)
{
this.task = task;
this.priority = priority;
}
@Override
public void handleBy(MainLoop handler)
{
handler.queueTask(task, priority);
}
}

@ -1,33 +0,0 @@
package mightypork.gamecore.core.events;
import mightypork.gamecore.core.modules.MainLoop;
import mightypork.gamecore.eventbus.BusEvent;
import mightypork.gamecore.eventbus.event_flags.NonConsumableEvent;
import mightypork.gamecore.eventbus.event_flags.SingleReceiverEvent;
import mightypork.gamecore.resources.audio.SoundSystem;
/**
* Shutdown request, non-interactive. Shutdown needs to execute on GL thread for
* display to deinit properly.
*
* @author MightyPork
*/
@SingleReceiverEvent
@NonConsumableEvent
public class ShudownRequest extends BusEvent<MainLoop> {
@Override
public void handleBy(final MainLoop handler)
{
handler.queueTask(new Runnable() {
@Override
public void run()
{
handler.shutdown();
}
}, true);
}
}

@ -1,32 +0,0 @@
package mightypork.gamecore.core.events;
import mightypork.gamecore.eventbus.BusEvent;
import mightypork.gamecore.eventbus.EventBus;
/**
* User quit request. This event is triggered when user clicks the "close"
* titlebar button, and if no client consumes it, the application will be shut
* down.
*
* @author MightyPork
*/
public class UserQuitRequest extends BusEvent<UserQuitRequestListener> {
@Override
protected void handleBy(UserQuitRequestListener handler)
{
handler.onQuitRequest(this);
}
@Override
public void onDispatchComplete(EventBus bus)
{
if (!isConsumed()) {
bus.send(new ShudownRequest());
}
}
}

@ -1,18 +0,0 @@
package mightypork.gamecore.core.events;
/**
* Quit request listener; implementing client can abort shutdown.
*
* @author MightyPork
*/
public interface UserQuitRequestListener {
/**
* Intercept quit request.<br>
* Consume the event to abort shutdown (ie. ask user to save)
*
* @param event quit request event.
*/
void onQuitRequest(UserQuitRequest event);
}

@ -1,41 +0,0 @@
package mightypork.gamecore.core.modules;
import mightypork.gamecore.eventbus.BusAccess;
import mightypork.gamecore.input.InputSystem;
import mightypork.gamecore.render.DisplaySystem;
import mightypork.gamecore.resources.audio.SoundSystem;
/**
* App interface visible to subsystems
*
* @author MightyPork
*/
public interface AppAccess extends BusAccess {
/**
* @return sound system
*/
abstract SoundSystem getSoundSystem();
/**
* @return input system
*/
abstract InputSystem getInput();
/**
* @return display system
*/
abstract DisplaySystem getDisplay();
/**
* Quit to OS<br>
* Destroy app & exit VM
*/
abstract void shutdown();
}

@ -1,65 +0,0 @@
package mightypork.gamecore.core.modules;
import mightypork.gamecore.eventbus.EventBus;
import mightypork.gamecore.input.InputSystem;
import mightypork.gamecore.render.DisplaySystem;
import mightypork.gamecore.resources.audio.SoundSystem;
/**
* App access adapter (defualt {@link AppAccess} implementation)
*
* @author MightyPork
*/
public class AppAccessAdapter implements AppAccess {
private final AppAccess app;
/**
* @param app app access
*/
public AppAccessAdapter(AppAccess app)
{
if (app == null) throw new NullPointerException("AppAccess instance cannot be null.");
this.app = app;
}
@Override
public final SoundSystem getSoundSystem()
{
return app.getSoundSystem();
}
@Override
public final InputSystem getInput()
{
return app.getInput();
}
@Override
public final DisplaySystem getDisplay()
{
return app.getDisplay();
}
@Override
public final EventBus getEventBus()
{
return app.getEventBus();
}
@Override
public final void shutdown()
{
app.shutdown();
}
}

@ -1,61 +0,0 @@
package mightypork.gamecore.core.modules;
import mightypork.gamecore.eventbus.clients.RootBusNode;
import mightypork.gamecore.input.InputSystem;
import mightypork.gamecore.render.DisplaySystem;
import mightypork.gamecore.resources.audio.SoundSystem;
/**
* App event bus client, to be used for subsystems, screens and anything that
* needs access to the eventbus and other systems; Attached directly to bus.
*
* @author MightyPork
*/
public abstract class AppModule extends RootBusNode implements AppAccess {
private final AppAccess app;
/**
* Create a module
*
* @param app access to app systems
*/
public AppModule(AppAccess app)
{
super(app);
this.app = app;
}
@Override
public final SoundSystem getSoundSystem()
{
return app.getSoundSystem();
}
@Override
public final InputSystem getInput()
{
return app.getInput();
}
@Override
public final DisplaySystem getDisplay()
{
return app.getDisplay();
}
@Override
public final void shutdown()
{
app.shutdown();
}
}

@ -1,63 +0,0 @@
package mightypork.gamecore.core.modules;
import mightypork.gamecore.eventbus.clients.BusNode;
import mightypork.gamecore.eventbus.clients.DelegatingClient;
import mightypork.gamecore.eventbus.clients.RootBusNode;
import mightypork.gamecore.input.InputSystem;
import mightypork.gamecore.render.DisplaySystem;
import mightypork.gamecore.resources.audio.SoundSystem;
/**
* Delegating bus client, to be attached to any {@link DelegatingClient}, such
* as a {@link RootBusNode}.
*
* @author MightyPork
*/
public class AppSubModule extends BusNode implements AppAccess {
private final AppAccess app;
/**
* Create submodule
*
* @param app access to app systems
*/
public AppSubModule(AppAccess app)
{
super(app);
this.app = app;
}
@Override
public final SoundSystem getSoundSystem()
{
return app.getSoundSystem();
}
@Override
public final InputSystem getInput()
{
return app.getInput();
}
@Override
public final DisplaySystem getDisplay()
{
return app.getDisplay();
}
@Override
public final void shutdown()
{
app.shutdown();
}
}

@ -1,525 +0,0 @@
package mightypork.gamecore.core.modules;
import java.io.File;
import java.io.IOException;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import javax.swing.JOptionPane;
import mightypork.gamecore.core.Config;
import mightypork.gamecore.core.WorkDir;
import mightypork.gamecore.core.Config.ConfigSetup;
import mightypork.gamecore.core.Config.KeySetup;
import mightypork.gamecore.core.WorkDir.RouteSetup;
import mightypork.gamecore.eventbus.EventBus;
import mightypork.gamecore.eventbus.events.DestroyEvent;
import mightypork.gamecore.gui.screens.ScreenRegistry;
import mightypork.gamecore.gui.screens.impl.CrossfadeOverlay;
import mightypork.gamecore.input.InputSystem;
import mightypork.gamecore.logging.Log;
import mightypork.gamecore.logging.SlickLogRedirector;
import mightypork.gamecore.logging.writers.LogWriter;
import mightypork.gamecore.render.DisplaySystem;
import mightypork.gamecore.resources.AsyncResourceLoader;
import mightypork.gamecore.resources.Res;
import mightypork.gamecore.resources.ResourceLoader;
import mightypork.gamecore.resources.ResourceSetup;
import mightypork.gamecore.resources.audio.SoundSystem;
import mightypork.gamecore.util.annot.DefaultImpl;
import mightypork.gamecore.util.files.InstanceLock;
import mightypork.gamecore.util.ion.Ion;
import mightypork.gamecore.util.math.algo.Coord;
import mightypork.gamecore.util.math.algo.Move;
/**
* Basic screen-based game with subsystems.<br>
* This class takes care of the initialization sequence.
*
* @author MightyPork
*/
public abstract class BaseApp implements AppAccess, UncaughtExceptionHandler {
/**
* Init options holder class
*/
public class AppInitOptions {
private String logDir = "log";
private String logFilePrefix = "runtime";
private String screenshotDir = "screenshots";
private int logArchiveCount = 0;
private boolean busLogging = false;
private String configFile = "settings.cfg";
private String configComment = "Main config file";
public String lockFile = ".lock";
private final List<ResourceSetup> resourceLists = new ArrayList<>();
private final List<Config.KeySetup> keyLists = new ArrayList<>();
private final List<ConfigSetup> configLists = new ArrayList<>();
private final List<RouteSetup> routeLists = new ArrayList<>();
private ResourceLoader resourceLoader = new AsyncResourceLoader();
private Level logLevel = Level.ALL;
public boolean sigleInstance;
private Level logSoutLevel;
public void setConfigFile(String filename, String comment)
{
configFile = filename;
configComment = comment;
}
public void addConfig(ConfigSetup cfg)
{
configLists.add(cfg);
}
public void addKeys(Config.KeySetup keys)
{
keyLists.add(keys);
}
public void addRoutes(RouteSetup keys)
{
routeLists.add(keys);
}
public void addResources(ResourceSetup res)
{
resourceLists.add(res);
}
public void setBusLogging(boolean yes)
{
busLogging = yes;
}
public void setLogOptions(String logDir, String filePrefix, int archivedCount, Level logLevel)
{
this.logDir = logDir;
this.logFilePrefix = filePrefix;
this.logArchiveCount = archivedCount;
this.logLevel = logLevel;
}
public void setResourceLoader(ResourceLoader resLoader)
{
resourceLoader = resLoader;
}
public void setScreenshotDir(String path)
{
this.screenshotDir = path;
}
public void setLockFile(String lockFile)
{
this.lockFile = lockFile;
}
public void setLogLevel(Level logLevel, Level soutLevel)
{
this.logLevel = logLevel;
this.logSoutLevel = soutLevel;
}
}
// modules
private InputSystem inputSystem;
private DisplaySystem displaySystem;
private SoundSystem soundSystem;
private EventBus eventBus;
private MainLoop gameLoop;
private ScreenRegistry screenRegistry;
private boolean started = false;
private boolean lockObtained = false;
// init opt holder
private final AppInitOptions opt = new AppInitOptions();
/**
* Get init options
*
* @return opt holder
*/
public AppInitOptions opt()
{
if (started) {
throw new IllegalStateException("Cannot alter init options after starting the App.");
}
return opt;
}
public BaseApp(File workdir, boolean singleInstance)
{
WorkDir.init(workdir);
opt.sigleInstance = singleInstance;
}
/**
* Start the application
*/
public final void start()
{
Thread.setDefaultUncaughtExceptionHandler(this);
initialize();
Log.i("Starting main loop...");
// open first screen
started = true;
gameLoop.start();
}
/**
* Init the app
*/
protected void initialize()
{
if (opt.sigleInstance) initLock();
lockObtained = true;
for (final RouteSetup rs : opt.routeLists) {
WorkDir.registerRoutes(rs);
}
WorkDir.addPath("_screenshot_dir", opt.screenshotDir);
// apply configurations
Config.init(WorkDir.getFile(opt.configFile), opt.configComment);
// add keys to config
for (final KeySetup l : opt.keyLists) {
Config.registerKeys(l);
}
// add options to config
for (final ConfigSetup c : opt.configLists) {
Config.registerOptions(c);
}
Config.load();
/*
* Setup logging
*/
final LogWriter log = Log.create(opt.logFilePrefix, new File(WorkDir.getDir(opt.logDir), opt.logFilePrefix + ".log"), opt.logArchiveCount);
Log.setMainLogger(log);
Log.setLevel(opt.logLevel);
Log.setSysoutLevel(opt.logSoutLevel);
// connect slickutil to the logger
org.newdawn.slick.util.Log.setLogSystem(new SlickLogRedirector(log));
writeLogHeader();
Log.i("=== Starting initialization sequence ===");
// pre-init hook
Log.f2("Calling pre-init hook...");
preInit();
/*
* Event bus
*/
Log.f2("Starting Event Bus...");
eventBus = new EventBus();
eventBus.subscribe(this);
eventBus.detailedLogging = opt.busLogging;
/*
* Ionizables
*/
Log.f3("Initializing ION save system...");
registerIonizables();
/*
* Display
*/
Log.f2("Initializing Display System...");
displaySystem = new DisplaySystem(this);
initDisplay(displaySystem);
/*
* Audio
*/
Log.f2("Initializing Sound System...");
soundSystem = new SoundSystem(this);
initSoundSystem(soundSystem);
/*
* Input
*/
Log.f2("Initializing Input System...");
inputSystem = new InputSystem(this);
initInputSystem(inputSystem);
/*
* Prepare main loop
*/
Log.f1("Creating Screen Registry and Game Loop...");
screenRegistry = new ScreenRegistry(this);
gameLoop = createMainLoop();
gameLoop.setRootRenderable(screenRegistry);
/*
* Load resources
*
* Resources should be registered to registries, and AsyncResourceLoader will load them.
*/
Log.f1("Loading resources...");
if (opt.resourceLoader != null) {
opt.resourceLoader.init(this);
}
Res.init(this);
for (final ResourceSetup rl : opt.resourceLists) {
Res.load(rl);
}
/*
* Screen registry
*
* Must be after resources, because screens can request them during instantiation.
*/
Log.f2("Registering screens...");
initScreens(screenRegistry);
postInit();
Log.i("=== Initialization sequence completed ===");
}
protected void writeLogHeader()
{
logSystemInfo();
}
protected void logSystemInfo()
{
String txt = "";
txt += "\n### SYSTEM INFO ###\n\n";
txt += " Platform ...... " + System.getProperty("os.name") + "\n";
txt += " Runtime ....... " + System.getProperty("java.runtime.name") + "\n";
txt += " Java .......... " + System.getProperty("java.version") + "\n";
txt += " Launch path ... " + System.getProperty("user.dir") + "\n";
try {
txt += " Workdir ....... " + WorkDir.getWorkDir().getCanonicalPath() + "\n";
} catch (final IOException e) {
Log.e(e);
}
Log.i(txt);
}
protected void registerIonizables()
{
Ion.registerType(Coord.ION_MARK, Coord.class);
Ion.registerType(Move.ION_MARK, Move.class);
}
/**
* Called at the beginning of the initialization sequence, right after lock
* was obtained.
*/
@DefaultImpl
protected void preInit()
{
}
/**
* Called at the end of init sequence, before main loop starts.
*/
@DefaultImpl
protected void postInit()
{
}
/**
* Create window and configure display system
*
* @param display
*/
@DefaultImpl
protected void initDisplay(DisplaySystem display)
{
display.createMainWindow(800, 600, true, false, "LWJGL game");
display.setTargetFps(60);
}
/**
* Configure sound system (ie. adjust volume)
*
* @param audio
*/
@DefaultImpl
protected void initSoundSystem(SoundSystem audio)
{
}
/**
* Register game screens to the registry.
*
* @param screens
*/
protected void initScreens(ScreenRegistry screens)
{
screens.addOverlay(new CrossfadeOverlay(this));
}
/**
* Create game loop instance
*
* @return the game loop.
*/
protected MainLoop createMainLoop()
{
return new MainLoop(this);
}
/*
* Try to obtain lock.
*/
private void initLock()
{
final File lock = WorkDir.getFile(opt.lockFile);
if (!InstanceLock.onFile(lock)) {
onLockError();
return;
}
}
@DefaultImpl
protected void initInputSystem(InputSystem input)
{
}
/**
* Triggered when lock cannot be obtained.<br>
* App should terminate gracefully.
*/
protected void onLockError()
{
Log.e("Could not obtain lock file.\nOnly one instance can run at a time.");
//@formatter:off
JOptionPane.showMessageDialog(
null,
"Another instance is already running.\n(Delete the "+opt.lockFile +" file in the working directory to override)",
"Lock Error",
JOptionPane.ERROR_MESSAGE
);
//@formatter:on
shutdown();
}
@Override
public final SoundSystem getSoundSystem()
{
return soundSystem;
}
@Override
public final InputSystem getInput()
{
return inputSystem;
}
@Override
public final DisplaySystem getDisplay()
{
return displaySystem;
}
@Override
public final EventBus getEventBus()
{
return eventBus;
}
protected void beforeShutdown()
{
if (lockObtained) Config.save();
}
@Override
public final void uncaughtException(Thread t, Throwable e)
{
onCrash(e);
}
protected void onCrash(Throwable e)
{
Log.e("The game has crashed.", e);
shutdown();
}
@Override
public final void shutdown()
{
beforeShutdown();
Log.i("Shutting down subsystems...");
try {
if (getEventBus() != null) {
getEventBus().send(new DestroyEvent());
getEventBus().destroy();
}
} catch (final Throwable e) {
Log.e(e);
}
Log.i("Terminating...");
System.exit(0);
}
}

@ -1,157 +0,0 @@
package mightypork.gamecore.core.modules;
import java.util.Deque;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.ConcurrentLinkedQueue;
import mightypork.gamecore.eventbus.events.UpdateEvent;
import mightypork.gamecore.gui.screens.ScreenRegistry;
import mightypork.gamecore.logging.Log;
import mightypork.gamecore.render.Renderable;
import mightypork.gamecore.render.TaskTakeScreenshot;
import mightypork.gamecore.render.events.ScreenshotRequestListener;
import mightypork.gamecore.resources.Profiler;
import mightypork.gamecore.util.Utils;
import mightypork.gamecore.util.annot.DefaultImpl;
import mightypork.gamecore.util.math.timing.TimerDelta;
/**
* Delta-timed game loop with task queue etc.
*
* @author MightyPork
*/
public class MainLoop extends AppModule implements ScreenshotRequestListener {
private static final double MAX_TIME_TASKS = 1 / 30D; // (avoid queue from hogging timing)
private static final double MAX_DELTA = 1 / 20D; // (skip huge gaps caused by loading resources etc)
private final Deque<Runnable> tasks = new ConcurrentLinkedDeque<>();
private TimerDelta timer;
private Renderable rootRenderable;
private volatile boolean running = true;
/**
* @param app {@link AppAccess} instance
*/
public MainLoop(AppAccess app)
{
super(app);
}
/**
* Set primary renderable
*
* @param rootRenderable main {@link Renderable}, typically a
* {@link ScreenRegistry}
*/
public void setRootRenderable(Renderable rootRenderable)
{
this.rootRenderable = rootRenderable;
}
/**
* Start the loop
*/
public void start()
{
timer = new TimerDelta();
while (running) {
getDisplay().beginFrame();
double delta = timer.getDelta();
if (delta > MAX_DELTA) {
Log.f3("(timing) Cropping delta: was " + delta + " , limit " + MAX_DELTA);
delta = MAX_DELTA;
}
getEventBus().sendDirect(new UpdateEvent(delta));
Runnable r;
long t = Profiler.begin();
while ((r = tasks.poll()) != null) {
Log.f3(" * Main loop task.");
r.run();
if (Profiler.end(t) > MAX_TIME_TASKS) {
Log.f3("! Postponing main loop tasks to next cycle.");
break;
}
}
beforeRender();
if (rootRenderable != null) {
rootRenderable.render();
}
afterRender();
getDisplay().endFrame();
}
}
/**
* Called before render
*/
@DefaultImpl
protected void beforeRender()
{
//
}
/**
* Called after render
*/
@DefaultImpl
protected void afterRender()
{
//
}
@Override
protected void deinit()
{
running = false;
}
/**
* Add a task to queue to be executed in the main loop (OpenGL thread)
*
* @param request task
* @param priority if true, skip other tasks
*/
public synchronized void queueTask(Runnable request, boolean priority)
{
if (priority) {
tasks.addFirst(request);
} else {
tasks.addLast(request);
}
}
@Override
public void onScreenshotRequest()
{
// ensure it's started in main thread
queueTask(new Runnable() {
@Override
public void run()
{
Utils.runAsThread(new TaskTakeScreenshot());
}
}, false);
}
}

@ -1,16 +0,0 @@
package mightypork.gamecore.eventbus;
/**
* Access to an {@link EventBus} instance
*
* @author MightyPork
*/
public interface BusAccess {
/**
* @return event bus
*/
EventBus getEventBus();
}

@ -1,121 +0,0 @@
package mightypork.gamecore.eventbus;
import mightypork.gamecore.eventbus.event_flags.DelayedEvent;
import mightypork.gamecore.eventbus.event_flags.DirectEvent;
import mightypork.gamecore.eventbus.event_flags.NonConsumableEvent;
import mightypork.gamecore.eventbus.event_flags.NotLoggedEvent;
import mightypork.gamecore.eventbus.event_flags.SingleReceiverEvent;
/**
* <p>
* Event that can be handled by HANDLER, subscribing to the event bus.
* </p>
* <p>
* Can be annotated as {@link SingleReceiverEvent} to be delivered once only,
* and {@link DelayedEvent} or {@link DirectEvent} to specify default sending
* mode. When marked as {@link NotLoggedEvent}, it will not appear in detailed
* bus logging (useful for very frequent events, such as UpdateEvent).
* </p>
* <p>
* Events annotated as {@link NonConsumableEvent} will throw an exception upon
* an attempt to consume them.
* </p>
* <p>
* Default sending mode (if not changed by annotations) is <i>queued</i> with
* zero delay.
* </p>
*
* @author MightyPork
* @param <HANDLER> handler type
*/
public abstract class BusEvent<HANDLER> {
private boolean consumed;
private boolean served;
/**
* Ask handler to handle this message.
*
* @param handler handler instance
*/
protected abstract void handleBy(HANDLER handler);
/**
* Consume the event, so no other clients will receive it.
*
* @throws UnsupportedOperationException if the {@link NonConsumableEvent}
* annotation is present.
*/
public final void consume()
{
if (consumed) throw new IllegalStateException("Already consumed.");
if (getClass().isAnnotationPresent(NonConsumableEvent.class)) {
throw new UnsupportedOperationException("Not consumable.");
}
consumed = true;
}
/**
* Deliver to a handler using the handleBy method.
*
* @param handler handler instance
*/
final void deliverTo(HANDLER handler)
{
handleBy(handler);
if (!served) {
if (getClass().isAnnotationPresent(SingleReceiverEvent.class)) {
consumed = true;
}
served = true;
}
}
/**
* Check if the event is consumed. Consumed event is not served to other
* clients.
*
* @return true if consumed
*/
public final boolean isConsumed()
{
return consumed;
}
/**
* @return true if the event was served to at least 1 client
*/
final boolean wasServed()
{
return served;
}
/**
* Clear "served" and "consumed" flags before dispatching.
*/
final void clearFlags()
{
served = false;
consumed = false;
}
/**
* Called after all clients have received the event.
*/
public void onDispatchComplete(EventBus bus)
{
}
}

@ -1,400 +0,0 @@
package mightypork.gamecore.eventbus;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
import mightypork.gamecore.eventbus.clients.DelegatingClient;
import mightypork.gamecore.eventbus.event_flags.DelayedEvent;
import mightypork.gamecore.eventbus.event_flags.DirectEvent;
import mightypork.gamecore.eventbus.event_flags.NotLoggedEvent;
import mightypork.gamecore.eventbus.events.Destroyable;
import mightypork.gamecore.logging.Log;
import mightypork.gamecore.util.Utils;
/**
* An event bus, accommodating multiple EventChannels.<br>
* Channel will be created when an event of type is first encountered.
*
* @author MightyPork
*/
final public class EventBus implements Destroyable, BusAccess {
/**
* Queued event holder
*/
private class DelayQueueEntry implements Delayed {
private final long due;
private final BusEvent<?> evt;
public DelayQueueEntry(double seconds, BusEvent<?> event)
{
super();
this.due = System.currentTimeMillis() + (long) (seconds * 1000);
this.evt = event;
}
@Override
public int compareTo(Delayed o)
{
return Long.valueOf(getDelay(TimeUnit.MILLISECONDS)).compareTo(o.getDelay(TimeUnit.MILLISECONDS));
}
@Override
public long getDelay(TimeUnit unit)
{
return unit.convert(due - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
}
public BusEvent<?> getEvent()
{
return evt;
}
}
/**
* Thread handling queued events
*/
private class QueuePollingThread extends Thread {
public volatile boolean stopped = false;
public QueuePollingThread()
{
super("Queue Polling Thread");
}
@Override
public void run()
{
DelayQueueEntry evt;
while (!stopped) {
evt = null;
try {
evt = sendQueue.take();
} catch (final InterruptedException ignored) {
//
}
if (evt != null) {
try {
dispatch(evt.getEvent());
} catch (final Throwable t) {
Log.w(logMark + "Error while dispatching event: ", t);
}
}
}
}
}
static final String logMark = "(bus) ";
private static Class<?> getEventListenerClass(BusEvent<?> event)
{
// BEHOLD, MAGIC!
final Type evtc = event.getClass().getGenericSuperclass();
if (evtc instanceof ParameterizedType) {
if (((ParameterizedType) evtc).getRawType() == BusEvent.class) {
final Type[] types = ((ParameterizedType) evtc).getActualTypeArguments();
for (final Type genericType : types) {
return (Class<?>) genericType;
}
}
}
throw new RuntimeException("Could not detect event listener type.");
}
/** Log detailed messages (debug) */
public boolean detailedLogging = false;
/** Queue polling thread */
private final QueuePollingThread busThread;
/** Registered clients */
private final Set<Object> clients = Collections.newSetFromMap(new ConcurrentHashMap<Object,Boolean>());
/** Whether the bus was destroyed */
private boolean dead = false;
/** Message channels */
private final Set<EventChannel<?, ?>> channels = Collections.newSetFromMap(new ConcurrentHashMap<EventChannel<?, ?>,Boolean>());
/** Messages queued for delivery */
private final DelayQueue<DelayQueueEntry> sendQueue = new DelayQueue<>();
/**
* Make a new bus and start it's queue thread.
*/
public EventBus()
{
busThread = new QueuePollingThread();
busThread.setDaemon(true);
busThread.start();
}
/**
* Halt bus thread and reject any future events.
*/
@Override
public void destroy()
{
assertLive();
busThread.stopped = true;
dead = true;
}
/**
* Send based on annotation
*
* @param event event
*/
public void send(BusEvent<?> event)
{
assertLive();
final DelayedEvent adelay = Utils.getAnnotation(event, DelayedEvent.class);
if (adelay != null) {
sendDelayed(event, adelay.delay());
return;
}
if (Utils.hasAnnotation(event, DirectEvent.class)) {
sendDirect(event);
return;
}
sendQueued(event);
}
/**
* Add event to a queue
*
* @param event event
*/
public void sendQueued(BusEvent<?> event)
{
assertLive();
sendDelayed(event, 0);
}
/**
* Add event to a queue, scheduled for given time.
*
* @param event event
* @param delay delay before event is dispatched
*/
public void sendDelayed(BusEvent<?> event, double delay)
{
assertLive();
final DelayQueueEntry dm = new DelayQueueEntry(delay, event);
if (shallLog(event)) {
Log.f3(logMark + "Qu [" + Log.str(event) + "]" + (delay == 0 ? "" : (", delay: " + delay + "s")));
}
sendQueue.add(dm);
}
/**
* Send immediately.<br>
* Should be used for real-time events that require immediate response, such
* as timing events.
*
* @param event event
*/
public void sendDirect(BusEvent<?> event)
{
assertLive();
if (shallLog(event)) Log.f3(logMark + "Di [" + Log.str(event) + "]");
dispatch(event);
}
public void sendDirectToChildren(DelegatingClient delegatingClient, BusEvent<?> event)
{
assertLive();
if (shallLog(event)) Log.f3(logMark + "Di->sub [" + Log.str(event) + "]");
doDispatch(delegatingClient.getChildClients(), event);
}
/**
* Connect a client to the bus. The client will be connected to all current
* and future channels, until removed from the bus.
*
* @param client the client
*/
public void subscribe(Object client)
{
assertLive();
if (client == null) return;
clients.add(client);
if (detailedLogging) Log.f3(logMark + "Client joined: " + Log.str(client));
}
/**
* Disconnect a client from the bus.
*
* @param client the client
*/
public void unsubscribe(Object client)
{
assertLive();
clients.remove(client);
if (detailedLogging) Log.f3(logMark + "Client left: " + Log.str(client));
}
@SuppressWarnings("unchecked")
private boolean addChannelForEvent(BusEvent<?> event)
{
try {
if (detailedLogging) {
Log.f3(logMark + "Setting up channel for new event type: " + Log.str(event.getClass()));
}
final Class<?> listener = getEventListenerClass(event);
final EventChannel<?, ?> ch = EventChannel.create(event.getClass(), listener);
if (ch.canBroadcast(event)) {
channels.add(ch);
//channels.flush();
if (detailedLogging) {
Log.f3(logMark + "Created new channel: " + Log.str(event.getClass()) + " -> " + Log.str(listener));
}
return true;
} else {
Log.w(logMark + "Could not create channel for event " + Log.str(event.getClass()));
}
} catch (final Throwable t) {
Log.w(logMark + "Error while trying to add channel for event.", t);
}
return false;
}
/**
* Make sure the bus is not destroyed.
*
* @throws IllegalStateException if the bus is dead.
*/
private void assertLive() throws IllegalStateException
{
if (dead) throw new IllegalStateException("EventBus is dead.");
}
/**
* Send immediately.<br>
* Should be used for real-time events that require immediate response, such
* as timing events.
*
* @param event event
*/
private synchronized void dispatch(BusEvent<?> event)
{
assertLive();
doDispatch(clients, event);
event.onDispatchComplete(this);
}
/**
* Send to a set of clients
*
* @param clients clients
* @param event event
*/
private synchronized void doDispatch(Collection<?> clients, BusEvent<?> event)
{
boolean accepted = false;
event.clearFlags();
for (int i = 0; i < 2; i++) { // two tries.
for (final EventChannel<?, ?> b : channels) {
if (b.canBroadcast(event)) {
accepted = true;
b.broadcast(event, clients);
}
if (event.isConsumed()) break;
}
if (!accepted) if (addChannelForEvent(event)) continue;
break;
}
if (!accepted) Log.e(logMark + "Not accepted by any channel: " + Log.str(event));
if (!event.wasServed() && shallLog(event)) Log.w(logMark + "Not delivered: " + Log.str(event));
}
private boolean shallLog(BusEvent<?> event)
{
if (!detailedLogging) return false;
if (Utils.hasAnnotation(event, NotLoggedEvent.class)) return false;
return true;
}
@Override
public EventBus getEventBus()
{
return this; // just for compatibility use-case
}
}

@ -1,207 +0,0 @@
package mightypork.gamecore.eventbus;
import java.util.Collection;
import java.util.HashSet;
import mightypork.gamecore.eventbus.clients.DelegatingClient;
import mightypork.gamecore.eventbus.clients.ToggleableClient;
import mightypork.gamecore.eventbus.event_flags.NonRejectableEvent;
import mightypork.gamecore.logging.Log;
import mightypork.gamecore.util.Utils;
/**
* Event delivery channel, module of {@link EventBus}
*
* @author MightyPork
* @param <EVENT> event type
* @param <CLIENT> client (subscriber) type
*/
class EventChannel<EVENT extends BusEvent<CLIENT>, CLIENT> {
private final Class<CLIENT> clientClass;
private final Class<EVENT> eventClass;
/**
* Create a channel
*
* @param eventClass event class
* @param clientClass client class
*/
public EventChannel(Class<EVENT> eventClass, Class<CLIENT> clientClass)
{
if (eventClass == null || clientClass == null) {
throw new NullPointerException("Null Event or Client class.");
}
this.clientClass = clientClass;
this.eventClass = eventClass;
}
/**
* Try to broadcast a event.<br>
* If event is of wrong type, <code>false</code> is returned.
*
* @param event a event to be sent
* @param clients collection of clients
*/
public void broadcast(BusEvent<?> event, Collection<?> clients)
{
if (!canBroadcast(event)) return;
doBroadcast(eventClass.cast(event), clients, new HashSet<>());
}
/**
* Send the event
*
* @param event sent event
* @param clients subscribing clients
* @param processed clients already processed
*/
private void doBroadcast(final EVENT event, final Collection<?> clients, final Collection<Object> processed)
{
for (final Object client : clients) {
// exclude obvious non-clients
if (!isClientValid(client)) {
continue;
}
// avoid executing more times
if (processed.contains(client)) {
Log.w(EventBus.logMark + "Client already served: " + Log.str(client));
continue;
}
processed.add(client);
final boolean must_deliver = Utils.hasAnnotation(event, NonRejectableEvent.class);
// opt-out
if (client instanceof ToggleableClient) {
if (!must_deliver && !((ToggleableClient) client).isListening()) continue;
}
sendTo(client, event);
if (event.isConsumed()) return;
// pass on to delegated clients
if (client instanceof DelegatingClient) {
if (must_deliver || ((DelegatingClient) client).doesDelegate()) {
final Collection<?> children = ((DelegatingClient) client).getChildClients();
if (children != null && children.size() > 0) {
doBroadcast(event, children, processed);
}
}
}
}
}
/**
* Send an event to a client.
*
* @param client target client
* @param event event to send
*/
@SuppressWarnings("unchecked")
private void sendTo(Object client, EVENT event)
{
if (isClientOfChannelType(client)) {
((BusEvent<CLIENT>) event).deliverTo((CLIENT) client);
}
}
/**
* Check if the given event can be broadcasted by this channel
*
* @param event event object
* @return can be broadcasted
*/
public boolean canBroadcast(BusEvent<?> event)
{
return event != null && eventClass.isInstance(event);
}
/**
* Create an instance for given types
*
* @param eventClass event class
* @param clientClass client class
* @return the broadcaster
*/
public static <F_EVENT extends BusEvent<F_CLIENT>, F_CLIENT> EventChannel<F_EVENT, F_CLIENT> create(Class<F_EVENT> eventClass, Class<F_CLIENT> clientClass)
{
return new EventChannel<>(eventClass, clientClass);
}
/**
* Check if client is of channel type
*
* @param client client
* @return is of type
*/
private boolean isClientOfChannelType(Object client)
{
return clientClass.isInstance(client);
}
/**
* Check if the channel is compatible with given
*
* @param client client
* @return is supported
*/
public boolean isClientValid(Object client)
{
return isClientOfChannelType(client) || (client instanceof DelegatingClient);
}
@Override
public int hashCode()
{
final int prime = 13;
int result = 1;
result = prime * result + ((clientClass == null) ? 0 : clientClass.hashCode());
result = prime * result + ((eventClass == null) ? 0 : eventClass.hashCode());
return result;
}
@Override
public boolean equals(Object obj)
{
if (this == obj) return true;
if (obj == null) return false;
if (!(obj instanceof EventChannel)) return false;
final EventChannel<?, ?> other = (EventChannel<?, ?>) obj;
if (clientClass == null) {
if (other.clientClass != null) return false;
} else if (!clientClass.equals(other.clientClass)) return false;
if (eventClass == null) {
if (other.eventClass != null) return false;
} else if (!eventClass.equals(other.eventClass)) return false;
return true;
}
@Override
public String toString()
{
return "{ " + Log.str(eventClass) + " => " + Log.str(clientClass) + " }";
}
}

@ -1,115 +0,0 @@
package mightypork.gamecore.eventbus.clients;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Set;
import mightypork.gamecore.eventbus.BusAccess;
import mightypork.gamecore.eventbus.EventBus;
/**
* Client that can be attached to the {@link EventBus}, or added as a child
* client to another {@link DelegatingClient}
*
* @author MightyPork
*/
public abstract class BusNode implements BusAccess, ClientHub {
private final BusAccess busAccess;
private final Set<Object> clients = new LinkedHashSet<>();
private boolean listening = true;
private boolean delegating = true;
/**
* @param busAccess access to bus
*/
public BusNode(BusAccess busAccess)
{
this.busAccess = busAccess;
}
@Override
public Collection<Object> getChildClients()
{
return clients;
}
@Override
public boolean doesDelegate()
{
return delegating;
}
@Override
public boolean isListening()
{
return listening;
}
/**
* Add a child subscriber to the {@link EventBus}.<br>
*
* @param client
*/
@Override
public void addChildClient(Object client)
{
if (client instanceof RootBusNode) {
throw new IllegalArgumentException("Cannot nest RootBusNode.");
}
clients.add(client);
}
/**
* Remove a child subscriber
*
* @param client subscriber to remove
*/
@Override
public void removeChildClient(Object client)
{
if (client != null) {
clients.remove(client);
}
}
/**
* Set whether events should be received.
*
* @param listening receive events
*/
public void setListening(boolean listening)
{
this.listening = listening;
}
/**
* Set whether events should be passed on to child nodes
*
* @param delegating
*/
public void setDelegating(boolean delegating)
{
this.delegating = delegating;
}
@Override
public EventBus getEventBus()
{
return busAccess.getEventBus();
}
}

@ -1,42 +0,0 @@
package mightypork.gamecore.eventbus.clients;
import java.util.Collection;
import mightypork.gamecore.eventbus.EventBus;
/**
* Common methods for client hubs (ie delegating vlient implementations)
*
* @author MightyPork
*/
public interface ClientHub extends DelegatingClient, ToggleableClient {
@Override
public boolean doesDelegate();
@Override
public Collection<Object> getChildClients();
@Override
public boolean isListening();
/**
* Add a child subscriber to the {@link EventBus}.<br>
*
* @param client
*/
public void addChildClient(Object client);
/**
* Remove a child subscriber
*
* @param client subscriber to remove
*/
void removeChildClient(Object client);
}

@ -1,21 +0,0 @@
package mightypork.gamecore.eventbus.clients;
import java.util.ArrayList;
/**
* Array-list with varargs constructor
*
* @author MightyPork
*/
public class ClientList extends ArrayList<Object> {
public ClientList(Object... clients)
{
for (final Object c : clients) {
super.add(c);
}
}
}

@ -1,27 +0,0 @@
package mightypork.gamecore.eventbus.clients;
import java.util.Collection;
/**
* Client containing child clients. According to the contract, if the collection
* of clients is ordered, the clients will be served in that order. In any case,
* the {@link DelegatingClient} itself will be served beforehand.
*
* @author MightyPork
*/
public interface DelegatingClient {
/**
* @return collection of child clients. Can not be null.
*/
public Collection<?> getChildClients();
/**
* @return true if delegating is active
*/
public boolean doesDelegate();
}

@ -1,51 +0,0 @@
package mightypork.gamecore.eventbus.clients;
import java.util.Collection;
import mightypork.gamecore.gui.Enableable;
/**
* Basic delegating client
*
* @author MightyPork
*/
public class DelegatingList extends ClientList implements DelegatingClient, Enableable {
private boolean enabled = true;
public DelegatingList(Object... clients)
{
super(clients);
}
@Override
public Collection<?> getChildClients()
{
return this;
}
@Override
public boolean doesDelegate()
{
return isEnabled();
}
@Override
public void setEnabled(boolean yes)
{
enabled = yes;
}
@Override
public boolean isEnabled()
{
return enabled;
}
}

@ -1,45 +0,0 @@
package mightypork.gamecore.eventbus.clients;
import mightypork.gamecore.eventbus.BusAccess;
import mightypork.gamecore.eventbus.events.Destroyable;
import mightypork.gamecore.util.annot.DefaultImpl;
/**
* Bus node that should be directly attached to the bus.
*
* @author MightyPork
*/
public abstract class RootBusNode extends BusNode implements Destroyable {
/**
* @param busAccess access to bus
*/
public RootBusNode(BusAccess busAccess)
{
super(busAccess);
getEventBus().subscribe(this);
}
@Override
public final void destroy()
{
deinit();
getEventBus().unsubscribe(this);
}
/**
* Deinitialize the subsystem<br>
* (called during destruction)
*/
@DefaultImpl
protected void deinit()
{
}
}

@ -1,16 +0,0 @@
package mightypork.gamecore.eventbus.clients;
/**
* Client that can toggle receiving messages.
*
* @author MightyPork
*/
public interface ToggleableClient {
/**
* @return true if the client wants to receive messages
*/
public boolean isListening();
}

@ -1,22 +0,0 @@
package mightypork.gamecore.eventbus.event_flags;
import java.lang.annotation.*;
/**
* Event that should be queued with given delay (default: 0);
*
* @author MightyPork
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
@Documented
public @interface DelayedEvent {
/**
* @return event dispatch delay [seconds]
*/
double delay() default 0;
}

@ -1,16 +0,0 @@
package mightypork.gamecore.eventbus.event_flags;
import java.lang.annotation.*;
/**
* Event that should not be queued.
*
* @author MightyPork
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
@Documented
public @interface DirectEvent {}

@ -1,21 +0,0 @@
package mightypork.gamecore.eventbus.event_flags;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Event that cannot be consumed
*
* @author MightyPork
*/
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.TYPE)
public @interface NonConsumableEvent {
}

@ -1,21 +0,0 @@
package mightypork.gamecore.eventbus.event_flags;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Event that is forcibly delivered to all clients (bypass Toggleable etc)
*
* @author MightyPork
*/
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.TYPE)
public @interface NonRejectableEvent {
}

@ -1,17 +0,0 @@
package mightypork.gamecore.eventbus.event_flags;
import java.lang.annotation.*;
/**
* Event that's not worth logging, unless there was an error with it.<br>
* Useful for common events that would otherwise clutter the log.
*
* @author MightyPork
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
@Documented
public @interface NotLoggedEvent {}

@ -1,16 +0,0 @@
package mightypork.gamecore.eventbus.event_flags;
import java.lang.annotation.*;
/**
* Handled only by the first client, then discarded.
*
* @author MightyPork
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
@Documented
public @interface SingleReceiverEvent {}

@ -1,24 +0,0 @@
package mightypork.gamecore.eventbus.events;
import mightypork.gamecore.eventbus.BusEvent;
import mightypork.gamecore.eventbus.event_flags.DirectEvent;
import mightypork.gamecore.eventbus.event_flags.NonConsumableEvent;
/**
* Invoke destroy() method of all subscribers. Used to deinit a system.
*
* @author MightyPork
*/
@DirectEvent
@NonConsumableEvent
public class DestroyEvent extends BusEvent<Destroyable> {
@Override
public void handleBy(Destroyable handler)
{
handler.destroy();
}
}

@ -1,15 +0,0 @@
package mightypork.gamecore.eventbus.events;
/**
* Object that can be destroyed (free resources etc)
*
* @author MightyPork
*/
public interface Destroyable {
/**
* Destroy this object
*/
public void destroy();
}

@ -1,37 +0,0 @@
package mightypork.gamecore.eventbus.events;
import mightypork.gamecore.eventbus.BusEvent;
import mightypork.gamecore.eventbus.event_flags.DirectEvent;
import mightypork.gamecore.eventbus.event_flags.NonConsumableEvent;
import mightypork.gamecore.eventbus.event_flags.NotLoggedEvent;
/**
* Delta timing update event. Not logged.
*
* @author MightyPork
*/
@NotLoggedEvent
@DirectEvent
@NonConsumableEvent
public class UpdateEvent extends BusEvent<Updateable> {
private final double deltaTime;
/**
* @param deltaTime time since last update (sec)
*/
public UpdateEvent(double deltaTime)
{
this.deltaTime = deltaTime;
}
@Override
public void handleBy(Updateable handler)
{
handler.update(deltaTime);
}
}

@ -1,17 +0,0 @@
package mightypork.gamecore.eventbus.events;
/**
* Uses delta timing
*
* @author MightyPork
*/
public interface Updateable {
/**
* Update item state based on elapsed time
*
* @param delta time elapsed since last update, in seconds
*/
public void update(double delta);
}

@ -1,51 +0,0 @@
package mightypork.gamecore.gui;
/**
* Triggered action
*
* @author MightyPork
*/
public abstract class Action implements Runnable, Enableable {
private boolean enabled = true;
/**
* Enable the action
*
* @param enable true to enable
*/
@Override
public final void setEnabled(boolean enable)
{
this.enabled = enable;
}
/**
* @return true if this action is enabled.
*/
@Override
public final boolean isEnabled()
{
return enabled;
}
/**
* Run the action, if it's enabled.
*/
@Override
public final void run()
{
if (enabled) execute();
}
/**
* Do the work.
*/
protected abstract void execute();
}

@ -1,36 +0,0 @@
package mightypork.gamecore.gui;
import java.util.HashSet;
import java.util.Set;
public class ActionGroup implements Enableable {
private boolean enabled = true;
private final Set<Enableable> groupMembers = new HashSet<>();
@Override
public void setEnabled(boolean yes)
{
enabled = yes;
for (final Enableable e : groupMembers)
e.setEnabled(yes);
}
@Override
public boolean isEnabled()
{
return enabled;
}
public void add(Enableable action)
{
groupMembers.add(action);
}
}

@ -1,17 +0,0 @@
package mightypork.gamecore.gui;
/**
* Element that can be assigned an action (ie. button);
*
* @author MightyPork
*/
public interface ActionTrigger {
/**
* Assign an action
*
* @param action action
*/
void setAction(Action action);
}

@ -1,12 +0,0 @@
package mightypork.gamecore.gui;
/**
* Horizontal align sides
*
* @author MightyPork
*/
public enum AlignX
{
LEFT, CENTER, RIGHT;
}

@ -1,12 +0,0 @@
package mightypork.gamecore.gui;
/**
* Vertical align sides
*
* @author MightyPork
*/
public enum AlignY
{
TOP, CENTER, BOTTOM;
}

@ -1,25 +0,0 @@
package mightypork.gamecore.gui;
/**
* Can be enabled or disabled.<br>
* Implementations should take appropriate action (ie. stop listening to events,
* updating etc.)
*
* @author MightyPork
*/
public interface Enableable {
/**
* Change enabled state
*
* @param yes enabled
*/
public void setEnabled(boolean yes);
/**
* @return true if enabled
*/
public boolean isEnabled();
}

@ -1,15 +0,0 @@
package mightypork.gamecore.gui;
/**
* Element that can be hidden or visible
*
* @author MightyPork
*/
public interface Hideable {
void setVisible(boolean yes);
boolean isVisible();
}

@ -1,169 +0,0 @@
package mightypork.gamecore.gui.components;
import mightypork.gamecore.gui.Enableable;
import mightypork.gamecore.gui.events.LayoutChangeEvent;
import mightypork.gamecore.gui.events.LayoutChangeListener;
import mightypork.gamecore.input.InputSystem;
import mightypork.gamecore.logging.Log;
import mightypork.gamecore.render.Renderable;
import mightypork.gamecore.util.annot.DefaultImpl;
import mightypork.gamecore.util.math.color.Color;
import mightypork.gamecore.util.math.constraints.num.Num;
import mightypork.gamecore.util.math.constraints.rect.Rect;
import mightypork.gamecore.util.math.constraints.rect.caching.AbstractRectCache;
import mightypork.gamecore.util.math.constraints.rect.proxy.RectBound;
import mightypork.gamecore.util.math.constraints.rect.proxy.RectBoundAdapter;
/**
* {@link Renderable} with pluggable context. When caching is enabled, the
* layout update can be triggered by firing the {@link LayoutChangeEvent}.
*
* @author MightyPork
*/
public abstract class BaseComponent extends AbstractRectCache implements Component, LayoutChangeListener, Enableable {
private Rect source;
private boolean visible = true;
private boolean enabled = true;
private int indirectDisableLevel = 0;
private Num alphaMul = Num.ONE;
public BaseComponent()
{
enableCaching(false);
}
@Override
public void setRect(RectBound rect)
{
this.source = new RectBoundAdapter(rect);
}
@Override
public final boolean isVisible()
{
return visible;
}
@Override
public final void setVisible(boolean visible)
{
this.visible = visible;
}
@Override
public final Rect getCacheSource()
{
return source.round(); // round to avoid visual artifacts in fonts and such
}
@Override
public final void render()
{
if (!isVisible()) return;
Color.pushAlpha(alphaMul);
renderComponent();
Color.popAlpha();
}
@Override
public final void onLayoutChanged()
{
try {
poll();
} catch (final NullPointerException e) {
Log.e("Component is missing a bounding rect, at: " + Log.str(getClass()));
}
}
@Override
public final void onConstraintChanged()
{
updateLayout();
}
@Override
public final boolean isMouseOver()
{
return InputSystem.getMousePos().isInside(this);
}
/**
* Draw the component (it's visible)
*/
protected abstract void renderComponent();
@Override
@DefaultImpl
public void updateLayout()
{
}
@Override
public void setEnabled(boolean yes)
{
enabled = yes;
}
@Override
public boolean isEnabled()
{
return enabled && isIndirectlyEnabled();
}
@Override
public final void setAlpha(Num alpha)
{
this.alphaMul = alpha;
}
@Override
public final void setAlpha(double alpha)
{
this.alphaMul = Num.make(alpha);
}
@Override
public void setIndirectlyEnabled(boolean yes)
{
if (!yes) {
indirectDisableLevel++;
} else {
if (indirectDisableLevel > 0) indirectDisableLevel--;
}
}
@Override
public boolean isIndirectlyEnabled()
{
return indirectDisableLevel == 0;
}
@Override
public boolean isDirectlyEnabled()
{
return enabled;
}
}

@ -1,95 +0,0 @@
package mightypork.gamecore.gui.components;
import mightypork.gamecore.gui.Enableable;
import mightypork.gamecore.gui.Hideable;
import mightypork.gamecore.util.math.constraints.num.Num;
/**
* Basic UI component interface
*
* @author MightyPork
*/
public interface Component extends Enableable, Hideable, PluggableRenderable {
/**
* Render the component, if it is visible.
*/
@Override
void render();
/**
* The bounding rect was changed. The component should now update any cached
* constraints derived from it.
*/
void updateLayout();
/**
* @return true if mouse is currently over the component
*/
boolean isMouseOver();
/**
* Set alpha multiplier for this and nested components
*
* @param alpha alpha multiplier (dynamic value)
*/
void setAlpha(Num alpha);
/**
* Set alpha multiplier for this and nested components
*
* @param alpha alpha multiplier (constant value)
*/
void setAlpha(double alpha);
/**
* Indirectly enable / disable, used for nested hierarchies.<br>
* When component is twice indirectly disabled, it needs to be twice
* indirectly enabled to be enabled again.
*
* @param yes
*/
void setIndirectlyEnabled(boolean yes);
/**
* Check if the compionent is not indirectly disabled. May still be directly
* disabled.
*
* @return indirectly enabled
*/
boolean isIndirectlyEnabled();
/**
* Check if the component is directly enabled (set by setEnabled()). May
* still be indirectly disabled.
*
* @return directly enabled
*/
boolean isDirectlyEnabled();
/**
* Set directly enabled (must be both directly and indirectly enabled to be
* enabled completely)
*/
@Override
public void setEnabled(boolean yes);
/**
* Check if the component is both directly and indirectly enabled
*
* @return enabled
*/
@Override
public boolean isEnabled();
}

@ -1,7 +0,0 @@
package mightypork.gamecore.gui.components;
public interface DynamicWidthComponent extends Component {
double computeWidth(double height);
}

@ -1,15 +0,0 @@
package mightypork.gamecore.gui.components;
import mightypork.gamecore.eventbus.clients.ToggleableClient;
import mightypork.gamecore.gui.Enableable;
public abstract class InputComponent extends BaseComponent implements Enableable, ToggleableClient {
@Override
public boolean isListening()
{
return isEnabled();
}
}

@ -1,162 +0,0 @@
package mightypork.gamecore.gui.components;
import java.util.Collection;
import java.util.LinkedList;
import mightypork.gamecore.core.modules.AppAccess;
import mightypork.gamecore.core.modules.AppSubModule;
import mightypork.gamecore.eventbus.EventBus;
import mightypork.gamecore.eventbus.clients.ClientHub;
import mightypork.gamecore.input.InputSystem;
import mightypork.gamecore.render.DisplaySystem;
import mightypork.gamecore.resources.audio.SoundSystem;
import mightypork.gamecore.util.math.constraints.rect.proxy.RectBound;
public abstract class LayoutComponent extends BaseComponent implements ClientHub, AppAccess {
private final AppSubModule subModule;
final LinkedList<Component> components = new LinkedList<>();
public LayoutComponent(AppAccess app, RectBound context)
{
this.subModule = new AppSubModule(app);
setRect(context);
enableCaching(true); // layout is typically updated only when screen resizes.
}
public LayoutComponent(AppAccess app)
{
this(app, null);
}
@Override
public EventBus getEventBus()
{
return subModule.getEventBus();
}
@Override
public Collection<Object> getChildClients()
{
return subModule.getChildClients();
}
@Override
public boolean doesDelegate()
{
return subModule.doesDelegate();
}
@Override
public boolean isListening()
{
return subModule.isListening();
}
@Override
public SoundSystem getSoundSystem()
{
return subModule.getSoundSystem();
}
@Override
public InputSystem getInput()
{
return subModule.getInput();
}
@Override
public DisplaySystem getDisplay()
{
return subModule.getDisplay();
}
@Override
public void shutdown()
{
subModule.shutdown();
}
@Override
public void addChildClient(Object client)
{
subModule.addChildClient(client);
}
@Override
public void removeChildClient(Object client)
{
subModule.removeChildClient(client);
}
@Override
public void setEnabled(boolean yes)
{
if (isDirectlyEnabled() != yes) {
super.setEnabled(yes);
for (final Component c : components) {
c.setIndirectlyEnabled(yes);
}
}
}
/**
* Connect to bus and add to element list
*
* @param component added component, whose context has already been set.
*/
protected final void attach(Component component)
{
if (component == null) return;
if (component == this) throw new IllegalArgumentException("Uruboros. (infinite recursion evaded)");
components.add(component);
addChildClient(component);
}
@Override
public void renderComponent()
{
for (final Component cmp : components) {
cmp.render();
}
}
@Override
public void updateLayout()
{
for (final Component cmp : components) {
cmp.updateLayout();
}
}
@Override
public void setIndirectlyEnabled(boolean yes)
{
super.setIndirectlyEnabled(yes);
for (final Component cmp : components) {
cmp.setIndirectlyEnabled(yes);
}
}
}

@ -1,78 +0,0 @@
package mightypork.gamecore.gui.components;
import mightypork.gamecore.util.math.constraints.num.Num;
import mightypork.gamecore.util.math.constraints.rect.Rect;
import mightypork.gamecore.util.math.constraints.rect.proxy.RectBound;
import mightypork.gamecore.util.math.constraints.vect.Vect;
import mightypork.gamecore.util.math.constraints.vect.proxy.VectAdapter;
public abstract class LinearComponent extends BaseComponent implements DynamicWidthComponent {
private final Rect rect = new Rect() {
@Override
public Vect size()
{
return new Vect() {
@Override
public double x()
{
return computeWidth(y());
}
@Override
public double y()
{
return height.value();
}
};
}
@Override
public Vect origin()
{
return new VectAdapter() {
@Override
protected Vect getSource()
{
return origin;
}
};
}
};
private Vect origin;
private Num height;
public LinearComponent()
{
super.setRect(rect);
}
@Override
public void setRect(RectBound rect)
{
throw new RuntimeException("Cannot assign a rect to a linear component. Set origin and height instead.");
}
public void setHeight(Num height)
{
this.height = height;
}
public void setOrigin(Vect origin)
{
this.origin = origin;
}
}

@ -1,28 +0,0 @@
package mightypork.gamecore.gui.components;
import mightypork.gamecore.render.Renderable;
import mightypork.gamecore.util.math.constraints.rect.Rect;
import mightypork.gamecore.util.math.constraints.rect.proxy.PluggableRectBound;
import mightypork.gamecore.util.math.constraints.rect.proxy.RectBound;
/**
* Renderable that can be assigned different context
*
* @author MightyPork
*/
public interface PluggableRenderable extends Renderable, PluggableRectBound {
@Override
void render();
@Override
Rect getRect();
@Override
void setRect(RectBound rect);
}

@ -1,49 +0,0 @@
package mightypork.gamecore.gui.components.input;
import mightypork.gamecore.gui.Action;
import mightypork.gamecore.gui.ActionTrigger;
import mightypork.gamecore.gui.components.InputComponent;
import mightypork.gamecore.input.events.MouseButtonEvent;
import mightypork.gamecore.input.events.MouseButtonHandler;
public abstract class ClickableComponent extends InputComponent implements ActionTrigger, MouseButtonHandler {
protected boolean btnDownOver;
private Action action;
@Override
public void setAction(Action action)
{
this.action = action;
}
protected void triggerAction()
{
if (action != null && isEnabled()) action.run();
}
@Override
public void receive(MouseButtonEvent event)
{
if (!event.isButtonEvent()) return;
if (event.isDown()) {
btnDownOver = event.isOver(this);
}
if (event.isUp()) {
if (btnDownOver && event.isOver(this)) {
triggerAction();
event.consume();
}
btnDownOver = false;
}
}
}

@ -1,64 +0,0 @@
package mightypork.gamecore.gui.components.input;
import java.util.Collection;
import mightypork.gamecore.eventbus.clients.ClientList;
import mightypork.gamecore.eventbus.clients.DelegatingClient;
import mightypork.gamecore.gui.components.Component;
public class ClickableWrapper extends ClickableComponent implements DelegatingClient {
private final Component wrapped;
private final ClientList list;
public ClickableWrapper(Component wrapped)
{
this.wrapped = wrapped;
wrapped.setRect(this);
list = new ClientList(wrapped);
}
@Override
public Collection<?> getChildClients()
{
return list;
}
@Override
public boolean doesDelegate()
{
return true;
}
@Override
protected void renderComponent()
{
wrapped.render();
}
@Override
public void setEnabled(boolean yes)
{
if (yes != super.isDirectlyEnabled()) {
super.setEnabled(yes);
wrapped.setIndirectlyEnabled(yes);
}
}
@Override
public void setIndirectlyEnabled(boolean yes)
{
super.setIndirectlyEnabled(yes);
wrapped.setIndirectlyEnabled(yes);
}
}

@ -1,78 +0,0 @@
package mightypork.gamecore.gui.components.input;
import mightypork.gamecore.gui.AlignX;
import mightypork.gamecore.gui.components.DynamicWidthComponent;
import mightypork.gamecore.gui.components.painters.TextPainter;
import mightypork.gamecore.input.InputSystem;
import mightypork.gamecore.resources.fonts.GLFont;
import mightypork.gamecore.util.math.color.Color;
import mightypork.gamecore.util.math.color.pal.RGB;
import mightypork.gamecore.util.math.constraints.vect.Vect;
import mightypork.gamecore.util.math.constraints.vect.mutable.VectVar;
/**
* Menu-like button with shadow and push state
*
* @author MightyPork
*/
public class TextButton extends ClickableComponent implements DynamicWidthComponent {
public final TextPainter textPainter;
private final VectVar offset = Vect.makeVar();
public Vect offsetPassive = height().div(16).toVectXY();
public Vect offsetOver = height().div(20).toVectXY();
public Vect offsetUnder = height().div(32).toVectXY();
private final Color color;
private boolean hoverMove = true;
public TextButton(GLFont font, String text, Color color)
{
this.color = color;
this.textPainter = new TextPainter(font, AlignX.CENTER, this.color, text);
this.textPainter.setRect(this);
this.textPainter.setShadow(RGB.BLACK_30, offset);
textPainter.setVPaddingPercent(5);
}
@Override
protected void renderComponent()
{
if (isMouseOver()) {
if (InputSystem.isMouseButtonDown(0)) {
offset.setTo(offsetUnder);
} else {
offset.setTo(hoverMove ? offsetOver : offsetPassive);
}
} else {
offset.setTo(offsetPassive);
}
textPainter.render();
}
/**
* Disable offset change on hover
*/
public void disableHoverEffect()
{
hoverMove = false;
}
@Override
public double computeWidth(double height)
{
return textPainter.computeWidth(height);
}
}

@ -1,45 +0,0 @@
package mightypork.gamecore.gui.components.layout;
import mightypork.gamecore.core.modules.AppAccess;
import mightypork.gamecore.gui.components.Component;
import mightypork.gamecore.util.math.constraints.rect.proxy.RectBound;
public class ColumnLayout extends GridLayout {
private int col = 0;
public ColumnLayout(AppAccess app, int rows)
{
this(app, null, rows);
}
public ColumnLayout(AppAccess app, RectBound context, int cols)
{
super(app, context, 1, cols);
}
public void add(final Component elem)
{
add(elem, 1);
}
public void add(final Component elem, int colSpan)
{
if (elem == null) return;
put(elem, 0, col, 1, colSpan);
col += colSpan;
}
public void skip(int cols)
{
col += cols;
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save