Source import

master
Ondřej Hruška 11 years ago
parent a694f45af0
commit e56148559c
  1. 7
      .classpath
  2. 18
      .gitignore
  3. 17
      .project
  4. 11
      .settings/org.eclipse.jdt.core.prefs
  5. 2
      README.md
  6. 133
      src/junk/AppInitOptions.java
  7. 184
      src/junk/BaseApp.java
  8. 299
      src/junk/DisplaySystem.java
  9. 542
      src/junk/Render.java
  10. 261
      src/junk/SoundSystem.java
  11. 220
      src/mightypork/gamecore/audio/AudioModule.java
  12. 47
      src/mightypork/gamecore/audio/DeferredAudio.java
  13. 99
      src/mightypork/gamecore/audio/IAudio.java
  14. 51
      src/mightypork/gamecore/audio/JointVolume.java
  15. 83
      src/mightypork/gamecore/audio/SoundRegistry.java
  16. 29
      src/mightypork/gamecore/audio/Volume.java
  17. 105
      src/mightypork/gamecore/audio/players/BaseAudioPlayer.java
  18. 66
      src/mightypork/gamecore/audio/players/EffectPlayer.java
  19. 166
      src/mightypork/gamecore/audio/players/LoopPlayer.java
  20. 277
      src/mightypork/gamecore/core/App.java
  21. 65
      src/mightypork/gamecore/core/AppBackend.java
  22. 34
      src/mightypork/gamecore/core/AppPlugin.java
  23. 24
      src/mightypork/gamecore/core/BackendModule.java
  24. 150
      src/mightypork/gamecore/core/InitTask.java
  25. 130
      src/mightypork/gamecore/core/MainLoop.java
  26. 19
      src/mightypork/gamecore/core/OptionalInitTask.java
  27. 91
      src/mightypork/gamecore/core/WorkDir.java
  28. 269
      src/mightypork/gamecore/core/config/Config.java
  29. 75
      src/mightypork/gamecore/core/config/InitTaskConfig.java
  30. 55
      src/mightypork/gamecore/core/config/KeyStrokeProperty.java
  31. 44
      src/mightypork/gamecore/core/events/MainLoopRequest.java
  32. 47
      src/mightypork/gamecore/core/events/ShutdownEvent.java
  33. 18
      src/mightypork/gamecore/core/events/ShutdownListener.java
  34. 42
      src/mightypork/gamecore/core/init/InitTaskCrashHandler.java
  35. 97
      src/mightypork/gamecore/core/init/InitTaskDisplay.java
  36. 74
      src/mightypork/gamecore/core/init/InitTaskIonizables.java
  37. 108
      src/mightypork/gamecore/core/init/InitTaskLog.java
  38. 54
      src/mightypork/gamecore/core/init/InitTaskLogHeader.java
  39. 136
      src/mightypork/gamecore/core/init/InitTaskWorkdir.java
  40. 68
      src/mightypork/gamecore/core/plugins/screenshot/InitTaskPluginScreenshot.java
  41. 34
      src/mightypork/gamecore/core/plugins/screenshot/ScreenshotPlugin.java
  42. 22
      src/mightypork/gamecore/core/plugins/screenshot/ScreenshotRequest.java
  43. 100
      src/mightypork/gamecore/core/plugins/screenshot/TaskTakeScreenshot.java
  44. 16
      src/mightypork/gamecore/graphics/FullscreenToggleRequest.java
  45. 406
      src/mightypork/gamecore/graphics/GraphicsModule.java
  46. 16
      src/mightypork/gamecore/graphics/Renderable.java
  47. 34
      src/mightypork/gamecore/graphics/Screenshot.java
  48. 98
      src/mightypork/gamecore/graphics/fonts/DeferredFont.java
  49. 79
      src/mightypork/gamecore/graphics/fonts/FontRegistry.java
  50. 213
      src/mightypork/gamecore/graphics/fonts/FontRenderer.java
  51. 23
      src/mightypork/gamecore/graphics/fonts/Glyphs.java
  52. 76
      src/mightypork/gamecore/graphics/fonts/IFont.java
  53. 57
      src/mightypork/gamecore/graphics/textures/DeferredTexture.java
  54. 12
      src/mightypork/gamecore/graphics/textures/FilterMode.java
  55. 64
      src/mightypork/gamecore/graphics/textures/ITexture.java
  56. 92
      src/mightypork/gamecore/graphics/textures/QuadGrid.java
  57. 143
      src/mightypork/gamecore/graphics/textures/TextureRegistry.java
  58. 164
      src/mightypork/gamecore/graphics/textures/TxQuad.java
  59. 121
      src/mightypork/gamecore/graphics/textures/TxSheet.java
  60. 12
      src/mightypork/gamecore/graphics/textures/WrapMode.java
  61. 54
      src/mightypork/gamecore/gui/Action.java
  62. 38
      src/mightypork/gamecore/gui/ActionGroup.java
  63. 17
      src/mightypork/gamecore/gui/ActionTrigger.java
  64. 169
      src/mightypork/gamecore/gui/components/BaseComponent.java
  65. 95
      src/mightypork/gamecore/gui/components/Component.java
  66. 7
      src/mightypork/gamecore/gui/components/DynamicWidthComponent.java
  67. 15
      src/mightypork/gamecore/gui/components/InputComponent.java
  68. 122
      src/mightypork/gamecore/gui/components/LayoutComponent.java
  69. 77
      src/mightypork/gamecore/gui/components/LinearComponent.java
  70. 28
      src/mightypork/gamecore/gui/components/PluggableRenderable.java
  71. 49
      src/mightypork/gamecore/gui/components/input/ClickableComponent.java
  72. 63
      src/mightypork/gamecore/gui/components/input/ClickableWrapper.java
  73. 77
      src/mightypork/gamecore/gui/components/input/TextButton.java
  74. 42
      src/mightypork/gamecore/gui/components/layout/ColumnLayout.java
  75. 36
      src/mightypork/gamecore/gui/components/layout/ConstraintLayout.java
  76. 85
      src/mightypork/gamecore/gui/components/layout/FlowColumnLayout.java
  77. 84
      src/mightypork/gamecore/gui/components/layout/FlowRowLayout.java
  78. 78
      src/mightypork/gamecore/gui/components/layout/GridLayout.java
  79. 18
      src/mightypork/gamecore/gui/components/layout/NullComponent.java
  80. 42
      src/mightypork/gamecore/gui/components/layout/RowLayout.java
  81. 78
      src/mightypork/gamecore/gui/components/layout/linear/AbstractLinearWrapper.java
  82. 24
      src/mightypork/gamecore/gui/components/layout/linear/LinearGap.java
  83. 93
      src/mightypork/gamecore/gui/components/layout/linear/LinearLayout.java
  84. 31
      src/mightypork/gamecore/gui/components/layout/linear/LinearRectangle.java
  85. 20
      src/mightypork/gamecore/gui/components/layout/linear/LinearSquare.java
  86. 20
      src/mightypork/gamecore/gui/components/layout/linear/LinearWrapper.java
  87. 46
      src/mightypork/gamecore/gui/components/painters/ImagePainter.java
  88. 62
      src/mightypork/gamecore/gui/components/painters/QuadPainter.java
  89. 163
      src/mightypork/gamecore/gui/components/painters/TextPainter.java
  90. 30
      src/mightypork/gamecore/gui/events/LayoutChangeEvent.java
  91. 7
      src/mightypork/gamecore/gui/events/LayoutChangeListener.java
  92. 33
      src/mightypork/gamecore/gui/events/ScreenRequest.java
  93. 15
      src/mightypork/gamecore/gui/events/ScreenRequestListener.java
  94. 44
      src/mightypork/gamecore/gui/events/ViewportChangeEvent.java
  95. 17
      src/mightypork/gamecore/gui/events/ViewportChangeListener.java
  96. 112
      src/mightypork/gamecore/gui/screens/LayeredScreen.java
  97. 222
      src/mightypork/gamecore/gui/screens/Overlay.java
  98. 153
      src/mightypork/gamecore/gui/screens/Screen.java
  99. 50
      src/mightypork/gamecore/gui/screens/ScreenLayer.java
  100. 106
      src/mightypork/gamecore/gui/screens/ScreenRegistry.java
  101. Some files were not shown because too many files have changed in this diff Show More

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-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 combineaccessrules="false" kind="src" path="/MightyUtils"/>
<classpathentry kind="output" path="bin"/>
</classpath>

18
.gitignore vendored

@ -1,12 +1,6 @@
*.class /bin/
/target/
# Mobile Tools for Java (J2ME) /~local/
.mtj.tmp/ *.log
.attach_pid*
# Package Files # *~
*.jar
*.war
*.ear
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>GameCore</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

@ -0,0 +1,11 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=1.7
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.source=1.7

@ -2,3 +2,5 @@ gamecore
======== ========
Versatile Java game engine with pluggable backends Versatile Java game engine with pluggable backends
TODO: better readme and tutorial games

@ -0,0 +1,133 @@
package junk;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import mightypork.gamecore.core.AppBackend;
import mightypork.gamecore.resources.ResourceSetup;
import mightypork.gamecore.resources.loading.AsyncResourceLoader;
import mightypork.gamecore.resources.loading.ResourceLoader;
/**
* Init options holder class
*/
public class AppInitOptions {
String logDir = "log";
String logFilePrefix = "runtime";
String screenshotDir = "screenshots";
boolean busLogging = false;
String configFile = "settings.cfg";
String configComment = "Main config file";
final List<ResourceSetup> resourceLists = new ArrayList<>();
final List<KeySetup> keyLists = new ArrayList<>();
final List<ConfigSetup> configLists = new ArrayList<>();
ResourceLoader resourceLoader = new AsyncResourceLoader();
Level logLevel = Level.ALL;
public boolean sigleInstance = true;
Level logSoutLevel = Level.ALL;
public void setConfigFile(String filename, String comment)
{
configFile = filename;
configComment = comment;
}
public void addConfig(ConfigSetup cfg)
{
configLists.add(cfg);
}
public void addKeys(KeySetup keys)
{
keyLists.add(keys);
}
public void addResources(ResourceSetup res)
{
resourceLists.add(res);
}
public void setBackend(AppBackend backend)
{
this.backend = backend;
}
/**
* Set whether to run in single instance mode, or allow multiple instances.<br>
* Multiple instances running can cause various collisions (eg. when writing
* config file or logging).
*
* @param sigleInstance true to allow only one instance
*/
public void setSigleInstance(boolean sigleInstance)
{
this.sigleInstance = sigleInstance;
}
/**
* Set working directory path. If not exists, it will be created.
*
* @param workdir work dir path
*/
public void setWorkdir(File workdir)
{
this.workdir = workdir;
}
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;
}
}

@ -0,0 +1,184 @@
package junk;
import java.lang.Thread.UncaughtExceptionHandler;
import mightypork.gamecore.backends.lwjgl.LwjglInputModule;
import mightypork.gamecore.core.App;
import mightypork.gamecore.core.AppBackend;
import mightypork.gamecore.core.MainLoop;
import mightypork.gamecore.core.WorkDir;
import mightypork.gamecore.core.config.Config;
import mightypork.gamecore.gui.screens.ScreenRegistry;
import mightypork.gamecore.gui.screens.impl.CrossfadeOverlay;
import mightypork.gamecore.resources.Res;
import mightypork.gamecore.resources.ResourceSetup;
import mightypork.utils.logging.Log;
/**
* Basic screen-based game with subsystems.<br>
* This class takes care of the initialization sequence.
*
* @author Ondřej Hruška (MightyPork)
*/
public abstract class BaseApp extends App implements UncaughtExceptionHandler {
// modules
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 getInitOptions()
{
if (started) {
throw new IllegalStateException("Cannot alter init options after starting the App.");
}
return opt;
}
public BaseApp(AppBackend backend) {
super(backend);
}
/**
* Start the application
*/
@Override
public final void start()
{
initialize();
Log.i("Starting main loop...");
// open first screen !!!
started = true;
gameLoop.start();
}
/**
* Init the app
*/
protected void initialize()
{
WorkDir.init(opt.workdir);
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();
/*
* Display
*/
Log.f2("Initializing Display System...");
initDisplay(gfx());
/*
* Audio
*/
Log.f2("Initializing Sound System...");
soundSystem = new SoundSystem(this);
initSoundSystem(soundSystem);
/*
* Input
*/
Log.f2("Initializing Input System...");
inputSystem = new LwjglInputModule(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);
}
/**
* 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);
}
protected void beforeShutdown()
{
// ???
if (lockObtained) Config.save();
}
}

@ -0,0 +1,299 @@
//package junk;
//
//
//import static org.lwjgl.opengl.GL11.*;
//
//import java.nio.ByteBuffer;
//
//import mightypork.gamecore.backend.lwjgl.AwtScreenshot;
//import mightypork.gamecore.core.modules.AppAccess;
//import mightypork.gamecore.core.modules.AppModule;
//import mightypork.gamecore.render.events.DisplayReadyEvent;
//import mightypork.gamecore.render.events.ViewportChangeEvent;
//import mightypork.utils.logging.Log;
//import mightypork.utils.math.constraints.rect.Rect;
//import mightypork.utils.math.constraints.rect.RectBound;
//import mightypork.utils.math.constraints.vect.Vect;
//import mightypork.utils.math.timing.FpsMeter;
//
//import org.lwjgl.BufferUtils;
//import org.lwjgl.LWJGLException;
//import org.lwjgl.opengl.Display;
//import org.lwjgl.opengl.DisplayMode;
//
//
///**
// * Display system
// *
// * @author Ondřej Hruška (MightyPork)
// */
//@Deprecated
//public class DisplaySystem extends AppModule implements RectBound {
//
// private DisplayMode windowDisplayMode;
// private int targetFps;
// private FpsMeter fpsMeter;
// private boolean fullscreenSwitchRequested;
//
// /** Current screen size */
// private static final Vect screenSize = new Vect() {
//
// @Override
// public double y()
// {
// return Display.getHeight();
// }
//
//
// @Override
// public double x()
// {
// return Display.getWidth();
// }
// };
//
// private static final Rect rect = Rect.make(screenSize);
//
//
// /**
// * @param app app access
// */
// public DisplaySystem(AppAccess app) {
// super(app);
// }
//
//
// @Override
// protected void deinit()
// {
// Display.destroy();
// }
//
//
// /**
// * Set target fps (for syncing in endFrame() call).<br>
// * With vsync enabled, the target fps may not be met.
// *
// * @param fps requested fps
// */
// public void setTargetFps(int fps)
// {
// this.targetFps = fps;
// }
//
//
// /**
// * Create a main window
// *
// * @param width requested width
// * @param height requested height
// * @param resizable is resizable by the user
// * @param fullscreen is in fullscreen
// * @param title window title
// */
// public void createMainWindow(int width, int height, boolean resizable, boolean fullscreen, String title)
// {
// try {
// Display.setDisplayMode(windowDisplayMode = new DisplayMode(width, height));
// Display.setResizable(resizable);
// Display.setVSyncEnabled(true);
// Display.setTitle(title);
// Display.create();
//
// fpsMeter = new FpsMeter();
//
// if (fullscreen) {
// switchFullscreen();
// Display.update();
// }
//
// getEventBus().send(new DisplayReadyEvent());
//
// } catch (final LWJGLException e) {
// throw new RuntimeException("Could not initialize screen", e);
// }
// }
//
//
// /**
// * Toggle FS if possible
// */
// public void switchFullscreen()
// {
// fullscreenSwitchRequested = true;
// }
//
//
// private void doSwitchFullscreen()
// {
// try {
//
// if (!Display.isFullscreen()) {
// Log.f3("Entering fullscreen.");
// // save window resize
// windowDisplayMode = new DisplayMode(Display.getWidth(), Display.getHeight());
//
// Display.setDisplayMode(Display.getDesktopDisplayMode());
// Display.setFullscreen(true);
// Display.update();
// } else {
// Log.f3("Leaving fullscreen.");
// Display.setDisplayMode(windowDisplayMode);
// Display.update();
// }
//
// getEventBus().send(new ViewportChangeEvent(getSize()));
//
// } catch (final Throwable t) {
// Log.e("Failed to toggle fullscreen mode.", t);
// try {
// Display.setDisplayMode(windowDisplayMode);
// Display.update();
// } catch (final Throwable t1) {
// throw new RuntimeException("Failed to revert failed fullscreen toggle.", t1);
// }
// }
// }
//
//
// /**
// * Take screenshot (expensive processing is done on-demand when screenshot
// * is processed).
// *
// * @return screenshot object
// */
// public static AwtScreenshot prepareScreenshot()
// {
// glReadBuffer(GL_FRONT);
// final int width = Display.getWidth();
// final int height = Display.getHeight();
// final int bpp = 4;
// final ByteBuffer buffer = BufferUtils.createByteBuffer(width * height * bpp);
// glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
//
// final AwtScreenshot sc = new AwtScreenshot(width, height, bpp, buffer);
//
// return sc;
// }
//
//
// /**
// * @return true if close was requested (i.e. click on cross)
// */
// public static boolean isCloseRequested()
// {
// return Display.isCloseRequested();
// }
//
//
// /**
// * Get fullscreen state
// *
// * @return is fullscreen
// */
// public static boolean isFullscreen()
// {
// return Display.isFullscreen();
// }
//
//
// /**
// * Get screen size. This Vect is final and views at it can safely be made.
// *
// * @return size
// */
// public static Vect getSize()
// {
// return screenSize;
// }
//
//
// /**
// * Get screen rect. Static version of getRect().
// *
// * @return size
// */
// public static Rect getBounds()
// {
// return rect;
// }
//
//
// /**
// * @return screen width
// */
// public static int getWidth()
// {
// return screenSize.xi();
// }
//
//
// /**
// * @return screen height
// */
// public static int getHeight()
// {
// return screenSize.yi();
// }
//
//
// /**
// * Start a OpenGL frame
// */
// public void beginFrame()
// {
// // handle resize
// if (Display.wasResized()) {
// getEventBus().send(new ViewportChangeEvent(getSize()));
// }
//
// if (fullscreenSwitchRequested) {
// fullscreenSwitchRequested = false;
// doSwitchFullscreen();
// }
//
// glLoadIdentity();
// glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// fpsMeter.frame();
// }
//
//
// /**
// * End an OpenGL frame, flip buffers, sync to fps.
// */
// public void endFrame()
// {
// Display.update(false); // don't poll input devices
// Display.sync(targetFps);
// }
//
//
// /**
// * Get screen rect. This Rect is final and views at it can safely be made.
// */
// @Override
// public Rect getRect()
// {
// return getBounds();
// }
//
//
// /**
// * @return current FPS
// */
// public long getFps()
// {
// return fpsMeter.getFPS();
// }
//
//
// /**
// * Get screen center. This vect is final and views at it can safely be made.
// *
// * @return screen center.
// */
// public static Vect getCenter()
// {
// return rect.center();
// }
//}

@ -0,0 +1,542 @@
//package junk;
//
//
//import static org.lwjgl.opengl.GL11.*;
//
//import java.io.IOException;
//
//import mightypork.gamecore.resources.textures.FilterMode;
//import mightypork.gamecore.resources.textures.ITexture;
//import mightypork.gamecore.resources.textures.TxQuad;
//import mightypork.utils.files.FileUtils;
//import mightypork.utils.logging.Log;
//import mightypork.utils.math.color.Color;
//import mightypork.utils.math.color.pal.RGB;
//import mightypork.utils.math.constraints.rect.Rect;
//import mightypork.utils.math.constraints.rect.caching.RectDigest;
//import mightypork.utils.math.constraints.vect.Vect;
//import mightypork.utils.math.constraints.vect.VectConst;
//
//import org.lwjgl.opengl.GL11;
//import org.newdawn.slick.opengl.Texture;
//import org.newdawn.slick.opengl.TextureLoader;
//
//
///**
// * Render utilities
// *
// * @author Ondřej Hruška (MightyPork)
// */
//@Deprecated
//public class Render {
//
// public static final VectConst AXIS_X = Vect.make(1, 0, 0);
// public static final VectConst AXIS_Y = Vect.make(0, 1, 0);
// public static final VectConst AXIS_Z = Vect.make(0, 0, 1);
//
//
// /**
// * Bind GL color
// *
// * @param color Color color
// */
// public static void setColor(Color color)
// {
// if (color != null) glColor4d(color.r(), color.g(), color.b(), color.a());
// }
//
//
// /**
// * Bind GL color
// *
// * @param color Color color
// * @param alpha alpha multiplier
// */
// public static void setColor(Color color, double alpha)
// {
// if (color != null) glColor4d(color.r(), color.g(), color.b(), color.a() * alpha);
// }
//
//
// /**
// * Translate
// *
// * @param x
// * @param y
// */
// public static void translate(double x, double y)
// {
// glTranslated(x, y, 0);
// }
//
//
// /**
// * Translate
// *
// * @param x
// * @param y
// * @param z
// */
// public static void translate(double x, double y, double z)
// {
// glTranslated(x, y, z);
// }
//
//
// /**
// * Translate with coord
// *
// * @param coord coord
// */
// public static void translate(Vect coord)
// {
// glTranslated(coord.x(), coord.y(), coord.z());
// }
//
//
// /**
// * Translate with coord, discard Z
// *
// * @param coord coord
// */
// public static void translateXY(Vect coord)
// {
// glTranslated(coord.x(), coord.y(), 0);
// }
//
//
// /**
// * Scale
// *
// * @param x
// * @param y
// */
// public static void scale(double x, double y)
// {
// glScaled(x, y, 0);
// }
//
//
// /**
// * Scale
// *
// * @param x
// * @param y
// * @param z
// */
// public static void scale(double x, double y, double z)
// {
// glScaled(x, y, z);
// }
//
//
// /**
// * Scale
// *
// * @param factor vector of scaling factors
// */
// public static void scale(Vect factor)
// {
// glScaled(factor.x(), factor.y(), factor.z());
// }
//
//
// /**
// * Scale by X factor
// *
// * @param factor scaling factor
// */
// public static void scaleXY(double factor)
// {
// glScaled(factor, factor, 1);
// }
//
//
// /**
// * Scale by X factor
// *
// * @param factor scaling factor
// */
// public static void scaleX(double factor)
// {
// glScaled(factor, 1, 1);
// }
//
//
// /**
// * Scale by Y factor
// *
// * @param factor scaling factor
// */
// public static void scaleY(double factor)
// {
// glScaled(1, factor, 1);
// }
//
//
// /**
// * Scale by Z factor
// *
// * @param factor scaling factor
// */
// public static void scaleZ(double factor)
// {
// glScaled(1, 1, factor);
// }
//
//
// /**
// * Rotate around X axis
// *
// * @param angle deg
// */
// public static void rotateX(double angle)
// {
// rotate(angle, AXIS_X);
// }
//
//
// /**
// * Rotate around Y axis
// *
// * @param angle deg
// */
// public static void rotateY(double angle)
// {
// rotate(angle, AXIS_Y);
// }
//
//
// /**
// * Rotate around Z axis
// *
// * @param angle deg
// */
// public static void rotateZ(double angle)
// {
// rotate(angle, AXIS_Z);
// }
//
//
// /**
// * Rotate
// *
// * @param angle rotate angle
// * @param axis rotation axis
// */
// public static void rotate(double angle, Vect axis)
// {
// final Vect vec = axis.norm(1);
// glRotated(angle, vec.x(), vec.y(), vec.z());
// }
//
// private static int pushed = 0;
// /** Can be used to avoid texture binding and glBegin/glEnd in textured quads */
// public static boolean batchTexturedQuadMode;
//
//
// /**
// * Store GL state
// */
// public static void pushState()
// {
// pushed++;
//
// if (pushed >= 100) {
// Log.w("Suspicious number of state pushes: " + pushed);
// }
//
// GL11.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS);
// GL11.glPushClientAttrib(GL11.GL_ALL_CLIENT_ATTRIB_BITS);
// GL11.glMatrixMode(GL11.GL_MODELVIEW);
// GL11.glPushMatrix();
// GL11.glMatrixMode(GL11.GL_PROJECTION);
// GL11.glPushMatrix();
// GL11.glMatrixMode(GL11.GL_MODELVIEW);
// }
//
//
// /**
// * Restore Gl state
// */
// public static void popState()
// {
// if (pushed == 0) {
// Log.w("Pop without push.");
// }
//
// pushed--;
//
// GL11.glMatrixMode(GL11.GL_PROJECTION);
// GL11.glPopMatrix();
// GL11.glMatrixMode(GL11.GL_MODELVIEW);
// GL11.glPopMatrix();
// GL11.glPopClientAttrib();
// GL11.glPopAttrib();
// }
//
//
// /**
// * Store matrix
// */
// public static void pushMatrix()
// {
// GL11.glPushMatrix();
// }
//
//
// /**
// * Restore Gl state
// */
// public static void popMatrix()
// {
// GL11.glPopMatrix();
// }
//
//
// /**
// * Load texture
// *
// * @param resourcePath
// * @param filtering filtering mode to use while loading.
// * @return the loaded texture
// */
// public synchronized static Texture loadSlickTexture(String resourcePath, FilterMode filtering)
// {
//
// try {
//
// final String ext = FileUtils.getExtension(resourcePath).toUpperCase();
//
// final Texture texture = TextureLoader.getTexture(ext, FileUtils.getResource(resourcePath), false, filtering.num);
//
// if (texture == null) {
// Log.w("Texture " + resourcePath + " could not be loaded.");
// }
//
// return texture;
//
// } catch (final IOException e) {
// Log.e("Loading of texture " + resourcePath + " failed.", e);
// throw new RuntimeException("Could not load texture " + resourcePath + ".", e);
// }
//
// }
//
//
// /**
// * Render quad 2D
// *
// * @param rect rectangle
// * @param color draw color
// */
// public static void quad(Rect rect, Color color)
// {
// setColor(color);
// quad(rect);
// }
//
//
// /**
// * Render quad
// *
// * @param quad the quad to draw (px)
// */
// public static void quad(Rect quad)
// {
// final RectDigest q = quad.digest();
//
// // draw with color
//
// glDisable(GL_TEXTURE_2D);
//
// // quad
// glBegin(GL_QUADS);
// glVertex2d(q.left, q.bottom);
// glVertex2d(q.right, q.bottom);
// glVertex2d(q.right, q.top);
// glVertex2d(q.left, q.top);
// glEnd();
// }
//
//
// /**
// * Draw quad with horizontal gradient
// *
// * @param quad drawn quad bounds
// * @param color1 left color
// * @param color2 right color
// */
// public static void quadGradH(Rect quad, Color color1, Color color2)
// {
// quadColor(quad, color1, color2, color2, color1);
// }
//
//
// public static void quadColor(Rect quad, Color color)
// {
// quadColor(quad, color, color, color, color);
// }
//
//
// /**
// * Draw quad with coloured vertices.
// *
// * @param quad drawn quad bounds
// * @param colorHMinVMin
// * @param colorHMaxVMin
// * @param colorHMaxVMax
// * @param colorHMinVMax
// */
// public static void quadColor(Rect quad, Color colorHMinVMin, Color colorHMaxVMin, Color colorHMaxVMax, Color colorHMinVMax)
// {
// final RectDigest r = quad.digest();
//
// // draw with color
//
// glDisable(GL_TEXTURE_2D);
//
// glBegin(GL_QUADS);
// setColor(colorHMinVMax);
// glVertex2d(r.left, r.bottom);
//
// setColor(colorHMaxVMax);
// glVertex2d(r.right, r.bottom);
//
// setColor(colorHMaxVMin);
// glVertex2d(r.right, r.top);
//
// setColor(colorHMinVMin);
// glVertex2d(r.left, r.top);
// glEnd();
// }
//
//
// /**
// * Draw quad with vertical gradient
// *
// * @param quad drawn quad bounds
// * @param color1 top color
// * @param color2 bottom color
// */
// public static void quadGradV(Rect quad, Color color1, Color color2)
// {
// quadColor(quad, color1, color1, color2, color2);
// }
//
//
// /**
// * Render textured rect
// *
// * @param quad rectangle (px)
// * @param txquad texture quad
// */
// public static void quadTextured(Rect quad, TxQuad txquad)
// {
// quadTextured(quad, txquad, RGB.WHITE);
// }
//
//
// /**
// * Render textured rect
// *
// * @param quad rectangle (px)
// * @param txquad texture instance
// * @param tint color tint
// */
// public static void quadTextured(Rect quad, TxQuad txquad, Color tint)
// {
// if (!batchTexturedQuadMode) {
// glEnable(GL_TEXTURE_2D);
// txquad.tx.bind();
// glBegin(GL_QUADS);
// setColor(tint);
// }
//
// final RectDigest q = quad.digest();
// final RectDigest u = txquad.uvs.digest();
//
// final double offs = 0.0001;// hack to avoid white stitching
//
// double tL = u.left + offs, tR = u.right - offs, tT = u.top + offs, tB = u.bottom - offs;
//
// // handle flip
// if (txquad.isFlippedY()) {
// final double swap = tT;
// tT = tB;
// tB = swap;
// }
//
// if (txquad.isFlippedX()) {
// final double swap = tL;
// tL = tR;
// tR = swap;
// }
//
// final double w = txquad.tx.getWidth01();
// final double h = txquad.tx.getHeight01();
//
// // quad with texture
// glTexCoord2d(tL * w, tB * h);
// glVertex2d(q.left, q.bottom);
//
// glTexCoord2d(tR * w, tB * h);
// glVertex2d(q.right, q.bottom);
//
// glTexCoord2d(tR * w, tT * h);
// glVertex2d(q.right, q.top);
//
// glTexCoord2d(tL * w, tT * h);
// glVertex2d(q.left, q.top);
//
// if (!batchTexturedQuadMode) glEnd();
// }
//
//
// /**
// * Setup Ortho projection for 2D graphics
// *
// * @param size viewport size (screen size)
// */
// public static void setupOrtho(Vect size)
// {
// // fix projection for changed size
// glMatrixMode(GL_PROJECTION);
// glLoadIdentity();
// glViewport(0, 0, size.xi(), size.yi());
// glOrtho(0, size.xi(), size.yi(), 0, -1000, 1000);
//
// // back to modelview
// glMatrixMode(GL_MODELVIEW);
//
// glLoadIdentity();
//
// glDisable(GL_LIGHTING);
//
// glClearDepth(1f);
// glEnable(GL_DEPTH_TEST);
// glDepthFunc(GL_LEQUAL);
//
// glEnable(GL_NORMALIZE);
//
// glShadeModel(GL_SMOOTH);
//
// glEnable(GL_BLEND);
// glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// }
//
//
// public static void enterBatchTexturedQuadMode(ITexture texture)
// {
// texture.bind();
// glBegin(GL11.GL_QUADS);
// batchTexturedQuadMode = true;
// }
//
//
// public static void leaveBatchTexturedQuadMode()
// {
// glEnd();
// batchTexturedQuadMode = false;
// }
//}

@ -0,0 +1,261 @@
package junk;
//package mightypork.gamecore.resources.audio;
//
//
//import java.nio.FloatBuffer;
//import java.util.ArrayList;
//import java.util.List;
//
//import mightypork.gamecore.backend.lwjgl.SlickAudio;
//import mightypork.gamecore.core.modules.App;
//import mightypork.gamecore.resources.ResourceLoadRequest;
//import mightypork.gamecore.resources.audio.players.EffectPlayer;
//import mightypork.gamecore.resources.audio.players.LoopPlayer;
//import mightypork.gamecore.util.BufferHelper;
//import mightypork.utils.eventbus.clients.BusNode;
//import mightypork.utils.interfaces.Destroyable;
//import mightypork.utils.interfaces.Updateable;
//import mightypork.utils.logging.Log;
//import mightypork.utils.math.constraints.vect.Vect;
//import mightypork.utils.math.constraints.vect.var.VectVar;
//
//import org.lwjgl.openal.AL;
//import org.lwjgl.openal.AL10;
//import org.newdawn.slick.openal.SoundStore;
//
//
///**
// * Sound system class (only one instance should be made per application)
// *
// * @author Ondřej Hruška (MightyPork)
// */
//@Deprecated
//public class SoundSystem extends BusNode implements Updateable, Destroyable {
//
// private static final Vect INITIAL_LISTENER_POS = Vect.ZERO;
// private static final int MAX_SOURCES = 256;
//
// private static VectVar listener = Vect.makeVar();
// private static boolean soundSystemInited = false;
//
//
// /**
// * Set listener pos
// *
// * @param pos
// */
// public static void setListener(Vect pos)
// {
// listener.setTo(pos);
// final FloatBuffer buf3 = BufferHelper.alloc(3);
// final FloatBuffer buf6 = BufferHelper.alloc(6);
// buf3.clear();
// BufferHelper.fill(buf3, (float) pos.x(), (float) pos.y(), (float) pos.z());
// AL10.alListener(AL10.AL_POSITION, buf3);
// buf3.clear();
// BufferHelper.fill(buf3, 0, 0, 0);
// AL10.alListener(AL10.AL_VELOCITY, buf3);
// buf6.clear();
// BufferHelper.fill(buf6, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f);
// AL10.alListener(AL10.AL_ORIENTATION, buf6);
// }
//
//
// /**
// * @return listener coordinate
// */
// public static Vect getListener()
// {
// return listener;
// }
//
// // -- instance --
//
// private final Volume masterVolume = new Volume(1D);
// private final Volume effectsVolume = new JointVolume(masterVolume);
// private final Volume loopsVolume = new JointVolume(masterVolume);
//
// private final List<LoopPlayer> loopPlayers = new ArrayList<>();
// private final List<DeferredAudio> resources = new ArrayList<>();
//
//
// /**
// * @param busAccess app access
// */
// public SoundSystem() {
//
// if (!soundSystemInited) {
// soundSystemInited = true;
//
// try {
// SoundStore.get().setMaxSources(MAX_SOURCES);
// SoundStore.get().init();
// setListener(INITIAL_LISTENER_POS);
//
// App.bus().send(new AudioReadyEvent());
// } catch (final Throwable t) {
// Log.e("Error initializing sound system.", t);
// }
// }
// }
//
//
// @Override
// public void destroy()
// {
// for (final DeferredAudio r : resources) {
// r.destroy();
// }
//
// SoundStore.get().clear();
// AL.destroy();
// }
//
//
// @Override
// public void update(double delta)
// {
// for (final Updateable lp : loopPlayers) {
// lp.update(delta);
// }
// }
//
//
// /**
// * Create effect resource
// *
// * @param resource resource path
// * @param pitch default pitch (1 = unchanged)
// * @param gain default gain (0-1)
// * @return player
// */
// public EffectPlayer createEffect(String resource, double pitch, double gain)
// {
// return new EffectPlayer(createResource(resource), pitch, gain, effectsVolume);
// }
//
//
// /**
// * Register loop resource (music / effect loop)
// *
// * @param resource resource path
// * @param pitch default pitch (1 = unchanged)
// * @param gain default gain (0-1)
// * @param fadeIn default time for fadeIn
// * @param fadeOut default time for fadeOut
// * @return player
// */
// public LoopPlayer createLoop(String resource, double pitch, double gain, double fadeIn, double fadeOut)
// {
// final LoopPlayer p = new LoopPlayer(createResource(resource), pitch, gain, loopsVolume);
// p.setFadeTimes(fadeIn, fadeOut);
// loopPlayers.add(p);
// return p;
// }
//
//
// /**
// * Create {@link DeferredAudio} for a resource
// *
// * @param res a resource name
// * @return the resource
// * @throws IllegalArgumentException if resource is already registered
// */
// private DeferredAudio createResource(String res)
// {
// final DeferredAudio a = new SlickAudio(res);
// App.bus().send(new ResourceLoadRequest(a));
// resources.add(a);
// return a;
// }
//
//
// /**
// * Fade out all loops (ie. for screen transitions)
// */
// public void fadeOutAllLoops()
// {
// for (final LoopPlayer p : loopPlayers) {
// p.fadeOut();
// }
// }
//
//
// /**
// * Pause all loops (leave volume unchanged)
// */
// public void pauseAllLoops()
// {
// for (final LoopPlayer p : loopPlayers) {
// p.pause();
// }
// }
//
//
// /**
// * Set level of master volume
// *
// * @param d level
// */
// public void setMasterVolume(double d)
// {
// masterVolume.set(d);
// }
//
//
// /**
// * Set level of effects volume
// *
// * @param d level
// */
// public void setEffectsVolume(double d)
// {
// effectsVolume.set(d);
// }
//
//
// /**
// * Set level of music volume
// *
// * @param d level
// */
// public void setMusicVolume(double d)
// {
// loopsVolume.set(d);
// }
//
//
// /**
// * Get level of master volume
// *
// * @return level
// */
// public double getMasterVolume()
// {
// return masterVolume.get();
// }
//
//
// /**
// * Get level of effects volume
// *
// * @return level
// */
// public double getEffectsVolume()
// {
// return effectsVolume.get();
// }
//
//
// /**
// * Get level of music volume
// *
// * @return level
// */
// public double getMusicVolume()
// {
// return loopsVolume.get();
// }
//}

@ -0,0 +1,220 @@
package mightypork.gamecore.audio;
import java.util.ArrayList;
import java.util.List;
import mightypork.gamecore.audio.players.EffectPlayer;
import mightypork.gamecore.audio.players.LoopPlayer;
import mightypork.gamecore.core.App;
import mightypork.gamecore.core.BackendModule;
import mightypork.gamecore.resources.loading.ResourceLoadRequest;
import mightypork.utils.interfaces.Updateable;
import mightypork.utils.math.constraints.vect.Vect;
/**
* Abstract audio module.
*
* @author Ondřej Hruška (MightyPork)
*/
public abstract class AudioModule extends BackendModule implements Updateable {
/**
* Set listener position
*
* @param pos listener position
*/
public abstract void setListenerPos(Vect pos);
/**
* Get current listener position
*
* @return listener position
*/
public abstract Vect getListenerPos();
// -- instance --
private final Volume masterVolume = new Volume(1D);
private final Volume effectsVolume = new JointVolume(masterVolume);
private final Volume loopsVolume = new JointVolume(masterVolume);
private final List<LoopPlayer> loopPlayers = new ArrayList<>();
private final List<DeferredAudio> resources = new ArrayList<>();
@Override
public void destroy()
{
for (final DeferredAudio r : resources) {
r.destroy();
}
deinitSoundSystem();
}
/**
* Deinitialize the soud system, release resources etc.<br>
* Audio resources are already destroyed.
*/
protected abstract void deinitSoundSystem();
@Override
public void update(double delta)
{
for (final Updateable lp : loopPlayers) {
lp.update(delta);
}
}
/**
* Create effect resource
*
* @param resource resource path
* @param pitch default pitch (1 = unchanged)
* @param gain default gain (0-1)
* @return player
*/
public EffectPlayer createEffect(String resource, double pitch, double gain)
{
return new EffectPlayer(createResource(resource), pitch, gain, effectsVolume);
}
/**
* Register loop resource (music / effect loop)
*
* @param resource resource path
* @param pitch default pitch (1 = unchanged)
* @param gain default gain (0-1)
* @param fadeIn default time for fadeIn
* @param fadeOut default time for fadeOut
* @return player
*/
public LoopPlayer createLoop(String resource, double pitch, double gain, double fadeIn, double fadeOut)
{
final LoopPlayer p = new LoopPlayer(createResource(resource), pitch, gain, loopsVolume);
p.setFadeTimes(fadeIn, fadeOut);
loopPlayers.add(p);
return p;
}
/**
* Create {@link DeferredAudio} for a resource, request deferred load and
* add to the resources list.
*
* @param res a resource name
* @return the resource
* @throws IllegalArgumentException if resource is already registered
*/
protected DeferredAudio createResource(String res)
{
final DeferredAudio a = doCreateResource(res);
App.bus().send(new ResourceLoadRequest(a));
resources.add(a);
return a;
}
/**
* Create a backend-specific deferred audio resource
*
* @param res resource path
* @return Deferred Audio
*/
protected abstract DeferredAudio doCreateResource(String res);
/**
* Fade out all loops (= fade out the currently playing loops)
*/
public void fadeOutAllLoops()
{
for (final LoopPlayer p : loopPlayers) {
p.fadeOut();
}
}
/**
* Pause all loops (leave volume unchanged)
*/
public void pauseAllLoops()
{
for (final LoopPlayer p : loopPlayers) {
p.pause();
}
}
/**
* Set level of master volume (volume multiplier)
*
* @param volume level (0..1)
*/
public void setMasterVolume(double volume)
{
masterVolume.set(volume);
}
/**
* Set level of effects volume (volume multiplier)
*
* @param volume level (0..1)
*/
public void setEffectsVolume(double volume)
{
effectsVolume.set(volume);
}
/**
* Set level of loops volume (volume multiplier)
*
* @param volume level (0..1)
*/
public void setLoopsVolume(double volume)
{
loopsVolume.set(volume);
}
/**
* Get level of master volume (volume multiplier)
*
* @return level (0..1)
*/
public double getMasterVolume()
{
return masterVolume.get();
}
/**
* Get level of effects volume (volume multiplier)
*
* @return level (0..1)
*/
public double getEffectsVolume()
{
return effectsVolume.get();
}
/**
* Get level of loops volume (volume multiplier)
*
* @return level (0..1)
*/
public double getLoopsVolume()
{
return loopsVolume.get();
}
}

@ -0,0 +1,47 @@
package mightypork.gamecore.audio;
import mightypork.gamecore.core.App;
import mightypork.gamecore.resources.BaseDeferredResource;
import mightypork.utils.annotations.Alias;
import mightypork.utils.math.constraints.vect.Vect;
/**
* Abstract deferred audio, to be extended in backend.
*
* @author Ondřej Hruška (MightyPork)
*/
@Alias(name = "Audio")
public abstract class DeferredAudio extends BaseDeferredResource implements IAudio {
/**
* Create audio
*
* @param resourceName resource to load (when needed)
*/
public DeferredAudio(String resourceName) {
super(resourceName);
}
@Override
public void play(double pitch, double gain, boolean loop)
{
play(pitch, gain, loop, App.audio().getListenerPos());
}
@Override
public void play(double pitch, double gain, boolean loop, double x, double y)
{
play(pitch, gain, loop, x, y, App.audio().getListenerPos().z());
}
@Override
public void play(double pitch, double gain, boolean loop, Vect pos)
{
play(pitch, gain, loop, pos.x(), pos.y(), pos.z());
}
}

@ -0,0 +1,99 @@
package mightypork.gamecore.audio;
import mightypork.utils.interfaces.Destroyable;
import mightypork.utils.math.constraints.vect.Vect;
/**
* Audio resource interface (backend independent)
*
* @author Ondřej Hruška (MightyPork)
*/
public interface IAudio extends Destroyable {
/**
* Pause loop (remember position and stop playing) - if was looping
*/
void pauseLoop();
/**
* Resume loop (if was paused)
*/
void resumeLoop();
/**
* Adjust gain for the currently playing effect (can be used for fading
* music)
*
* @param gain gain to set 0..1
*/
void adjustGain(double gain);
/**
* Stop audio playback
*/
void stop();
/**
* @return true if the audio is playing
*/
boolean isPlaying();
/**
* @return trie if the audio is paused
*/
boolean isPaused();
/**
* Play as sound effect at listener position
*
* @param pitch pitch (1 = default)
* @param gain gain (0-1)
* @param loop looping
*/
void play(double pitch, double gain, boolean loop);
/**
* Play as sound effect at given X-Y position
*
* @param pitch pitch (1 = default)
* @param gain gain (0-1)
* @param loop looping
* @param x
* @param y
*/
void play(double pitch, double gain, boolean loop, double x, double y);
/**
* Play as sound effect at given position
*
* @param pitch pitch (1 = default)
* @param gain gain (0-1)
* @param loop looping
* @param x
* @param y
* @param z
*/
void play(double pitch, double gain, boolean loop, double x, double y, double z);
/**
* Play as sound effect at given position
*
* @param pitch pitch (1 = default)
* @param gain gain (0-1)
* @param loop looping
* @param pos coord
*/
void play(double pitch, double gain, boolean loop, Vect pos);
}

@ -0,0 +1,51 @@
package mightypork.gamecore.audio;
import mightypork.utils.math.Calc;
/**
* Volume combined of multiple volumes, combining them (multiplication).
*
* @author Ondřej Hruška (MightyPork)
*/
public class JointVolume extends Volume {
private final Volume[] volumes;
/**
* Create joint volume with master gain of 1
*
* @param volumes individual volumes to join
*/
@SafeVarargs
public JointVolume(Volume... volumes) {
super(1D);
this.volumes = volumes;
}
/**
* Get combined gain (multiplied)
*/
@Override
public Double get()
{
double d = super.get();
for (final Volume v : volumes)
d *= v.get();
return Calc.clamp(d, 0, 1);
}
/**
* Set master gain
*/
@Override
public void set(Double o)
{
super.set(o);
}
}

@ -0,0 +1,83 @@
package mightypork.gamecore.audio;
import java.util.HashMap;
import java.util.Map;
import mightypork.gamecore.audio.players.EffectPlayer;
import mightypork.gamecore.audio.players.LoopPlayer;
import mightypork.gamecore.core.App;
/**
* Audio resource storage
*
* @author Ondřej Hruška (MightyPork)
*/
public class SoundRegistry {
private final Map<String, EffectPlayer> effects = new HashMap<>();
private final Map<String, LoopPlayer> loops = new HashMap<>();
/**
* Register effect resource
*
* @param key sound key
* @param resource resource path
* @param pitch default pitch (1 = unchanged)
* @param gain default gain (0-1)
*/
public void addEffect(String key, String resource, double pitch, double gain)
{
effects.put(key, App.audio().createEffect(resource, pitch, gain));
}
/**
* Register loop resource (music / effect loop)
*
* @param key sound key
* @param resource resource path
* @param pitch default pitch (1 = unchanged)
* @param gain default gain (0-1)
* @param fadeIn default time for fadeIn
* @param fadeOut default time for fadeOut
*/
public void addLoop(String key, String resource, double pitch, double gain, double fadeIn, double fadeOut)
{
loops.put(key, App.audio().createLoop(resource, pitch, gain, fadeIn, fadeOut));
}
/**
* Get a loop player for key
*
* @param key sound key
* @return loop player
*/
public LoopPlayer getLoop(String key)
{
final LoopPlayer p = loops.get(key);
if (p == null) {
throw new RuntimeException("Unknown sound loop \"" + key + "\".");
}
return p;
}
/**
* Get a effect player for key
*
* @param key sound key
* @return effect player
*/
public EffectPlayer getEffect(String key)
{
final EffectPlayer p = effects.get(key);
if (p == null) {
throw new RuntimeException("Unknown sound effect \"" + key + "\".");
}
return p;
}
}

@ -0,0 +1,29 @@
package mightypork.gamecore.audio;
import mightypork.utils.math.Calc;
import mightypork.utils.struct.Mutable;
/**
* Mutable volume 0-1
*
* @author Ondřej Hruška (MightyPork)
*/
public class Volume extends Mutable<Double> {
/**
* @param d initial value
*/
public Volume(Double d) {
super(d);
}
@Override
public void set(Double d)
{
super.set(Calc.clamp(d, 0, 1));
}
}

@ -0,0 +1,105 @@
package mightypork.gamecore.audio.players;
import mightypork.gamecore.audio.DeferredAudio;
import mightypork.gamecore.audio.Volume;
import mightypork.utils.interfaces.Destroyable;
/**
* Basic abstract player
*
* @author Ondřej Hruška (MightyPork)
*/
public abstract class BaseAudioPlayer implements Destroyable {
/** the track */
private final DeferredAudio audio;
/** base gain for sfx */
private final double baseGain;
/** base pitch for sfx */
private final double basePitch;
/** dedicated volume control */
private final Volume gainMultiplier;
/**
* @param track audio resource
* @param basePitch base pitch (pitch multiplier)
* @param baseGain base gain (volume multiplier)
* @param volume colume control
*/
public BaseAudioPlayer(DeferredAudio track, double basePitch, double baseGain, Volume volume) {
this.audio = track;
this.baseGain = baseGain;
this.basePitch = basePitch;
if (volume == null) volume = new Volume(1D);
this.gainMultiplier = volume;
}
@Override
public void destroy()
{
audio.destroy();
}
/**
* @return audio resource
*/
protected DeferredAudio getAudio()
{
return audio;
}
/**
* Get play gain, computed based on volume and given multiplier
*
* @param multiplier extra volume adjustment
* @return computed gain
*/
protected double computeGain(double multiplier)
{
return baseGain * gainMultiplier.get() * multiplier;
}
/**
* Get pitch
*
* @param multiplier pitch adjustment
* @return computed pitch
*/
protected double computePitch(double multiplier)
{
return basePitch * multiplier;
}
/**
* Get if audio is valid
*
* @return is valid
*/
protected boolean hasAudio()
{
return (audio != null);
}
/**
* force load the resource
*/
public void load()
{
if (hasAudio()) audio.load();
}
}

@ -0,0 +1,66 @@
package mightypork.gamecore.audio.players;
import mightypork.gamecore.audio.DeferredAudio;
import mightypork.gamecore.audio.Volume;
import mightypork.utils.math.constraints.vect.Vect;
/**
* Player for one-off effects
*
* @author Ondřej Hruška (MightyPork)
*/
public class EffectPlayer extends BaseAudioPlayer {
/**
* @param track audio resource
* @param basePitch base pitch (pitch multiplier)
* @param baseGain base gain (volume multiplier)
* @param volume volume control
*/
public EffectPlayer(DeferredAudio track, double basePitch, double baseGain, Volume volume) {
super(track, (float) basePitch, (float) baseGain, volume);
}
/**
* Play at listener
*
* @param pitch play pitch
* @param gain play gain
*/
public void play(double pitch, double gain)
{
if (!hasAudio()) return;
getAudio().play(computePitch(pitch), computeGain(gain), false);
}
/**
* Play at listener
*
* @param gain play gain
*/
public void play(double gain)
{
play(1, gain);
}
/**
* Play at given position
*
* @param pitch play pitch
* @param gain play gain
* @param pos play position
*/
public void play(double pitch, double gain, Vect pos)
{
if (!hasAudio()) return;
getAudio().play(computePitch(pitch), computeGain(gain), false, pos);
}
}

@ -0,0 +1,166 @@
package mightypork.gamecore.audio.players;
import mightypork.gamecore.audio.DeferredAudio;
import mightypork.gamecore.audio.Volume;
import mightypork.utils.interfaces.Pauseable;
import mightypork.utils.interfaces.Updateable;
import mightypork.utils.math.animation.NumAnimated;
/**
* Audio loop player (with fading, good for music)
*
* @author Ondřej Hruška (MightyPork)
*/
public class LoopPlayer extends BaseAudioPlayer implements Updateable, Pauseable {
private int sourceID = -1;
/** animator for fade in and fade out */
private final NumAnimated fadeAnim = new NumAnimated(0);
private double lastUpdateGain = 0;
/** flag that track is paused */
private boolean paused = true;
/** Default fadeIn time */
private double inTime = 1;
/** Default fadeOut time */
private double outTime = 1;
/**
* @param track audio resource
* @param basePitch base pitch (pitch multiplier)
* @param baseGain base gain (volume multiplier)
* @param volume volume control
*/
public LoopPlayer(DeferredAudio track, double basePitch, double baseGain, Volume volume) {
super(track, (float) basePitch, (float) baseGain, volume);
paused = true;
}
/**
* Set fading duration (seconds)
*
* @param in duration of fade-in
* @param out duration of fade-out
*/
public void setFadeTimes(double in, double out)
{
inTime = in;
outTime = out;
}
private void initLoop()
{
if (hasAudio() && sourceID == -1) {
getAudio().play(computePitch(1), computeGain(1), true);
getAudio().pauseLoop();
}
}
@Override
public void pause()
{
if (!hasAudio() || paused) return;
initLoop();
getAudio().pauseLoop();
paused = true;
}
@Override
public boolean isPaused()
{
return paused;
}
@Override
public void resume()
{
if (!hasAudio() || !paused) return;
initLoop();
paused = false;
getAudio().adjustGain(computeGain(fadeAnim.value()));
}
@Override
public void update(double delta)
{
if (!hasAudio() || paused) return;
initLoop();
fadeAnim.update(delta);
final double gain = computeGain(fadeAnim.value());
if (!paused && gain != lastUpdateGain) {
getAudio().adjustGain(gain);
lastUpdateGain = gain;
}
if (gain == 0 && !paused) pause(); // pause on zero volume
}
/**
* Resume if paused, and fade in (pick up from current volume).
*
* @param secs
*/
public void fadeIn(double secs)
{
if (!hasAudio()) return;
if (isPaused()) fadeAnim.setTo(0);
resume();
fadeAnim.fadeIn(secs);
}
/**
* Fade out and pause when reached zero volume
*
* @param secs fade duration
*/
public void fadeOut(double secs)
{
if (!hasAudio()) return;
if (isPaused()) return;
fadeAnim.fadeOut(secs);
}
/**
* Fade in with default duration
*/
public void fadeIn()
{
fadeIn(inTime);
}
/**
* Fade out with default duration
*/
public void fadeOut()
{
fadeOut(outTime);
}
}

@ -0,0 +1,277 @@
package mightypork.gamecore.core;
import java.util.ArrayList;
import java.util.List;
import mightypork.gamecore.audio.AudioModule;
import mightypork.gamecore.core.config.Config;
import mightypork.gamecore.core.events.ShutdownEvent;
import mightypork.gamecore.graphics.GraphicsModule;
import mightypork.gamecore.input.InputModule;
import mightypork.utils.annotations.Stub;
import mightypork.utils.eventbus.EventBus;
import mightypork.utils.eventbus.clients.BusNode;
import mightypork.utils.eventbus.clients.DelegatingList;
import mightypork.utils.eventbus.events.DestroyEvent;
import mightypork.utils.logging.Log;
/**
* Game base class & static subsystem access
*
* @author MightyPork
*/
public class App extends BusNode {
private static App instance;
private final AppBackend backend;
private final EventBus eventBus = new EventBus();
private boolean started = false;
protected final DelegatingList plugins = new DelegatingList();
protected final List<InitTask> initializers = new ArrayList<>();
/**
* Create an app with given backend.
*
* @param backend
*/
public App(AppBackend backend) {
if (App.instance != null) {
throw new IllegalStateException("App already initialized");
}
// store current instance in static field
App.instance = this;
// join the bus
this.eventBus.subscribe(this);
// create plugin registry attached to bus
this.eventBus.subscribe(this.plugins);
// initialize and use backend
this.backend = backend;
this.eventBus.subscribe(backend);
this.backend.bind(this);
this.backend.initialize();
}
/**
* Add a plugin to the app. Plugins can eg. listen to bus events and react
* to them.
*
* @param plugin the added plugin.
*/
public void addPlugin(AppPlugin plugin)
{
if (started) {
throw new IllegalStateException("App already started, cannot add plugins.");
}
// attach to event bus
plugins.add(plugin);
plugin.bind(this);
plugin.initialize();
}
/**
* Add an initializer to the app.
*
* @param initializer
*/
public void addInitTask(InitTask initializer)
{
if (started) {
throw new IllegalStateException("App already started, cannot add initializers.");
}
initializers.add(initializer);
}
/**
* Get current backend
*
* @return the backend
*/
public AppBackend getBackend()
{
return backend;
}
/**
* Initialize the App and start operating.<br>
* This method should be called after adding all required initializers and
* plugins.
*/
public final void start()
{
if (started) {
throw new IllegalStateException("Already started.");
}
started = true;
// pre-init hook, just in case anyone wanted to have one.
Log.f2("Calling pre-init hook...");
preInit();
Log.i("=== Starting initialization sequence ===");
// sort initializers by order.
List<InitTask> orderedInitializers = InitTask.inOrder(initializers);
for (InitTask initializer : orderedInitializers) {
Log.f1("Running init task \"" + initializer.getName() + "\"...");
initializer.bind(this);
initializer.init();
initializer.run();
}
Log.i("=== Initialization sequence completed ===");
// user can now start the main loop etc.
Log.f2("Calling post-init hook...");
postInit();
}
/**
* Hook called before the initialization sequence starts.
*/
@Stub
protected void preInit()
{
}
/**
* Hook called after the initialization sequence is finished.
*/
@Stub
protected void postInit()
{
}
/**
* Shut down the running instance.<br>
* Deinitialize backend modules and terminate the JVM.
*/
public static void shutdown()
{
if (instance != null) {
Log.i("Dispatching Shutdown event...");
bus().send(new ShutdownEvent(new Runnable() {
@Override
public void run()
{
try {
final EventBus bus = bus();
if (bus != null) {
bus.send(new DestroyEvent());
bus.destroy();
}
} catch (final Throwable e) {
Log.e(e);
}
Log.i("Shutdown completed.");
System.exit(0);
}
}));
} else {
Log.w("App is not running.");
System.exit(0);
}
}
/**
* Get the currently running App instance.
*
* @return app instance
*/
public static App instance()
{
return instance;
}
/**
* Get graphics module from the running app's backend
*
* @return graphics module
*/
public static GraphicsModule gfx()
{
return instance.backend.getGraphics();
}
/**
* Get audio module from the running app's backend
*
* @return audio module
*/
public static AudioModule audio()
{
return instance.backend.getAudio();
}
/**
* Get input module from the running app's backend
*
* @return input module
*/
public static InputModule input()
{
return instance.backend.getInput();
}
/**
* Get event bus instance.
*
* @return event bus
*/
public static EventBus bus()
{
return instance.eventBus;
}
/**
* Get the main config, if initialized.
*
* @return main config
* @throws IllegalArgumentException if there is no such config.
*/
public static Config cfg()
{
return cfg("main");
}
/**
* Get a config by alias.
*
* @param alias config alias
* @return the config
* @throws IllegalArgumentException if there is no such config.
*/
public static Config cfg(String alias)
{
return Config.forAlias(alias);
}
}

@ -0,0 +1,65 @@
package mightypork.gamecore.core;
import mightypork.gamecore.audio.AudioModule;
import mightypork.gamecore.graphics.GraphicsModule;
import mightypork.gamecore.input.InputModule;
import mightypork.utils.eventbus.clients.BusNode;
/**
* Application backend interface (set of core modules).<br>
* The goal of this abstraction is to allow easy migration to different
* environment with different libraries etc. It should be as simple as using
* different backend.
*
* @author MightyPork
*/
public abstract class AppBackend extends BusNode {
protected App app;
/**
* Assign an app instance.
*
* @param app app
*/
public void bind(App app)
{
if (this.app != null) {
throw new IllegalStateException("App already set.");
}
this.app = app;
}
/**
* Initialize backend modules, add them to event bus.
*/
public abstract void initialize();
/**
* Get graphics module (screen manager, texture and font loader, renderer)
*
* @return graphics module
*/
public abstract GraphicsModule getGraphics();
/**
* Get audio module (
*
* @return audio module
*/
public abstract AudioModule getAudio();
/**
* Get input module
*
* @return input module
*/
public abstract InputModule getInput();
}

@ -0,0 +1,34 @@
package mightypork.gamecore.core;
import mightypork.utils.annotations.Stub;
import mightypork.utils.eventbus.clients.BusNode;
/**
* App plugin. Plugins are an easy way to extend app functionality.<br>
* Typically, a plugin waits for trigger event(s) and performs some action upon
* receiving them.
*
* @author Ondřej Hruška (MightyPork)
*/
public class AppPlugin extends BusNode {
protected App app;
void bind(App app)
{
this.app = app;
}
/**
* Initialize the plugin for the given App.<br>
* The plugin is already attached to the event bus.
*/
@Stub
public void initialize()
{
}
}

@ -0,0 +1,24 @@
package mightypork.gamecore.core;
import mightypork.utils.annotations.Stub;
import mightypork.utils.eventbus.clients.BusNode;
import mightypork.utils.interfaces.Destroyable;
/**
* Abstract application backend module.
*
* @author MightyPork
*/
public abstract class BackendModule extends BusNode implements Destroyable {
public abstract void init();
@Override
@Stub
public void destroy()
{
}
}

@ -0,0 +1,150 @@
package mightypork.gamecore.core;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import mightypork.utils.Reflect;
import mightypork.utils.annotations.Stub;
import mightypork.utils.logging.Log;
/**
* App initializer. A sequence of initializers is executed once the start()
* method on App is called. Adding initializers is one way to customize the App
* behavior and features.
*
* @author Ondřej Hruška (MightyPork)
*/
public abstract class InitTask {
protected App app;
/**
* Assign the initialized app instance to a protected "app" field.
*
* @param app app
*/
void bind(App app)
{
this.app = app;
}
/**
* An intialization method that is called before the run() method.<br>
* This method should be left unimplemented in the task, and can be used to
* configure the init task when using it as anonymous inner type.
*/
@Stub
public void init()
{
}
/**
* Run the initalizer on app.
*/
public abstract void run();
/**
* Get name of this initializer (for dependency resolver).<br>
* The name should be short, snake_case and precise.
*
* @return name
*/
public abstract String getName();
/**
* Get what other initializers must be already loaded before this can load.<br>
* Depending on itself or creating a circular dependency will cause error.<br>
* If the dependencies cannot be satisfied, the initialization sequence will
* be aborted.
*
* @return array of names of required initializers.
*/
@Stub
public String[] getDependencies()
{
return new String[] {};
}
/**
* Order init tasks so that all dependencies are loaded before thye are
* needed by the tasks.
*
* @param tasks task list
* @return task list ordered
*/
public static List<InitTask> inOrder(List<InitTask> tasks)
{
List<InitTask> remaining = new ArrayList<>(tasks);
List<InitTask> ordered = new ArrayList<>();
Set<String> loaded = new HashSet<>();
// resolve task order
int addedThisIteration = 0;
do {
for (Iterator<InitTask> i = remaining.iterator(); i.hasNext();) {
InitTask task = i.next();
String[] deps = task.getDependencies();
if (deps == null) deps = new String[] {};
int unmetDepsCount = deps.length;
for (String d : deps) {
if (loaded.contains(d)) unmetDepsCount--;
}
if (unmetDepsCount == 0) {
ordered.add(task);
loaded.add(task.getName());
i.remove();
addedThisIteration++;
}
}
} while (addedThisIteration > 0);
// check if any tasks are left out
if (remaining.size() > 0) {
// build error message for each bad task
int badInitializers = 0;
for (InitTask task : remaining) {
if (Reflect.hasAnnotation(task.getClass(), OptionalInitTask.class)) {
continue;
}
badInitializers++;
String notSatisfied = "";
for (String d : task.getDependencies()) {
if (!loaded.contains(d)) {
if (!notSatisfied.isEmpty()) {
notSatisfied += ", ";
}
notSatisfied += d;
}
}
Log.w("InitTask \"" + task.getName() + "\" - missing dependencies: " + notSatisfied);
}
if (badInitializers > 0) throw new RuntimeException("Some InitTask dependencies could not be satisfied.");
}
return ordered;
}
}

@ -0,0 +1,130 @@
package mightypork.gamecore.core;
import java.util.Deque;
import java.util.concurrent.ConcurrentLinkedDeque;
import mightypork.gamecore.graphics.Renderable;
import mightypork.gamecore.gui.screens.ScreenRegistry;
import mightypork.utils.annotations.Stub;
import mightypork.utils.eventbus.clients.BusNode;
import mightypork.utils.eventbus.events.UpdateEvent;
import mightypork.utils.interfaces.Destroyable;
import mightypork.utils.logging.Log;
import mightypork.utils.math.timing.Profiler;
import mightypork.utils.math.timing.TimerDelta;
/**
* Delta-timed game loop with task queue etc.
*
* @author Ondřej Hruška (MightyPork)
*/
public class MainLoop extends BusNode implements Destroyable {
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;
/**
* 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) {
App.gfx().beginFrame();
double delta = timer.getDelta();
if (delta > MAX_DELTA) {
Log.f3("(timing) Cropping delta: was " + delta + " , limit " + MAX_DELTA);
delta = MAX_DELTA;
}
App.bus().sendDirect(new UpdateEvent(delta));
Runnable r;
final 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();
App.gfx().endFrame();
}
}
/**
* Called before render
*/
@Stub
protected void beforeRender()
{
//
}
/**
* Called after render
*/
@Stub
protected void afterRender()
{
//
}
@Override
public void destroy()
{
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);
}
}
}

@ -0,0 +1,19 @@
package mightypork.gamecore.core;
import java.lang.annotation.*;
/**
* Indicates that an {@link InitTask} can safely be ignored if it's dependencies
* are not satisfied.
*
* @author Ondřej Hruška (MightyPork)
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Inherited
public @interface OptionalInitTask {
}

@ -0,0 +1,91 @@
package mightypork.gamecore.core;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import mightypork.utils.logging.Log;
/**
* Static application workdir accessor.
*
* @author Ondřej Hruška (MightyPork)
*/
public class WorkDir {
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), relative to the workdir.
*
* @param alias path alias
* @param path path relative to workdir
*/
public static void addPath(String alias, String path)
{
namedPaths.put(alias, path);
}
/**
* 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;
}
}

@ -0,0 +1,269 @@
package mightypork.gamecore.core.config;
import java.util.HashMap;
import java.util.Map;
import mightypork.gamecore.core.WorkDir;
import mightypork.gamecore.input.Key;
import mightypork.gamecore.input.KeyStroke;
import mightypork.utils.config.propmgr.Property;
import mightypork.utils.config.propmgr.PropertyManager;
import mightypork.utils.config.propmgr.PropertyStore;
import mightypork.utils.config.propmgr.store.PropertyFile;
import mightypork.utils.logging.Log;
/**
* Settings repository.
*
* @author Ondřej Hruška (MightyPork)
*/
public class Config {
protected static Map<String, Config> configs = new HashMap<>();
private Map<String, KeyStrokeProperty> strokes = new HashMap<>();
private PropertyManager propertyManager;
/**
* Get a config from the static map, by given alias
*
* @param alias alias
* @return the config
*/
public static Config forAlias(String alias)
{
Config c = configs.get(alias);
if (c == null) {
throw new IllegalArgumentException("There is no config with alias \"" + alias + "\"");
}
return c;
}
/**
* Register a config by alias.
*
* @param alias config alias
* @param config the config
*/
public static void register(String alias, Config config)
{
if (configs.get(alias) != null) {
throw new IllegalArgumentException("The alias \"" + alias + "\" is already used.");
}
configs.put(alias, config);
}
/**
* Initialize property manager for a file
*
* @param file config file, relative to workdir
* @param headComment file comment
*/
public Config(String file, String headComment) {
this(new PropertyFile(WorkDir.getFile(file), headComment));
}
/**
* Initialize property manager for a given store
*
* @param store property store backing the property manager
*/
public Config(PropertyStore store) {
if (propertyManager != null) {
throw new IllegalStateException("Config already initialized.");
}
propertyManager = new PropertyManager(store);
}
/**
* Add a keystroke property
*
* @param key key in config file
* @param defval default value (keystroke datastring)
* @param comment optional comment, can be null
*/
public void addKeyStroke(String key, String defval, String comment)
{
final KeyStrokeProperty kprop = new KeyStrokeProperty(prefixKeyStroke(key), KeyStroke.createFromString(defval), comment);
strokes.put(prefixKeyStroke(key), kprop);
propertyManager.addProperty(kprop);
}
/**
* Add a boolean property (flag)
*
* @param key key in config file
* @param defval default value
* @param comment optional comment, can be null
*/
public void addBoolean(String key, boolean defval, String comment)
{
propertyManager.addBoolean(key, defval, comment);
}
/**
* Add an integer property
*
* @param key key in config file
* @param defval default value
* @param comment optional comment, can be null
*/
public void addInteger(String key, int defval, String comment)
{
propertyManager.addInteger(key, defval, comment);
}
/**
* Add a double property
*
* @param key key in config file
* @param defval default value
* @param comment optional comment, can be null
*/
public void addDouble(String key, double defval, String comment)
{
propertyManager.addDouble(key, defval, comment);
}
/**
* Add a string property
*
* @param key key in config file
* @param defval default value
* @param comment optional comment, can be null
*/
public void addString(String key, String defval, String comment)
{
propertyManager.addString(key, defval, comment);
}
/**
* Add an arbitrary property (can be custom type)
*
* @param prop the property to add
*/
public <T> void addProperty(Property<T> prop)
{
propertyManager.addProperty(prop);
}
/**
* Load config from file
*/
public void load()
{
propertyManager.load();
}
/**
* Save config to file
*/
public void save()
{
Log.f3("Saving config.");
propertyManager.save();
}
/**
* Get an option for key
*
* @param key
* @return option value
*/
public <T> T getValue(String key)
{
try {
if (propertyManager.getProperty(key) == null) {
throw new IllegalArgumentException("No such property: " + key);
}
return propertyManager.getValue(key);
} catch (final ClassCastException cce) {
throw new RuntimeException("Property of incompatible type: " + key);
}
}
/**
* Set option to a value. Call the save() method to make the change
* permanent.
*
* @param key option key
* @param value value to set
*/
public <T> void setValue(String key, T value)
{
if (propertyManager.getProperty(key) == null) {
throw new IllegalArgumentException("No such property: " + key);
}
propertyManager.setValue(key, value);
}
/**
* Add "key." before the given config file key
*
* @param cfgKey config key
* @return key. + cfgKey
*/
private String prefixKeyStroke(String cfgKey)
{
return "key." + cfgKey;
}
/**
* Get keystroke for name
*
* @param cfgKey stroke identifier in config file
* @return the stroke
*/
public KeyStroke getKeyStroke(String cfgKey)
{
final KeyStrokeProperty kp = strokes.get(prefixKeyStroke(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 void setKeyStroke(String cfgKey, Key key, int mod)
{
final KeyStrokeProperty kp = strokes.get(prefixKeyStroke(cfgKey));
if (kp == null) {
throw new IllegalArgumentException("No such stroke: " + cfgKey);
}
kp.getValue().setTo(key, mod);
}
}

@ -0,0 +1,75 @@
package mightypork.gamecore.core.config;
import mightypork.gamecore.core.InitTask;
import mightypork.utils.annotations.Stub;
/**
* Initialize config. To apply this initializer, you must extend it. That
* ensures that the workdir initializer has already finished when the code is
* executed (such as resolving a file path for the config file).
*
* @author Ondřej Hruška (MightyPork)
*/
public abstract class InitTaskConfig extends InitTask {
/**
* Add a config with given alias
*
* @param alias config alias
* @param config config to add
*/
protected void addConfig(String alias, Config config)
{
Config.register(alias, config);
}
/**
* Initialize the main config.
*
* @return the main config.
*/
protected abstract Config buildConfig();
/**
* Initialize extra configs.<br>
* the addConfig() method can be used to register configs.
*/
@Stub
protected void buildExtraConfigs()
{
}
// locked to encourage the use of the build* methods.
@Override
public final void init()
{
}
@Override
public final void run()
{
addConfig("main", buildConfig());
buildExtraConfigs();
}
@Override
public String getName()
{
return "config";
}
@Override
public String[] getDependencies()
{
return new String[] { "workdir" };
}
}

@ -0,0 +1,55 @@
package mightypork.gamecore.core.config;
import mightypork.gamecore.input.Key;
import mightypork.gamecore.input.KeyStroke;
import mightypork.gamecore.input.Keys;
import mightypork.utils.config.propmgr.Property;
/**
* Key property.<br>
* The stored value must stay the same instance ({@link KeyStroke} is mutable).<br>
* That ensures that bindings based on this keystroke are automatically updated
* when the settings change.
*
* @author Ondřej Hruška (MightyPork)
*/
public class KeyStrokeProperty extends Property<KeyStroke> {
public KeyStrokeProperty(String key, KeyStroke defaultValue, String comment) {
super(key, defaultValue, comment);
}
@Override
public void fromString(String string)
{
if (string != null) {
// keep the same instance
final Key backup_key = value.getKey();
final int backup_mod = value.getMod();
value.loadFromString(string);
if (value.getKey() == Keys.NONE) {
value.setTo(backup_key, backup_mod);
}
}
}
@Override
public String toString()
{
return value.saveToString();
}
@Override
public void setValue(Object value)
{
// keep the same instance
this.value.setTo(((KeyStroke) value).getKey(), ((KeyStroke) value).getMod());
}
}

@ -0,0 +1,44 @@
package mightypork.gamecore.core.events;
import mightypork.gamecore.core.MainLoop;
import mightypork.utils.eventbus.BusEvent;
import mightypork.utils.eventbus.events.flags.SingleReceiverEvent;
/**
* Request to execute given {@link Runnable} in main loop.
*
* @author Ondřej Hruška (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
*/
public MainLoopRequest(Runnable task) {
this(task, false);
}
/**
* @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);
}
}

@ -0,0 +1,47 @@
package mightypork.gamecore.core.events;
import mightypork.utils.eventbus.BusEvent;
import mightypork.utils.eventbus.EventBus;
import mightypork.utils.logging.Log;
/**
* Shutdown event.<br>
* This event is dispatched when the <code>App.shutdown()</code> method is
* called. If no client consumes it, the shutdown will immediately follow.<br>
* This is a way to allow clients to abort the shutdown (ie. ask user to save
* game). After the game is saved, the <code>App.shutdown()</code> method can be
* called again.
*
* @author Ondřej Hruška (MightyPork)
*/
public class ShutdownEvent extends BusEvent<ShutdownListener> {
private Runnable shutdownTask;
public ShutdownEvent(Runnable doShutdown) {
this.shutdownTask = doShutdown;
}
@Override
protected void handleBy(ShutdownListener handler)
{
handler.onShutdown(this);
}
@Override
public void onDispatchComplete(EventBus bus)
{
if (!isConsumed()) {
Log.i("Shutting down...");
shutdownTask.run();
} else {
Log.i("Shutdown aborted.");
}
}
}

@ -0,0 +1,18 @@
package mightypork.gamecore.core.events;
/**
* Quit request listener; implementing client can abort shutdown.
*
* @author Ondřej Hruška (MightyPork)
*/
public interface ShutdownListener {
/**
* Intercept quit request.<br>
* Consume the event to abort shutdown (ie. ask user to save)
*
* @param event quit request event.
*/
void onShutdown(ShutdownEvent event);
}

@ -0,0 +1,42 @@
package mightypork.gamecore.core.init;
import java.lang.Thread.UncaughtExceptionHandler;
import mightypork.gamecore.core.App;
import mightypork.gamecore.core.InitTask;
import mightypork.utils.annotations.Stub;
import mightypork.utils.logging.Log;
/**
* Add a crash handler to the app.<br>
* For customized crash message / crash dialog etc, override the
* uncaughtException() method.
*
* @author Ondřej Hruška (MightyPork)
*/
public class InitTaskCrashHandler extends InitTask implements UncaughtExceptionHandler {
@Override
public void run()
{
Thread.setDefaultUncaughtExceptionHandler(this);
}
@Override
@Stub
public void uncaughtException(Thread thread, Throwable throwable)
{
Log.e("The game has crashed.", throwable);
App.shutdown();
}
@Override
public String getName()
{
return "crash_handler";
}
}

@ -0,0 +1,97 @@
package mightypork.gamecore.core.init;
import mightypork.gamecore.core.InitTask;
import mightypork.gamecore.graphics.GraphicsModule;
/**
* Setup main window.
*
* @author Ondřej Hruška (MightyPork)
*/
public class InitTaskDisplay extends InitTask {
private int width = 800, height = 600, fps = 60;
private boolean resizable, fullscreen;
private String title = "Game";
/**
* Set initial window size
*
* @param width width (px)
* @param height height (px)
*/
public void setSize(int width, int height)
{
this.width = width;
this.height = height;
}
/**
* Set whether the window should be resizable
*
* @param resizable true for resizable
*/
public void setResizable(boolean resizable)
{
this.resizable = resizable;
}
/**
* Set window title
*
* @param title title text
*/
public void setTitle(String title)
{
this.title = title;
}
/**
* Set desired framerate.
*
* @param fps FPS
*/
public void setTargetFps(int fps)
{
this.fps = fps;
}
/**
* Set whether the window should start in fullscreen
*
* @param fullscreen true for fullscreen
*/
public void setFullscreen(boolean fullscreen)
{
this.fullscreen = fullscreen;
}
@Override
public void run()
{
GraphicsModule gfx = app.getBackend().getGraphics();
gfx.setSize(width, height);
gfx.setResizable(resizable);
gfx.setTitle(title);
gfx.setTargetFps(fps);
if (fullscreen) gfx.setFullscreen(true);
}
@Override
public String getName()
{
return "display";
}
}

@ -0,0 +1,74 @@
package mightypork.gamecore.core.init;
import java.io.IOException;
import mightypork.gamecore.core.InitTask;
import mightypork.utils.ion.Ion;
import mightypork.utils.ion.IonInput;
import mightypork.utils.ion.IonOutput;
import mightypork.utils.ion.IonizerBinary;
import mightypork.utils.math.algo.Coord;
import mightypork.utils.math.algo.Move;
/**
* Register extra ionizables added by the game library (non-native ION types).<br>
* This initializer can be called anywhere in the initialization sequence.
*
* @author Ondřej Hruška (MightyPork)
*/
public class InitTaskIonizables extends InitTask {
@Override
public void run()
{
Ion.registerIndirect(255, new IonizerBinary<Coord>() {
@Override
public void save(Coord object, IonOutput out) throws IOException
{
out.writeInt(object.x);
out.writeInt(object.y);
}
@Override
public Coord load(IonInput in) throws IOException
{
final int x = in.readInt();
final int y = in.readInt();
return new Coord(x, y);
}
});
Ion.registerIndirect(254, new IonizerBinary<Move>() {
@Override
public void save(Move object, IonOutput out) throws IOException
{
out.writeInt(object.x());
out.writeInt(object.y());
}
@Override
public Move load(IonInput in) throws IOException
{
final int x = in.readInt();
final int y = in.readInt();
return new Move(x, y);
}
});
}
@Override
public String getName()
{
return "ion";
}
}

@ -0,0 +1,108 @@
package mightypork.gamecore.core.init;
import java.io.File;
import java.util.logging.Level;
import mightypork.gamecore.core.InitTask;
import mightypork.gamecore.core.WorkDir;
import mightypork.utils.logging.Log;
import mightypork.utils.logging.writers.LogWriter;
import mightypork.utils.string.StringUtil;
/**
* Init main logger and console log printing.<br>
* Must be called after workdir is initialized.
*
* @author Ondřej Hruška (MightyPork)
*/
public class InitTaskLog extends InitTask {
private String logDir = "log";
private String logName = "runtime";
private int archiveCount = 5;
private Level levelWrite = Level.ALL;
private Level levelPrint = Level.ALL;
/**
* Set log directory (relative to workdir).<br>
* Defaults to "log".
*
* @param logDir log directory.
*/
public void setLogDir(String logDir)
{
this.logDir = logDir;
}
/**
* Set log name. This name is used as a prefix for archived log files.<br>
* Should contain only valid filename characters.<br>
* Defaults to "runtime".
*
* @param logName log name
*/
public void setLogName(String logName)
{
if (!StringUtil.isValidFilenameString(logName)) {
throw new IllegalArgumentException("Invalid log name.");
}
this.logName = logName;
}
/**
* Set number of logs to keep in the logs directory.<br>
* Set to 0 to keep just the last log, -1 to keep unlimited number of logs.<br>
* Defaults to 5.
*
* @param archiveCount logs to keep
*/
public void setArchiveCount(int archiveCount)
{
this.archiveCount = archiveCount;
}
/**
* Set logging levels (minimal level of message to be accepted)<br>
* Defaults to ALL, ALL.
*
* @param levelWrite level for writing to file
* @param levelPrint level for writing to stdout / stderr
*/
public void setLevels(Level levelWrite, Level levelPrint)
{
this.levelWrite = levelWrite;
this.levelPrint = levelPrint;
}
@Override
public void run()
{
final LogWriter log = Log.create(logName, new File(WorkDir.getDir(logDir), logName + ".log"), archiveCount);
Log.setMainLogger(log);
Log.setLevel(levelWrite);
Log.setSysoutLevel(levelPrint);
}
@Override
public String getName()
{
return "log";
}
@Override
public String[] getDependencies()
{
return new String[] { "workdir" };
}
}

@ -0,0 +1,54 @@
package mightypork.gamecore.core.init;
import java.io.IOException;
import mightypork.gamecore.core.InitTask;
import mightypork.gamecore.core.OptionalInitTask;
import mightypork.gamecore.core.WorkDir;
import mightypork.utils.logging.Log;
/**
* initializer task that writes a system info header to the log file.<br>
* Must be called after log is initialized.
*
* @author Ondřej Hruška (MightyPork)
*/
@OptionalInitTask
public class InitTaskLogHeader extends InitTask {
@Override
public void run()
{
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);
}
@Override
public String getName()
{
return "log_header";
}
@Override
public String[] getDependencies()
{
return new String[] { "log", "workdir" };
}
}

@ -0,0 +1,136 @@
package mightypork.gamecore.core.init;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import javax.swing.JOptionPane;
import mightypork.gamecore.core.App;
import mightypork.gamecore.core.InitTask;
import mightypork.gamecore.core.WorkDir;
import mightypork.utils.annotations.Stub;
import mightypork.utils.files.InstanceLock;
import mightypork.utils.logging.Log;
/**
* Initializer that takes care of setting up the proper workdir.
*
* @author Ondřej Hruška (MightyPork)
*/
public class InitTaskWorkdir extends InitTask {
private File workdirPath;
private boolean doLock;
private String lockFile = ".lock";
private Map<String, String> namedPaths = new HashMap<>();
/**
* @param workdir path to the working directory
* @param lock whether to lock the directory (single instance mode)
*/
public InitTaskWorkdir(File workdir, boolean lock) {
this.workdirPath = workdir;
this.doLock = lock;
}
/**
* Set workdir root path
*
* @param path workdir path
*/
public void setWorkdirPath(File path)
{
this.workdirPath = path;
}
/**
* Set whether the workdir should be locked when the app is running, to
* prevent other instances from running simultaneously.
*
* @param lock
*/
public void setInstanceLock(boolean lock)
{
this.doLock = lock;
}
/**
* Set name of the lock file.
*
* @param lockFile
*/
public void setLockFileName(String lockFile)
{
this.lockFile = lockFile;
}
/**
* Add a named path
*
* @param alias path alias (snake_case)
* @param path path (relative to the workdir)
*/
public void addPath(String alias, String path)
{
namedPaths.put(alias, path);
}
@Override
public void run()
{
WorkDir.init(workdirPath);
// lock working directory
if (doLock) {
final File lock = WorkDir.getFile(lockFile);
if (!InstanceLock.onFile(lock)) {
onLockError();
return;
}
}
for (Entry<String, String> e : namedPaths.entrySet()) {
WorkDir.addPath(e.getKey(), e.getValue());
}
}
/**
* Called when the lock file could not be obtained (cannot write or already
* exists).<br>
* Feel free to override this method to define custom behavior.
*/
@Stub
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 "+lockFile +" file in the working directory to override)",
"Lock Error",
JOptionPane.ERROR_MESSAGE
);
//@formatter:on
App.shutdown();
}
@Override
public String getName()
{
return "workdir";
}
}

@ -0,0 +1,68 @@
package mightypork.gamecore.core.plugins.screenshot;
import mightypork.gamecore.core.InitTask;
import mightypork.gamecore.core.WorkDir;
/**
* Register screenshot plugin to the App.
*
* @author Ondřej Hruška (MightyPork)
*/
public class InitTaskPluginScreenshot extends InitTask {
private String screenshotDir;
/**
* Initialize to use the "screenshots" directory
*/
public InitTaskPluginScreenshot() {
this("screenshots");
}
/**
* Initialize to use the given directory for saving.
*
* @param dir screenshot dir (relative to workdir)
*/
public InitTaskPluginScreenshot(String dir) {
this.screenshotDir = dir;
}
/**
* Set screenshot directory
*
* @param dir screenshot dir (relative to workdir)
*/
public void setScreenshotDir(String dir)
{
this.screenshotDir = dir;
}
@Override
public void run()
{
WorkDir.addPath("_screenshot_dir", screenshotDir);
app.addPlugin(new ScreenshotPlugin());
}
@Override
public String getName()
{
return "plugin_screenshot";
}
@Override
public String[] getDependencies()
{
return new String[] { "workdir" };
}
}

@ -0,0 +1,34 @@
package mightypork.gamecore.core.plugins.screenshot;
import mightypork.gamecore.core.App;
import mightypork.gamecore.core.AppPlugin;
import mightypork.gamecore.core.events.MainLoopRequest;
import mightypork.utils.Support;
/**
* This plugin waits for a {@link ScreenshotRequest} event.<br>
* Upon receiving it, a screenshot is captured and written to file
* asynchronously.
*
* @author Ondřej Hruška (MightyPork)
*/
public class ScreenshotPlugin extends AppPlugin {
/**
* Take screenshot. Called by the trigger event.
*/
void takeScreenshot()
{
App.bus().send(new MainLoopRequest(new Runnable() {
@Override
public void run()
{
Runnable tts = new TaskTakeScreenshot();
Support.runAsThread(tts);
}
}));
}
}

@ -0,0 +1,22 @@
package mightypork.gamecore.core.plugins.screenshot;
import mightypork.utils.eventbus.BusEvent;
import mightypork.utils.eventbus.events.flags.SingleReceiverEvent;
/**
* Event used to request screenshot capture.
*
* @author MightyPork
*/
@SingleReceiverEvent
public class ScreenshotRequest extends BusEvent<ScreenshotPlugin> {
@Override
protected void handleBy(ScreenshotPlugin handler)
{
handler.takeScreenshot();
}
}

@ -0,0 +1,100 @@
package mightypork.gamecore.core.plugins.screenshot;
import java.io.File;
import java.io.IOException;
import mightypork.gamecore.core.App;
import mightypork.gamecore.core.WorkDir;
import mightypork.gamecore.graphics.Screenshot;
import mightypork.utils.Support;
import mightypork.utils.logging.Log;
/**
* Task that takes screenshot and asynchronously saves it to a file.<br>
* Can be run in a separate thread, but must be instantiated in the render
* thread.
*
* @author MightyPork
*/
public class TaskTakeScreenshot implements Runnable {
private final Screenshot scr;
/**
* Take screenshot. Must be called in render thread.
*/
public TaskTakeScreenshot() {
scr = App.gfx().takeScreenshot();
}
@Override
public void run()
{
// generate unique filename
final File file = getScreenshotFile();
Log.f3("Saving screenshot to file: " + file);
// save to disk
try {
scr.save(file);
} catch (final IOException e) {
Log.e("Failed to save screenshot.", e);
}
}
/**
* @return File to save the screenshot to.
*/
protected File getScreenshotFile()
{
final String fname = getBaseFilename();
return findFreeFile(fname);
}
/**
* @return directory for screenshots
*/
protected File getScreenshotDirectory()
{
return WorkDir.getDir("_screenshot_dir");
}
/**
* Get base filename for the screenshot, without extension.
*
* @return filename
*/
protected String getBaseFilename()
{
return Support.getTime("yyyy-MM-dd_HH-mm-ss");
}
/**
* Find first free filename for the screenshot, by adding -NUMBER after the
* base filename and before extension.
*
* @param base_name base filename
* @return full path to screenshot file
*/
protected File findFreeFile(String base_name)
{
File file;
int index = 0;
while (true) {
file = new File(getScreenshotDirectory(), base_name + (index > 0 ? "-" + index : "") + ".png");
if (!file.exists()) break;
index++;
}
return file;
}
}

@ -0,0 +1,16 @@
package mightypork.gamecore.graphics;
import mightypork.utils.eventbus.BusEvent;
import mightypork.utils.eventbus.events.flags.SingleReceiverEvent;
@SingleReceiverEvent
public class FullscreenToggleRequest extends BusEvent<GraphicsModule> {
@Override
protected void handleBy(GraphicsModule handler)
{
handler.switchFullscreen();
}
}

@ -0,0 +1,406 @@
package mightypork.gamecore.graphics;
import mightypork.gamecore.core.BackendModule;
import mightypork.gamecore.graphics.textures.DeferredTexture;
import mightypork.gamecore.graphics.textures.TxQuad;
import mightypork.gamecore.gui.events.ViewportChangeEvent;
import mightypork.utils.math.color.Color;
import mightypork.utils.math.color.Grad;
import mightypork.utils.math.constraints.rect.Rect;
import mightypork.utils.math.constraints.vect.Vect;
import mightypork.utils.math.constraints.vect.VectConst;
import mightypork.utils.math.timing.FpsMeter;
/**
* Render and display backend module.<br>
* This module takes care of setting and getting screen size and parameters,
* drawing on screen and timing render frames.
*
* @author MightyPork
*/
public abstract class GraphicsModule extends BackendModule {
protected static final VectConst AXIS_X = Vect.make(1, 0, 0);
protected static final VectConst AXIS_Y = Vect.make(0, 1, 0);
protected static final VectConst AXIS_Z = Vect.make(0, 0, 1);
/**
* Set drawing color
*
* @param color color
*/
public abstract void setColor(Color color);
/**
* Set drawing color, adjust alpha
*
* @param color color
* @param alpha alpha multiplier
*/
public abstract void setColor(Color color, double alpha);
/**
* Translate by x, y
*
* @param x x offset
* @param y y offset
*/
public abstract void translate(double x, double y);
/**
* Translate by x, y, z
*
* @param x x offset
* @param y y offset
* @param z z offset
*/
public abstract void translate(double x, double y, double z);
/**
* Translate by offset vector
*
* @param offset offset coordinate
*/
public abstract void translate(Vect offset);
/**
* Translate by offset vector, ignore Z
*
* @param offset offset coordinate
*/
public abstract void translateXY(Vect offset);
/**
* Set scale for translations and coordinates
*
* @param x x scale
* @param y y scale
*/
public abstract void scale(double x, double y);
/**
* Set scale for translations and coordinates
*
* @param x x scale
* @param y y scale
* @param z z scale
*/
public abstract void scale(double x, double y, double z);
/**
* Set scale for translations and coordinates
*
* @param scale vector
*/
public abstract void scale(Vect scale);
/**
* Set scale for translations and coordinates (same value for X and Y scale)
*
* @param scale scaling factor
*/
public abstract void scaleXY(double scale);
/**
* Set X scale for translations and coordinates
*
* @param scale scaling factor
*/
public abstract void scaleX(double scale);
/**
* Set Y scale for translations and coordinates
*
* @param scale scaling factor
*/
public abstract void scaleY(double scale);
/**
* Set Z scale for translations and coordinates
*
* @param scale scaling factor
*/
public abstract void scaleZ(double scale);
/**
* Rotate coordinate system around X axis
*
* @param angle rotation (in degrees)
*/
public abstract void rotateX(double angle);
/**
* Rotate coordinate system around Y axis
*
* @param angle rotation (in degrees)
*/
public abstract void rotateY(double angle);
/**
* Rotate coordinate system around Z axis
*
* @param angle rotation (in degrees)
*/
public abstract void rotateZ(double angle);
/**
* Rotate coordinate system around given axis
*
* @param angle rotation angle
* @param axis rotation axis (unit vector)
*/
public abstract void rotate(double angle, Vect axis);
/**
* Store render state on stack<br>
* This includes pushGeometry and pushColor.
*/
public abstract void pushState();
/**
* Restore state from stack (must be pushed first)<br>
* This includes popColor and popGeometry.
*/
public abstract void popState();
/**
* Store current rotation and translation on stack
*/
public abstract void pushGeometry();
/**
* Restore rotation and translation from stack
*/
public abstract void popGeometry();
/**
* Store color on stack (so it can later be restored)
*/
public abstract void pushColor();
/**
* Restore color from stack (must be pushed first)
*/
public abstract void popColor();
/**
* Render 2D quad with currently set color
*
* @param rect drawn rect
*/
public abstract void quad(Rect rect);
/**
* Render 2D quad with given color.<br>
* This may change current drawing color.
*
* @param rect rectangle
* @param color draw color
*/
public abstract void quad(Rect rect, Color color);
/**
* Render 2D quad with gradient.<br>
* This may change current drawing color.
*
* @param rect rectangle
* @param grad gradient
*/
public abstract void quad(Rect rect, Grad grad);
/**
* Render textured quad with current color
*
* @param rect rectangle to draw
* @param txquad texture quad
*/
public abstract void quad(Rect rect, TxQuad txquad);
/**
* Render textured quad with given color
*
* @param rect rectangle to draw
* @param txquad texture instance
* @param color color tint
*/
public abstract void quad(Rect rect, TxQuad txquad, Color color);
/**
* Setup projection for 2D graphics, using current scren size
*/
public abstract void setupProjection();
/**
* Get backend-flavoured lazy texture
*
* @param path path to texture
* @return the lazy texture
*/
public abstract DeferredTexture getLazyTexture(String path);
/**
* Set target fps (for syncing in endFrame() call).<br>
* With vsync enabled, the target fps may not be met.
*
* @param fps requested fps
*/
public abstract void setTargetFps(int fps);
/**
* Set fullscreen. The fullscreen state will be changed when possible (eg.
* at the end of the current frame) and a {@link ViewportChangeEvent} will
* be fired.
*
* @param fs true for fullscreen
*/
public abstract void setFullscreen(boolean fs);
/**
* Request fullscreen toggle. See setFullscreen() for more info)
*/
public abstract void switchFullscreen();
/**
* Get fullscreen state (note that methods changing fullscreen may not have
* immediate effect, so this method may report the old state if the
* fullscreen state has not yet been changed).
*
* @return is fullscreen
*/
public abstract boolean isFullscreen();
/**
* Take screenshot (expensive processing should be done in separate thread
* when screenshot is saved).<br>
* This method is utilized by the Screenshot plugin.
*
* @return screenshot object
*/
public abstract Screenshot takeScreenshot();
/**
* Start a render frame - clear buffers, prepare rendering context etc.
*/
public abstract void beginFrame();
/**
* End a render frame: flip buffers, sync to fps...
*/
public abstract void endFrame();
/**
* Set display dimensions
*
* @param width display width (pixels)
* @param height display height (pixels)
*/
public abstract void setSize(int width, int height);
/**
* Set window titlebar text
*
* @param title titlebar text
*/
public abstract void setTitle(String title);
/**
* Enable or disable VSync
*
* @param vsync true for vsync enabled
*/
public abstract void setVSync(boolean vsync);
/**
* Set window resizable / fixed
*
* @param resizable true for resizable
*/
public abstract void setResizable(boolean resizable);
/**
* Get screen rect. Should always return the same Rect instance.
*
* @return the rect
*/
public abstract Rect getRect();
/**
* Get current FPS (eg. measured by a {@link FpsMeter})
*
* @return current FPS
*/
public abstract long getFps();
/**
* Get screen center. Should always return the same Vect instance.
*
* @return screen center.
*/
public abstract Vect getCenter();
/**
* Get screen size. Should always return the same Vect instance.
*
* @return size
*/
public abstract Vect getSize();
/**
* @return screen width
*/
public abstract int getWidth();
/**
* @return screen height
*/
public abstract int getHeight();
}

@ -0,0 +1,16 @@
package mightypork.gamecore.graphics;
/**
* Can be rendered
*
* @author Ondřej Hruška (MightyPork)
*/
public interface Renderable {
/**
* Render on screen.
*/
void render();
}

@ -0,0 +1,34 @@
package mightypork.gamecore.graphics;
import java.io.File;
import java.io.IOException;
/**
* <p>
* Screenshot object used to save screenshot to a file. The Screenshot object is
* created by the Graphics module.
* </p>
* <p>
* Screenshot typically takes a byte buffer and converts it to image before
* saving to file. This image can be cached to speed up repeated saving.
* </p>
* <p>
* Once created (passing byte buffer in constructor), the Screenshot should be
* safe to process (call the save() method) in separate thread.
* </p>
*
* @author MightyPork
*/
public interface Screenshot {
/**
* Process byte buffer and write image to a file.<br>
* Image can be cached for future save.
*
* @param file target file
* @throws IOException on error writing to file
*/
void save(File file) throws IOException;
}

@ -0,0 +1,98 @@
package mightypork.gamecore.graphics.fonts;
import mightypork.gamecore.graphics.textures.FilterMode;
import mightypork.gamecore.resources.BaseDeferredResource;
/**
* Abstract deferred font stub.
*
* @author Ondřej Hruška (MightyPork)
*/
public abstract class DeferredFont extends BaseDeferredResource implements IFont {
public static enum FontStyle
{
PLAIN(0), BOLD(1), ITALIC(2), BOLD_ITALIC(3);
public int numval;
/**
* Font style
*
* @param style style index as in awt Font. Not using constants to be
* independent on awt.
*/
private FontStyle(int style) {
this.numval = style;
}
}
protected double size = 12;
protected FontStyle style = FontStyle.PLAIN;
protected String chars = Glyphs.basic;
protected FilterMode filter = FilterMode.NEAREST;
protected boolean antialias = false;
protected double discardTop = 0;
protected double discardBottom = 0;
public DeferredFont(String resource) {
super(resource);
}
public void setSize(double size)
{
this.size = size;
}
public void setStyle(FontStyle style)
{
this.style = style;
}
public void setChars(String chars)
{
this.chars = chars;
}
public void setFilter(FilterMode filter)
{
this.filter = filter;
}
public void setAntialias(boolean antialias)
{
this.antialias = antialias;
}
@Override
public void setDiscardRatio(double top, double bottom)
{
discardTop = top;
discardBottom = bottom;
}
@Override
public double getTopDiscardRatio()
{
return discardTop;
}
@Override
public double getBottomDiscardRatio()
{
return discardBottom;
}
}

@ -0,0 +1,79 @@
package mightypork.gamecore.graphics.fonts;
import java.util.HashMap;
import mightypork.gamecore.core.App;
import mightypork.gamecore.resources.loading.ResourceLoadRequest;
import mightypork.utils.eventbus.clients.BusNode;
/**
* Font loader and registry
*
* @author Ondřej Hruška (MightyPork)
*/
public class FontRegistry extends BusNode {
private final HashMap<String, IFont> fonts = new HashMap<>();
private final HashMap<String, String> aliases = new HashMap<>();
/**
* Load a {@link DeferredLwjglFont}
*
* @param key font key
* @param font font instance
*/
public void addFont(String key, DeferredFont font)
{
App.bus().send(new ResourceLoadRequest(font));
fonts.put(key, font);
}
/**
* Add a {@link IFont} to the bank.
*
* @param key font key
* @param font font instance
*/
public void addFont(String key, IFont font)
{
fonts.put(key, font);
}
/**
* Add a font alias.
*
* @param alias_key alias key
* @param font_key font key
*/
public void addAlias(String alias_key, String font_key)
{
aliases.put(alias_key, font_key);
}
/**
* Get a loaded {@link Texture}
*
* @param key texture key
* @return the texture
*/
public IFont getFont(String key)
{
IFont f = fonts.get(key);
if (f == null) f = fonts.get(aliases.get(key));
if (f == null) {
throw new RuntimeException("There's no font called " + key + "!");
}
return f;
}
}

@ -0,0 +1,213 @@
package mightypork.gamecore.graphics.fonts;
import mightypork.gamecore.core.App;
import mightypork.utils.math.AlignX;
import mightypork.utils.math.color.Color;
import mightypork.utils.math.color.pal.RGB;
import mightypork.utils.math.constraints.rect.Rect;
import mightypork.utils.math.constraints.vect.Vect;
/**
* Font renderer
*
* @author Ondřej Hruška (MightyPork)
*/
public class FontRenderer {
private IFont font;
private Color color;
/**
* @param font used font
*/
public FontRenderer(IFont font) {
this(font, RGB.WHITE);
}
/**
* @param font used font
* @param color drawing color
*/
public FontRenderer(IFont font, Color color) {
this.font = font;
this.color = color;
}
/**
* Get region needed to draw text at size
*
* @param text text to draw
* @param height drawing height
* @return taken space (width, height)
*/
public Vect getNeededSpace(String text, double height)
{
return font.getNeededSpace(text).mul(getScale(height));
}
/**
* Get width needed to draw text at size
*
* @param text text to draw
* @param height drawing height
* @return needed width
*/
public double getWidth(String text, double height)
{
return getNeededSpace(text, height).x();
}
private double getScale(double height)
{
return height / font.getLineHeight();
}
/**
* Change drawing font
*
* @param font font to use for drawing
*/
public void setFont(IFont font)
{
this.font = font;
}
/**
* Set drawing color
*
* @param color color
*/
public void setColor(Color color)
{
this.color = color;
}
/**
* Draw on screen
*
* @param text text to draw
* @param pos origin (min coord)
* @param height drawing height
* @param color drawing color
*/
public void draw(String text, Vect pos, double height, Color color)
{
App.gfx().pushGeometry();
final double sc = getScale(height);
App.gfx().translate(pos.x(), pos.y());
App.gfx().scaleXY(sc);
font.draw(text, color);
App.gfx().popGeometry();
}
/**
* Draw on screen
*
* @param text text to draw
* @param bounds drawing bounds (height for font height, horizontal bounds
* for align)
* @param align horizontal alignment (with respect to bounds)
*/
public void draw(String text, Rect bounds, AlignX align)
{
this.draw(text, bounds, align, this.color);
}
/**
* Draw on screen
*
* @param text text to draw
* @param bounds drawing bounds (height for font height, horizontal bounds
* for align)
* @param align horizontal alignment (with respect to bounds)
* @param color drawing color
*/
public void draw(String text, Rect bounds, AlignX align, Color color)
{
Vect start;
switch (align) {
case LEFT:
start = bounds.topLeft();
break;
case CENTER:
start = bounds.topCenter();
break;
case RIGHT:
default:
start = bounds.topRight();
break;
}
draw(text, start, bounds.height().value(), align, color);
}
/**
* Draw on screen
*
* @param text text to draw
* @param pos origin (min coord)
* @param height drawing height
* @param align horizontal alignment
*/
public void draw(String text, Vect pos, double height, AlignX align)
{
draw(text, pos, height, align, this.color);
}
/**
* Draw on screen
*
* @param text text to draw
* @param pos origin (min coord)
* @param height drawing height
* @param align horizontal alignment
* @param color drawing color
*/
public void draw(String text, Vect pos, double height, AlignX align, Color color)
{
final double w = getWidth(text, height);
Vect start;
switch (align) {
case LEFT:
start = pos;
break;
case CENTER:
start = pos.sub(w / 2D, 0);
break;
case RIGHT:
default:
start = pos.sub(w, 0);
break;
}
draw(text, start, height, color);
}
}

@ -0,0 +1,23 @@
package mightypork.gamecore.graphics.fonts;
/**
* Glyph tables, can be used for font loading.
*
* @author Ondřej Hruška (MightyPork)
*/
public class Glyphs {
public static final String latin = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
public static final String latin_extra = "ŒÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜŸÝßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿĚŠČŘŽŤŇĎŮěščřžťňďůŁłđ";
public static final String numbers = "0123456789";
public static final String punctuation = ".-,.?!:;\"'";
public static final String punctuation_extra = "()¿¡»«›‹“”‘’„…";
public static final String symbols = "[]{}#$%&§*+/<=>@\\^_|~°";
public static final String symbols_extra = "¥€£¢`ƒ†‡ˆ‰•¤¦¨ªº¹²³¬­¯±´µ¶·¸¼½¾×÷™©­®→↓←↑";
public static final String basic = latin + numbers + punctuation + symbols;
public static final String extra = latin_extra + punctuation_extra + symbols_extra;
public static final String all = basic + extra;
}

@ -0,0 +1,76 @@
package mightypork.gamecore.graphics.fonts;
import mightypork.utils.math.color.Color;
import mightypork.utils.math.constraints.vect.Vect;
/**
* Interface bor drawable font.
*
* @author Ondřej Hruška (MightyPork)
*/
public interface IFont {
/**
* Draw without scaling at (0, 0) in given color.
*
* @param text text to draw
* @param color draw color
*/
void draw(String text, Color color);
/**
* Get suize needed to render give string
*
* @param text string to check
* @return coord (width, height)
*/
Vect getNeededSpace(String text);
/**
* @return font height
*/
int getLineHeight();
/**
* @param text texted text
* @return space needed
*/
int getWidth(String text);
/**
* @return specified font size
*/
int getFontSize();
/**
* Set what vertical ratio of the font size is blank and should be cut off
* when rendering
*
* @param top top ratio (0-1)
* @param bottom bottom ratio (0-1)
*/
void setDiscardRatio(double top, double bottom);
/**
* Get top discard ratio (blank unused space)
*
* @return ratio
*/
double getTopDiscardRatio();
/**
* Get bottom discard ratio (blank unused space)
*
* @return ratio
*/
double getBottomDiscardRatio();
}

@ -0,0 +1,57 @@
package mightypork.gamecore.graphics.textures;
import mightypork.gamecore.resources.BaseDeferredResource;
import mightypork.gamecore.resources.loading.MustLoadInRenderingContext;
import mightypork.utils.annotations.Alias;
import mightypork.utils.math.constraints.rect.Rect;
/**
* Deferred texture (to be extended by backend texture)
*
* @author Ondřej Hruška (MightyPork)
*/
@Alias(name = "Texture")
@MustLoadInRenderingContext
public abstract class DeferredTexture extends BaseDeferredResource implements ITexture {
protected FilterMode filter = FilterMode.NEAREST;
protected WrapMode wrap = WrapMode.CLAMP;
/**
* @param resourcePath resource path
*/
public DeferredTexture(String resourcePath) {
super(resourcePath);
}
@Override
public TxQuad makeQuad(Rect uvs)
{
return new TxQuad(this, uvs);
}
@Override
public void setFilter(FilterMode filterMin)
{
this.filter = filterMin;
}
@Override
public void setWrap(WrapMode wrapping)
{
this.wrap = wrapping;
}
@Override
public QuadGrid grid(int x, int y)
{
return new QuadGrid(this, x, y);
}
}

@ -0,0 +1,12 @@
package mightypork.gamecore.graphics.textures;
/**
* Texture filtering mode
*
* @author Ondřej Hruška (MightyPork)
*/
public enum FilterMode
{
LINEAR, NEAREST;
}

@ -0,0 +1,64 @@
package mightypork.gamecore.graphics.textures;
import mightypork.utils.interfaces.Destroyable;
import mightypork.utils.math.constraints.rect.Rect;
/**
* Texture interface, backend independent
*
* @author Ondřej Hruška (MightyPork)
*/
public interface ITexture extends Destroyable {
/**
* Set filter for scaling
*
* @param filter filter
*/
void setFilter(FilterMode filter);
/**
* @param wrapping wrap mode
*/
void setWrap(WrapMode wrapping);
/**
* Get a quad from this texture of given position/size
*
* @param uvs quad rect
* @return the quad
*/
TxQuad makeQuad(Rect uvs);
/**
* Get a grid for given number of tiles
*
* @param x horizontal tile count
* @param y vertical tile count
* @return grid
*/
QuadGrid grid(int x, int y);
/**
* @return source image width (corresponding to width01)
*/
int getImageWidth();
/**
* @return source image height (corresponding to height01)
*/
int getImageHeight();
/**
* @return true if the image is RGBA
*/
boolean hasAlpha();
}

@ -0,0 +1,92 @@
package mightypork.gamecore.graphics.textures;
import mightypork.utils.math.constraints.rect.Rect;
/**
* {@link TxQuad} and {@link TxSheet} building utility
*
* @author Ondřej Hruška (MightyPork)
*/
public class QuadGrid {
private final ITexture tx;
private final int txHeight;
private final int txWidth;
private final double tileW;
private final double tileH;
public QuadGrid(ITexture tx, int tilesX, int tilesY) {
this.tx = tx;
this.txWidth = tilesX;
this.txHeight = tilesY;
this.tileW = 1D / tilesX;
this.tileH = 1D / tilesY;
}
/**
* Make square quad at given coords (one grid cell)
*
* @param x x coordinate (cells)
* @param y y coordinate (cells)
* @return the quad
*/
public TxQuad makeQuad(double x, double y)
{
if (x < 0 || x >= txWidth || y < 0 || y >= txHeight) {
throw new IndexOutOfBoundsException("Requested invalid txquad coordinates.");
}
return makeQuad(x, y, 1, 1);
}
/**
* Make square quad at given coords, with arbitrary size. Coordinates are
* multiples of cell size.
*
* @param x x coordinate (cells)
* @param y y coordinate (cells)
* @param width width (cells)
* @param height height (cells)
* @return the quad
*/
public TxQuad makeQuad(double x, double y, double width, double height)
{
if (x < 0 || x >= txWidth || y < 0 || y >= txHeight) {
throw new IndexOutOfBoundsException("Requested invalid txquad coordinates.");
}
if (x + width > txWidth || y + height > txHeight) {
throw new IndexOutOfBoundsException("Requested invalid txquad size (would go beyond texture size).");
}
return tx.makeQuad(Rect.make(tileW * x, tileH * y, tileW * width, tileH * height));
}
/**
* Make a sheet.
*
* @param x x origin coordinate (cells)
* @param y y origin coordinate (cells)
* @param width width (cells)
* @param height height (cells)
* @return the sheet
*/
public TxSheet makeSheet(double x, double y, double width, double height)
{
if (x < 0 || x >= txWidth || y < 0 || y >= txHeight) {
throw new IndexOutOfBoundsException("Requested invalid txquad coordinates.");
}
if (x + width > txWidth || y + height > txHeight) {
throw new IndexOutOfBoundsException("Requested invalid txsheet size (would go beyond texture size).");
}
return makeQuad(x, y).makeSheet(width, height);
}
}

@ -0,0 +1,143 @@
package mightypork.gamecore.graphics.textures;
import java.util.HashMap;
import java.util.Map;
import mightypork.gamecore.core.App;
import mightypork.gamecore.resources.loading.ResourceLoadRequest;
import mightypork.utils.exceptions.KeyAlreadyExistsException;
import mightypork.utils.math.constraints.rect.Rect;
/**
* Texture storage and quad/sheet registry. Quads and Sheets are interchangeable
* once registered.
*
* @author Ondřej Hruška (MightyPork)
*/
public class TextureRegistry {
private final Map<String, ITexture> textures = new HashMap<>();
private final Map<String, TxSheet> sheets = new HashMap<>();
/**
* Load a texture from resource, without a key. This texture will not be
* added to the bank.
*
* @param resourcePath resource path of the texture
* @param filter
* @param wrap
* @return texture reference
*/
public ITexture addTexture(String resourcePath, FilterMode filter, WrapMode wrap)
{
return addTexture(resourcePath, resourcePath, filter, wrap);
}
/**
* Load a texture from resource; if key is not null, the texture will be
* added to the bank.
*
* @param key texture key, can be null.
* @param resourcePath resource path of the texture
* @param filter
* @param wrap
* @return texture reference
*/
public ITexture addTexture(String key, String resourcePath, FilterMode filter, WrapMode wrap)
{
if (key != null) if (textures.containsKey(key)) throw new KeyAlreadyExistsException();
final DeferredTexture texture = App.gfx().getLazyTexture(resourcePath);
texture.setFilter(filter);
texture.setWrap(wrap);
App.bus().send(new ResourceLoadRequest(texture));
if (key != null) {
textures.put(key, texture);
add(key, texture.makeQuad(Rect.ONE));
}
return texture;
}
/**
* Add already created quad to the quad registry
*
* @param quadKey key
* @param quad quad to add
*/
public void add(String quadKey, TxQuad quad)
{
if (sheets.containsKey(quadKey)) throw new KeyAlreadyExistsException();
sheets.put(quadKey, quad.makeSheet(1, 1));
}
/**
* Add an already created sheet
*
* @param sheetKey key
* @param sheet sheet to add
*/
public void add(String sheetKey, TxSheet sheet)
{
if (sheets.containsKey(sheetKey)) throw new KeyAlreadyExistsException();
sheets.put(sheetKey, sheet);
}
/**
* Get a {@link TxQuad} for key; if it was added as sheet, the first quad
* ofthe sheet is returned.
*
* @param key quad key
* @return the quad
*/
public TxQuad getQuad(String key)
{
return getSheet(key).getQuad(0); // get the first
}
/**
* Get a loaded {@link ITexture}
*
* @param key texture key
* @return the texture
*/
public ITexture getTexture(String key)
{
final ITexture tx = textures.get(key);
if (tx == null) throw new RuntimeException("There's no texture called \"" + key + "\"!");
return tx;
}
/**
* Get a {@link TxSheet} for key
*
* @param key sheet key
* @return the sheet
*/
public TxSheet getSheet(String key)
{
final TxSheet sh = sheets.get(key);
if (sh == null) {
throw new RuntimeException("There's no sheet called \"" + key + "\"!");
}
return sh;
}
}

@ -0,0 +1,164 @@
package mightypork.gamecore.graphics.textures;
import mightypork.utils.math.constraints.rect.Rect;
import mightypork.utils.math.constraints.rect.RectConst;
/**
* Texture Quad (describing a part of a texture)
*
* @author Ondřej Hruška (MightyPork)
*/
public class TxQuad {
/** The texture */
public final ITexture tx;
/** Coords in texture (0-1) */
public final RectConst uvs;
private boolean flipX;
private boolean flipY;
/**
* TxQuad from origin and size in pixels
*
* @param tx texture
* @param xPx left top X (0-1)
* @param yPx left top Y (0-1)
* @param widthPx area width (0-1)
* @param heightPx area height (0-1)
* @return new TxQuad
*/
public static TxQuad fromSizePx(ITexture tx, double xPx, double yPx, double widthPx, double heightPx)
{
final double w = tx.getImageWidth();
final double h = tx.getImageHeight();
return fromSize(tx, xPx / w, yPx / h, widthPx / w, heightPx / h);
}
/**
* TxQuad from origin and size 0-1
*
* @param tx texture
* @param x1 left top X (0-1)
* @param y1 left top Y (0-1)
* @param width area width (0-1)
* @param height area height (0-1)
* @return new TxQuad
*/
public static TxQuad fromSize(ITexture tx, double x1, double y1, double width, double height)
{
return new TxQuad(tx, x1, y1, x1 + width, y1 + height);
}
/**
* Make of coords
*
* @param tx texture
* @param x1 left top X (0-1)
* @param y1 left top Y (0-1)
* @param x2 right bottom X (0-1)
* @param y2 right bottom Y (0-1)
*/
public TxQuad(ITexture tx, double x1, double y1, double x2, double y2) {
this(tx, Rect.make(x1, y1, x2, y2));
}
/**
* @param tx Texture
* @param uvs Rect of texture UVs (0-1); will be frozen.
*/
public TxQuad(ITexture tx, Rect uvs) {
this.tx = tx;
this.uvs = uvs.freeze();
}
/**
* Clone another
*
* @param txQuad a copied quad
*/
public TxQuad(TxQuad txQuad) {
this.tx = txQuad.tx;
this.uvs = txQuad.uvs;
this.flipX = txQuad.flipX;
this.flipY = txQuad.flipY;
}
/**
* Get copy
*
* @return copy of this
*/
public TxQuad copy()
{
return new TxQuad(this);
}
/**
* Make a sheet starting with this quad, spannign to right and down.
*
* @param width sheet width
* @param height sheet height
* @return sheet
*/
public TxSheet makeSheet(double width, double height)
{
return new TxSheet(this, (int) Math.round(width), (int) Math.round(height));
}
/**
* @return copy flipped X
*/
public TxQuad flipX()
{
final TxQuad copy = new TxQuad(this);
copy.flipX ^= true;
return copy;
}
/**
* @return copy flipped Y
*/
public TxQuad flipY()
{
final TxQuad copy = new TxQuad(this);
copy.flipY ^= true;
return copy;
}
public boolean isFlippedY()
{
return flipY;
}
public boolean isFlippedX()
{
return flipX;
}
/**
* Use the same flit/other attributes as the original txQuad
*
* @param original
*/
public void dupeAttrs(TxQuad original)
{
this.flipX = original.flipX;
this.flipY = original.flipY;
}
}

@ -0,0 +1,121 @@
package mightypork.gamecore.graphics.textures;
import java.util.Random;
import mightypork.utils.logging.Log;
/**
* Basic sprite sheet
*
* @author Ondřej Hruška (MightyPork)
*/
public class TxSheet {
private final TxQuad original;
private final TxQuad[] sprites;
private final int width;
private final Random rand = new Random();
private final Random randForSeed = new Random();
private final int count;
public TxSheet(TxQuad tx, int width, int height) {
this.original = tx;
this.width = width;
this.count = width * height;
this.sprites = new TxQuad[count];
}
/**
* @return number of quads
*/
public int getQuadCount()
{
return count;
}
/**
* Get a quad based on ratio 0-1 (0: first, 1: last)
*
* @param ratio ratio
* @return quad
*/
public TxQuad getQuad(double ratio)
{
return getQuad((int) Math.round((count - 1) * ratio));
}
/**
* Get quad of index
*
* @param index index
* @return the quad
*/
public TxQuad getQuad(int index)
{
if (index < 0 || index >= count) {
Log.w("Index out of bounds: " + index + ", allowed: 0.." + count);
index = index % count;
}
// lazy - init only when needed
if (sprites[index] == null) {
final int x = index % width;
final int y = index / width;
final double origW = original.uvs.width().value();
final double origH = original.uvs.height().value();
final TxQuad txq = new TxQuad(original.tx, original.uvs.move(x * origW, y * origH));
txq.dupeAttrs(original);
sprites[index] = txq;
}
return sprites[index];
}
/**
* Get entirely random TxQuad from this sheet
*
* @return the picked quad
*/
public TxQuad getRandomQuad()
{
return getQuad(rand.nextInt(count));
}
/**
* Get random TxQuad from this sheet
*
* @param seed random number generator seed
* @return the picked quad
*/
public TxQuad getRandomQuad(long seed)
{
randForSeed.setSeed(seed);
return getQuad(randForSeed.nextInt(count));
}
/**
* Get random TxQuad from this sheet
*
* @param seed random number generator seed (double will be converted to
* long)
* @return the picked quad
*/
public TxQuad getRandomQuad(double seed)
{
return getRandomQuad(Double.doubleToLongBits(seed));
}
}

@ -0,0 +1,12 @@
package mightypork.gamecore.graphics.textures;
/**
* Texture wrap mode
*
* @author Ondřej Hruška (MightyPork)
*/
public enum WrapMode
{
CLAMP, REPEAT;
}

@ -0,0 +1,54 @@
package mightypork.gamecore.gui;
import mightypork.utils.interfaces.Enableable;
/**
* Triggered action
*
* @author Ondřej Hruška (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();
}

@ -0,0 +1,38 @@
package mightypork.gamecore.gui;
import java.util.HashSet;
import java.util.Set;
import mightypork.utils.interfaces.Enableable;
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);
}
}

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

@ -0,0 +1,169 @@
package mightypork.gamecore.gui.components;
import mightypork.gamecore.core.App;
import mightypork.gamecore.graphics.Renderable;
import mightypork.gamecore.gui.events.LayoutChangeEvent;
import mightypork.gamecore.gui.events.LayoutChangeListener;
import mightypork.utils.Support;
import mightypork.utils.annotations.Stub;
import mightypork.utils.interfaces.Enableable;
import mightypork.utils.logging.Log;
import mightypork.utils.math.color.Color;
import mightypork.utils.math.constraints.num.Num;
import mightypork.utils.math.constraints.rect.Rect;
import mightypork.utils.math.constraints.rect.RectBound;
import mightypork.utils.math.constraints.rect.caching.AbstractRectCache;
import mightypork.utils.math.constraints.rect.proxy.RectProxy;
/**
* {@link Renderable} with pluggable context. When caching is enabled, the
* layout update can be triggered by firing the {@link LayoutChangeEvent}.
*
* @author Ondřej Hruška (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 RectProxy(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: " + Support.str(getClass()));
}
}
@Override
public final void onConstraintChanged()
{
updateLayout();
}
@Override
public final boolean isMouseOver()
{
return App.input().getMousePos().isInside(this);
}
/**
* Draw the component (it's visible)
*/
protected abstract void renderComponent();
@Override
@Stub
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;
}
}

@ -0,0 +1,95 @@
package mightypork.gamecore.gui.components;
import mightypork.utils.interfaces.Enableable;
import mightypork.utils.interfaces.Hideable;
import mightypork.utils.math.constraints.num.Num;
/**
* Basic UI component interface
*
* @author Ondřej Hruška (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();
}

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

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

@ -0,0 +1,122 @@
package mightypork.gamecore.gui.components;
import java.util.Collection;
import java.util.LinkedList;
import mightypork.utils.eventbus.clients.ClientHub;
import mightypork.utils.eventbus.clients.DelegatingList;
import mightypork.utils.math.constraints.rect.RectBound;
public abstract class LayoutComponent extends BaseComponent implements ClientHub {
private final DelegatingList clientList;
final LinkedList<Component> components = new LinkedList<>();
public LayoutComponent(RectBound context) {
this.clientList = new DelegatingList();
setRect(context);
enableCaching(true); // layout is typically updated only when screen resizes.
}
public LayoutComponent() {
this(null);
}
@Override
public Collection<Object> getChildClients()
{
return clientList;
}
@Override
public boolean doesDelegate()
{
return clientList.doesDelegate();
}
@Override
public boolean isListening()
{
return clientList.isListening();
}
@Override
public void addChildClient(Object client)
{
clientList.add(client);
}
@Override
public void removeChildClient(Object client)
{
clientList.remove(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);
}
}
}

@ -0,0 +1,77 @@
package mightypork.gamecore.gui.components;
import mightypork.utils.math.constraints.num.Num;
import mightypork.utils.math.constraints.rect.Rect;
import mightypork.utils.math.constraints.rect.RectBound;
import mightypork.utils.math.constraints.vect.Vect;
import mightypork.utils.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;
}
}

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

@ -0,0 +1,49 @@
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;
}
}
}

@ -0,0 +1,63 @@
package mightypork.gamecore.gui.components.input;
import java.util.Collection;
import mightypork.gamecore.gui.components.Component;
import mightypork.utils.eventbus.clients.ClientList;
import mightypork.utils.eventbus.clients.DelegatingClient;
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);
}
}

@ -0,0 +1,77 @@
package mightypork.gamecore.gui.components.input;
import mightypork.gamecore.core.App;
import mightypork.gamecore.graphics.fonts.IFont;
import mightypork.gamecore.gui.components.DynamicWidthComponent;
import mightypork.gamecore.gui.components.painters.TextPainter;
import mightypork.utils.math.AlignX;
import mightypork.utils.math.color.Color;
import mightypork.utils.math.color.pal.RGB;
import mightypork.utils.math.constraints.vect.Vect;
import mightypork.utils.math.constraints.vect.var.VectVar;
/**
* Menu-like button with shadow and push state
*
* @author Ondřej Hruška (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(IFont 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 (App.input().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);
}
}

@ -0,0 +1,42 @@
package mightypork.gamecore.gui.components.layout;
import mightypork.gamecore.gui.components.Component;
import mightypork.utils.math.constraints.rect.RectBound;
public class ColumnLayout extends GridLayout {
private int col = 0;
public ColumnLayout(int rows) {
this(null, rows);
}
public ColumnLayout(RectBound context, int cols) {
super(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;
}
}

@ -0,0 +1,36 @@
package mightypork.gamecore.gui.components.layout;
import mightypork.gamecore.gui.components.Component;
import mightypork.gamecore.gui.components.LayoutComponent;
import mightypork.utils.math.constraints.rect.RectBound;
/**
* Layout for components with arbitrary constraints.
*
* @author Ondřej Hruška (MightyPork)
*/
public class ConstraintLayout extends LayoutComponent {
public ConstraintLayout() {
}
public ConstraintLayout(RectBound context) {
super(context);
}
/**
* Add a component to the layout.<br>
* The component's rect must be set up manually.
*
* @param component
*/
public void add(Component component)
{
attach(component);
}
}

@ -0,0 +1,85 @@
package mightypork.gamecore.gui.components.layout;
import mightypork.gamecore.gui.components.Component;
import mightypork.gamecore.gui.components.LayoutComponent;
import mightypork.utils.math.AlignX;
import mightypork.utils.math.constraints.num.Num;
import mightypork.utils.math.constraints.rect.Rect;
import mightypork.utils.math.constraints.rect.RectBound;
/**
* Holder with same-sized columns, aligned to left or right
*
* @author Ondřej Hruška (MightyPork)
*/
public class FlowColumnLayout extends LayoutComponent {
private int col = 0;
private Num elementWidth;
private final AlignX align;
/**
* @param context context
* @param elementWidth width of all elements
* @param align component align. Legal values are LEFT and RIGHT.
*/
public FlowColumnLayout(RectBound context, Num elementWidth, AlignX align) {
super(context);
this.elementWidth = elementWidth;
this.align = align;
if (align != AlignX.LEFT && align != AlignX.RIGHT) {
throw new IllegalArgumentException("Can align only left or right.");
}
}
/**
* make a new holder.<br>
* Context must be assigned before rendering.
*
* @param elementWidth width of all elements
* @param align component align. Legal values are LEFT and RIGHT.
*/
public FlowColumnLayout(Num elementWidth, AlignX align) {
this(null, elementWidth, align);
}
/**
* Add an item
*
* @param elem
*/
public void add(final Component elem)
{
if (elem == null) return;
final Rect r;
switch (align) {
case LEFT:
r = leftEdge().growRight(elementWidth).moveX(elementWidth.mul(col++));
break;
case RIGHT:
r = rightEdge().growLeft(elementWidth).moveX(elementWidth.mul(-(col++)));
break;
default:
throw new IllegalArgumentException("Bad align.");
}
elem.setRect(r);
attach(elem);
}
public void setElementWidth(Num elementWidth)
{
this.elementWidth = elementWidth;
}
}

@ -0,0 +1,84 @@
package mightypork.gamecore.gui.components.layout;
import mightypork.gamecore.gui.components.Component;
import mightypork.gamecore.gui.components.LayoutComponent;
import mightypork.utils.math.AlignY;
import mightypork.utils.math.constraints.num.Num;
import mightypork.utils.math.constraints.rect.Rect;
import mightypork.utils.math.constraints.rect.RectBound;
/**
* Holder with same-sized rows, aligned to top or bottom
*
* @author Ondřej Hruška (MightyPork)
*/
public class FlowRowLayout extends LayoutComponent {
private int row = 0;
private Num elementHeight;
private final AlignY align;
/**
* @param context context
* @param elementHeight height of all elements
* @param align component align. Legal values are TOP and BOTTOM.
*/
public FlowRowLayout(RectBound context, Num elementHeight, AlignY align) {
super(context);
this.elementHeight = elementHeight;
this.align = align;
if (align != AlignY.TOP && align != AlignY.BOTTOM) {
throw new IllegalArgumentException("Can align only to top or bottom.");
}
}
/**
* make a new holder.<br>
* Context must be assigned before rendering.
*
* @param elementHeight height of all elements
* @param align component align. Legal values are TOP and BOTTOM.
*/
public FlowRowLayout(Num elementHeight, AlignY align) {
this(null, elementHeight, align);
}
/**
* Add an item
*
* @param elem
*/
public void add(final Component elem)
{
if (elem == null) return;
final Rect r;
switch (align) {
case TOP:
r = topEdge().growDown(elementHeight).moveY(elementHeight.mul(row++));
break;
case BOTTOM:
r = bottomEdge().growUp(elementHeight).moveY(elementHeight.mul(-(row++)));
break;
default:
throw new IllegalArgumentException("Bad align.");
}
elem.setRect(r);
attach(elem);
}
public void setElementHeight(Num elementHeight)
{
this.elementHeight = elementHeight;
}
}

@ -0,0 +1,78 @@
package mightypork.gamecore.gui.components.layout;
import mightypork.gamecore.gui.components.Component;
import mightypork.gamecore.gui.components.LayoutComponent;
import mightypork.utils.math.constraints.rect.RectBound;
import mightypork.utils.math.constraints.rect.builders.TiledRect;
/**
* Holder with table cells
*
* @author Ondřej Hruška (MightyPork)
*/
public class GridLayout extends LayoutComponent {
private final TiledRect tiler;
/**
* @param context context
* @param rows number of rows
* @param cols number of columns
*/
public GridLayout(RectBound context, int rows, int cols) {
super(context);
this.tiler = tiles(cols, rows);
}
/**
* make a new holder.<br>
* Context must be assigned before rendering.
*
* @param rows number of rows
* @param cols number of columns
*/
public GridLayout(int rows, int cols) {
this(null, rows, cols);
}
/**
* Add a row to the holder.
*
* @param row row (one-based)
* @param column column (one-based)
* @param elem added component
*/
public void put(Component elem, int row, int column)
{
if (elem == null) return;
elem.setRect(tiler.tile(column, row));
attach(elem);
}
/**
* Put with span
*
* @param elem
* @param row
* @param column
* @param rowspan
* @param colspan
*/
public void put(Component elem, int row, int column, int rowspan, int colspan)
{
if (elem == null) return;
elem.setRect(tiler.span(column, row, colspan, rowspan));
attach(elem);
}
}

@ -0,0 +1,18 @@
package mightypork.gamecore.gui.components.layout;
import mightypork.gamecore.gui.components.BaseComponent;
/**
* Invisible component that does nothing at all; Null object pattern
*
* @author Ondřej Hruška (MightyPork)
*/
public class NullComponent extends BaseComponent {
@Override
protected void renderComponent()
{
}
}

@ -0,0 +1,42 @@
package mightypork.gamecore.gui.components.layout;
import mightypork.gamecore.gui.components.Component;
import mightypork.utils.math.constraints.rect.RectBound;
public class RowLayout extends GridLayout {
private int row = 0;
public RowLayout(int rows) {
this(null, rows);
}
public RowLayout(RectBound context, int rows) {
super(context, rows, 1);
}
public void add(final Component elem)
{
add(elem, 1);
}
public void add(final Component elem, int rowSpan)
{
if (elem == null) return;
put(elem, row, 0, rowSpan, 1);
row += rowSpan;
}
public void skip(int rows)
{
row += rows;
}
}

@ -0,0 +1,78 @@
package mightypork.gamecore.gui.components.layout.linear;
import java.util.Collection;
import mightypork.gamecore.gui.components.Component;
import mightypork.gamecore.gui.components.LinearComponent;
import mightypork.utils.eventbus.clients.ClientList;
import mightypork.utils.eventbus.clients.DelegatingClient;
/**
* Converts a component into a linear component
*
* @author Ondřej Hruška (MightyPork)
*/
public abstract class AbstractLinearWrapper extends LinearComponent implements DelegatingClient {
protected final Component wrapped;
private final ClientList list;
/**
* @param wrapped wrapped component. Can be null.
*/
public AbstractLinearWrapper(Component wrapped) {
this.wrapped = wrapped;
if (wrapped != null) {
if (wrapped instanceof LinearComponent) {
((LinearComponent) wrapped).setHeight(height());
((LinearComponent) wrapped).setOrigin(origin());
} else {
wrapped.setRect(this);
}
}
list = new ClientList(wrapped);
}
@Override
protected void renderComponent()
{
if (wrapped != null) wrapped.render();
}
@Override
public Collection<?> getChildClients()
{
return list;
}
@Override
public boolean doesDelegate()
{
return true;
}
@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);
}
}

@ -0,0 +1,24 @@
package mightypork.gamecore.gui.components.layout.linear;
import mightypork.gamecore.gui.components.layout.NullComponent;
import mightypork.utils.math.constraints.num.Num;
/**
* Gap in linear layout
*
* @author Ondřej Hruška (MightyPork)
*/
public class LinearGap extends LinearRectangle {
public LinearGap(Num width) {
super(new NullComponent(), width);
}
public LinearGap(double heightPercent) {
this(Num.ZERO);
setWidth(height().perc(heightPercent));
}
}

@ -0,0 +1,93 @@
package mightypork.gamecore.gui.components.layout.linear;
import mightypork.gamecore.gui.components.DynamicWidthComponent;
import mightypork.gamecore.gui.components.LayoutComponent;
import mightypork.gamecore.gui.components.LinearComponent;
import mightypork.utils.math.AlignX;
import mightypork.utils.math.constraints.num.Num;
import mightypork.utils.math.constraints.num.batch.NumSum;
import mightypork.utils.math.constraints.rect.RectBound;
import mightypork.utils.math.constraints.vect.Vect;
import mightypork.utils.math.constraints.vect.proxy.VectAdapter;
/**
* Layout that aligns elements while taking into account their actual
* dimensions.<br>
* Useful eg. for buttons that stretch based on text length.
*
* @author Ondřej Hruška (MightyPork)
*/
public class LinearLayout extends LayoutComponent {
public LinearLayout(AlignX align) {
this.align = align;
}
public LinearLayout(RectBound context, AlignX align) {
super(context);
this.align = align;
}
private final NumSum totalWidth = new NumSum();
private final Vect leftAlignOrigin = LinearLayout.this.origin();
private final Vect centerAlignOrigin = LinearLayout.this.topCenter().sub(totalWidth.half(), Num.ZERO);
private final Vect rightAlignOrigin = LinearLayout.this.topRight().sub(totalWidth, Num.ZERO);
private final Vect leftMostOrigin = new VectAdapter() {
@Override
protected Vect getSource()
{
switch (align) {
default:
case LEFT:
return leftAlignOrigin;
case CENTER:
return centerAlignOrigin;
case RIGHT:
return rightAlignOrigin;
}
}
};
private Vect nextOrigin = leftMostOrigin;
private AlignX align = AlignX.LEFT;
public void add(DynamicWidthComponent dwcomp)
{
add(new LinearWrapper(dwcomp));
}
public void add(LinearComponent lincomp)
{
lincomp.setHeight(height());
lincomp.setOrigin(nextOrigin);
nextOrigin = nextOrigin.add(lincomp.width(), Num.ZERO);
totalWidth.addSummand(lincomp.width());
attach(lincomp);
}
public void setAlign(AlignX align)
{
this.align = align;
}
/**
* Add a gap.
*
* @param heightPercent percent of height for gap width
*/
public void gap(double heightPercent)
{
add(new LinearGap(heightPercent));
}
}

@ -0,0 +1,31 @@
package mightypork.gamecore.gui.components.layout.linear;
import mightypork.gamecore.gui.components.Component;
import mightypork.utils.math.constraints.num.Num;
public class LinearRectangle extends AbstractLinearWrapper {
private Num width;
public LinearRectangle(Component wrapped, Num width) {
super(wrapped);
this.width = width;
}
public void setWidth(Num width)
{
this.width = width;
}
@Override
public double computeWidth(double height)
{
return this.width.value();
}
}

@ -0,0 +1,20 @@
package mightypork.gamecore.gui.components.layout.linear;
import mightypork.gamecore.gui.components.Component;
public class LinearSquare extends AbstractLinearWrapper {
public LinearSquare(Component wrapped) {
super(wrapped);
}
@Override
public double computeWidth(double height)
{
return height;
}
}

@ -0,0 +1,20 @@
package mightypork.gamecore.gui.components.layout.linear;
import mightypork.gamecore.gui.components.DynamicWidthComponent;
public class LinearWrapper extends AbstractLinearWrapper {
public LinearWrapper(DynamicWidthComponent wrapped) {
super(wrapped);
}
@Override
public double computeWidth(double height)
{
return ((DynamicWidthComponent) wrapped).computeWidth(height);
}
}

@ -0,0 +1,46 @@
package mightypork.gamecore.gui.components.painters;
import mightypork.gamecore.core.App;
import mightypork.gamecore.graphics.textures.TxQuad;
import mightypork.gamecore.gui.components.BaseComponent;
import mightypork.gamecore.gui.components.DynamicWidthComponent;
/**
* Draws image in given rect
*
* @author Ondřej Hruška (MightyPork)
*/
public class ImagePainter extends BaseComponent implements DynamicWidthComponent {
private TxQuad txQuad;
/**
* @param txQuad drawn image
*/
public ImagePainter(TxQuad txQuad) {
this.txQuad = txQuad;
}
@Override
public void renderComponent()
{
App.gfx().quad(this, txQuad);
}
@Override
public double computeWidth(double height)
{
return (height / txQuad.uvs.height().value()) * txQuad.uvs.width().value();
}
public void setTxQuad(TxQuad txQuad)
{
this.txQuad = txQuad;
}
}

@ -0,0 +1,62 @@
package mightypork.gamecore.gui.components.painters;
import mightypork.gamecore.core.App;
import mightypork.gamecore.gui.components.BaseComponent;
import mightypork.utils.annotations.FactoryMethod;
import mightypork.utils.math.color.Color;
import mightypork.utils.math.color.Grad;
/**
* Draws image in given rect
*
* @author Ondřej Hruška (MightyPork)
*/
public class QuadPainter extends BaseComponent {
@FactoryMethod
public static QuadPainter gradH(Color colorLeft, Color colorRight)
{
return new QuadPainter(colorLeft, colorRight, colorRight, colorLeft);
}
@FactoryMethod
public static QuadPainter gradV(Color colorTop, Color colorBottom)
{
return new QuadPainter(colorTop, colorTop, colorBottom, colorBottom);
}
private final Grad grad;
/**
* Painter with solid color
*
* @param color
*/
public QuadPainter(Color color) {
this.grad = new Grad(color, color, color, color);
}
/**
* Painter with coloured vertices.
*
* @param leftTop
* @param rightTop
* @param leftBottom
* @param rightBottom
*/
public QuadPainter(Color leftTop, Color rightTop, Color leftBottom, Color rightBottom) {
this.grad = new Grad(leftTop, rightTop, rightBottom, leftBottom);
}
@Override
public void renderComponent()
{
App.gfx().quad(getRect(), grad);
}
}

@ -0,0 +1,163 @@
package mightypork.gamecore.gui.components.painters;
import mightypork.gamecore.core.App;
import mightypork.gamecore.graphics.fonts.FontRenderer;
import mightypork.gamecore.graphics.fonts.IFont;
import mightypork.gamecore.gui.components.BaseComponent;
import mightypork.gamecore.gui.components.DynamicWidthComponent;
import mightypork.utils.math.AlignX;
import mightypork.utils.math.color.Color;
import mightypork.utils.math.color.pal.RGB;
import mightypork.utils.math.constraints.num.Num;
import mightypork.utils.math.constraints.rect.Rect;
import mightypork.utils.math.constraints.vect.Vect;
import mightypork.utils.string.StringProvider;
import mightypork.utils.string.StringWrapper;
/**
* Text painting component.
*
* @author Ondřej Hruška (MightyPork)
*/
public class TextPainter extends BaseComponent implements DynamicWidthComponent {
private static final boolean DEBUG_FONT_RENDER = false;
private final FontRenderer font;
private Color color;
private AlignX align;
private StringProvider text;
private boolean shadow;
private double yPaddingPerc = 0;
private Color shadowColor = RGB.BLACK;
private Vect shadowOffset = Vect.make(2, 2);
/**
* @param font font to use
*/
public TextPainter(IFont font) {
this(font, AlignX.LEFT, RGB.WHITE);
}
public TextPainter(IFont font, Color color, String text) {
this(font, AlignX.LEFT, color, new StringWrapper(text));
}
public TextPainter(IFont font, Color color, StringProvider text) {
this(font, AlignX.LEFT, color, text);
}
public TextPainter(IFont font, Color color) {
this(font, AlignX.LEFT, color, (StringProvider) null);
}
public TextPainter(IFont font, AlignX align, Color color, String text) {
this(font, align, color, new StringWrapper(text));
}
public TextPainter(IFont font, AlignX align, Color color, StringProvider text) {
this.font = new FontRenderer(font);
this.color = color;
this.align = align;
this.text = text;
}
public TextPainter(IFont font, AlignX align, Color color) {
this(font, align, color, (StringProvider) null);
}
@Override
public void renderComponent()
{
if (text == null) return;
final String str = text.getString();
final Num shrY = height().perc(yPaddingPerc);
final Rect rect = getRect().shrink(Num.ZERO, shrY);
if (shadow) {
font.draw(str, rect.round(), align, shadowColor);
}
final Rect r = (shadow ? rect.move(shadowOffset.neg()) : rect).round();
font.draw(str, r, align, color);
if (DEBUG_FONT_RENDER) App.gfx().quad(r, RGB.PINK.withAlpha(0.4));
}
public void setShadow(Color color, Vect offset)
{
setShadow(true);
setShadowColor(color);
setShadowOffset(offset);
}
public void setShadow(boolean shadow)
{
this.shadow = shadow;
}
public void setShadowColor(Color shadowColor)
{
this.shadowColor = shadowColor;
}
public void setShadowOffset(Vect shadowOffset)
{
this.shadowOffset = shadowOffset;
}
public void setColor(Color color)
{
this.color = color;
}
public void setAlign(AlignX align)
{
this.align = align;
}
public void setText(String text)
{
this.text = new StringWrapper(text);
}
public void setText(StringProvider text)
{
this.text = text;
}
public void setVPaddingPercent(double percY)
{
yPaddingPerc = percY;
}
@Override
public double computeWidth(double height)
{
return font.getWidth(this.text.getString(), height * ((100 - yPaddingPerc * 2) / 100D));
}
}

@ -0,0 +1,30 @@
package mightypork.gamecore.gui.events;
import mightypork.utils.eventbus.BusEvent;
import mightypork.utils.eventbus.events.flags.DirectEvent;
import mightypork.utils.eventbus.events.flags.NonConsumableEvent;
import mightypork.utils.eventbus.events.flags.NonRejectableEvent;
/**
* Intended use is to notify UI component sub-clients that they should poll
* their cached constraints.
*
* @author Ondřej Hruška (MightyPork)
*/
@DirectEvent
@NonConsumableEvent
@NonRejectableEvent
public class LayoutChangeEvent extends BusEvent<LayoutChangeListener> {
public LayoutChangeEvent() {
}
@Override
public void handleBy(LayoutChangeListener handler)
{
handler.onLayoutChanged();
}
}

@ -0,0 +1,7 @@
package mightypork.gamecore.gui.events;
public interface LayoutChangeListener {
public void onLayoutChanged();
}

@ -0,0 +1,33 @@
package mightypork.gamecore.gui.events;
import mightypork.utils.eventbus.BusEvent;
import mightypork.utils.eventbus.events.flags.SingleReceiverEvent;
/**
* Request to change screen
*
* @author Ondřej Hruška (MightyPork)
*/
@SingleReceiverEvent
public class ScreenRequest extends BusEvent<ScreenRequestListener> {
private final String scrName;
/**
* @param screenKey screen name
*/
public ScreenRequest(String screenKey) {
scrName = screenKey;
}
@Override
public void handleBy(ScreenRequestListener handler)
{
handler.showScreen(scrName);
}
}

@ -0,0 +1,15 @@
package mightypork.gamecore.gui.events;
/**
* {@link ScreenRequest} listener
*
* @author Ondřej Hruška (MightyPork)
*/
public interface ScreenRequestListener {
/**
* @param key screen to show
*/
void showScreen(String key);
}

@ -0,0 +1,44 @@
package mightypork.gamecore.gui.events;
import mightypork.utils.eventbus.BusEvent;
import mightypork.utils.eventbus.events.flags.NonConsumableEvent;
import mightypork.utils.eventbus.events.flags.NotLoggedEvent;
import mightypork.utils.math.constraints.vect.Vect;
/**
* Screen resolution or mode was changed
*
* @author Ondřej Hruška (MightyPork)
*/
@NonConsumableEvent
@NotLoggedEvent
public class ViewportChangeEvent extends BusEvent<ViewportChangeListener> {
private final Vect screenSize;
/**
* @param size new screen size
*/
public ViewportChangeEvent(Vect size) {
this.screenSize = size;
}
/**
* @return new screen size
*/
public Vect getScreenSize()
{
return screenSize;
}
@Override
public void handleBy(ViewportChangeListener handler)
{
handler.onViewportChanged(this);
}
}

@ -0,0 +1,17 @@
package mightypork.gamecore.gui.events;
/**
* {@link ViewportChangeEvent} listener
*
* @author Ondřej Hruška (MightyPork)
*/
public interface ViewportChangeListener {
/**
* Handle event
*
* @param event
*/
void onViewportChanged(ViewportChangeEvent event);
}

@ -0,0 +1,112 @@
package mightypork.gamecore.gui.screens;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import mightypork.utils.eventbus.clients.DelegatingClient;
/**
* Screen with multiple instances of {@link ScreenLayer}
*
* @author Ondřej Hruška (MightyPork)
*/
public abstract class LayeredScreen extends Screen {
/**
* Wrapper for delegating client, to use custom client ordering.
*
* @author Ondřej Hruška (MightyPork)
*/
private class LayersClient implements DelegatingClient {
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public Collection getChildClients()
{
return layersByEventPriority;
}
@Override
public boolean doesDelegate()
{
return true;
}
}
private final List<ScreenLayer> layersByZIndex = new ArrayList<>();
private final List<ScreenLayer> layersByEventPriority = new ArrayList<>();
private final LayersClient layersClient = new LayersClient();
public LayeredScreen() {
addChildClient(layersClient);
}
@Override
protected void renderScreen()
{
for (final ScreenLayer layer : layersByZIndex) {
if (layer.isVisible()) layer.render();
}
}
/**
* Add a layer to the screen.
*
* @param layer
*/
protected void addLayer(ScreenLayer layer)
{
this.layersByZIndex.add(layer);
this.layersByEventPriority.add(layer);
Collections.sort(layersByEventPriority, new Comparator<Overlay>() {
@Override
public int compare(Overlay o1, Overlay o2)
{
return o2.getEventPriority() - o1.getEventPriority();
}
});
Collections.sort(layersByZIndex, new Comparator<Overlay>() {
@Override
public int compare(Overlay o1, Overlay o2)
{
return o1.getZIndex() - o2.getZIndex();
}
});
}
@Override
protected void onScreenEnter()
{
for (final ScreenLayer layer : layersByEventPriority) {
layer.onScreenEnter();
}
}
@Override
protected void onScreenLeave()
{
for (final ScreenLayer layer : layersByEventPriority) {
layer.onScreenLeave();
}
}
}

@ -0,0 +1,222 @@
package mightypork.gamecore.gui.screens;
import java.util.ArrayList;
import java.util.Collection;
import mightypork.gamecore.core.App;
import mightypork.gamecore.graphics.Renderable;
import mightypork.gamecore.gui.components.layout.ConstraintLayout;
import mightypork.gamecore.gui.events.LayoutChangeListener;
import mightypork.gamecore.input.KeyBinder;
import mightypork.gamecore.input.KeyBindingPool;
import mightypork.gamecore.input.KeyStroke;
import mightypork.gamecore.input.Trigger;
import mightypork.utils.annotations.Stub;
import mightypork.utils.eventbus.clients.BusNode;
import mightypork.utils.interfaces.Enableable;
import mightypork.utils.interfaces.Hideable;
import mightypork.utils.interfaces.Updateable;
import mightypork.utils.math.color.Color;
import mightypork.utils.math.constraints.num.Num;
import mightypork.utils.math.constraints.vect.Vect;
/**
* Abstract overlay.<br>
* Overlay is connected to event bus and is renderable.
*
* @author Ondřej Hruška (MightyPork)
*/
public abstract class Overlay extends BusNode implements Comparable<Overlay>, Updateable, Renderable, KeyBinder, Hideable, Enableable, LayoutChangeListener {
private boolean visible = true;
private boolean enabled = true;
private final KeyBindingPool keybindings = new KeyBindingPool();
/** Root layout, rendered and attached to the event bus. */
protected final ConstraintLayout root;
/** Constraint: Mouse position. */
protected final Vect mouse;
/** Extra rendered items (outside root) */
protected final Collection<Renderable> rendered = new ArrayList<>();
/** Extra updated items (outside root - those can just implement Updateable) */
protected final Collection<Updateable> updated = new ArrayList<>();
private Num alphaMul = Num.ONE;
public Overlay() {
this.mouse = App.input().getMousePos();
this.root = new ConstraintLayout(App.gfx().getRect());
addChildClient(root);
addChildClient(keybindings);
rendered.add(root);
}
@Override
public final void bindKey(KeyStroke stroke, Trigger edge, Runnable task)
{
keybindings.bindKey(stroke, edge, task);
}
@Override
public final void unbindKey(KeyStroke stroke)
{
keybindings.unbindKey(stroke);
}
@Override
public final boolean isVisible()
{
return visible;
}
@Override
public void setVisible(boolean visible)
{
if (visible != this.visible) {
this.visible = visible;
root.setVisible(visible);
}
}
@Override
public void setEnabled(boolean yes)
{
if (enabled != yes) {
this.enabled = yes;
root.setEnabled(yes);
}
}
@Override
public boolean isEnabled()
{
return enabled;
}
/**
* Get rendering layer
*
* @return higher = on top.
*/
@Stub
public abstract int getZIndex();
/**
* Get event bus listening priority - useful to block incoming events.
*
* @return higher = first.
*/
public int getEventPriority()
{
return getZIndex();
}
/**
* Render the overlay. The caller MUST check for visibility himself.
*/
@Override
public void render()
{
if (!isVisible()) return;
Color.pushAlpha(alphaMul);
for (final Renderable r : rendered) {
r.render();
}
Color.popAlpha();
}
@Override
public void update(double delta)
{
if (!isEnabled()) return;
for (final Updateable u : updated) {
u.update(delta);
}
}
@Override
public int compareTo(Overlay o)
{
return o.getEventPriority() - getEventPriority();
}
/**
* <p>
* Screen size changed.
* </p>
* <p>
* Layouts / components should listen for this event and update their cached
* constraints; components added to root or directly to this overlay as
* child clients will receive the event.
* </p>
*/
@Override
@Stub
public void onLayoutChanged()
{
}
public void setAlpha(Num alpha)
{
this.alphaMul = alpha;
}
public void setAlpha(double alpha)
{
this.alphaMul = Num.make(alpha);
}
public void show()
{
setVisible(true);
setEnabled(true);
}
public void hide()
{
setVisible(false);
setEnabled(false);
}
@Override
public boolean isListening()
{
return (isVisible() || isEnabled());
}
@Override
public boolean doesDelegate()
{
return isListening();
}
}

@ -0,0 +1,153 @@
package mightypork.gamecore.gui.screens;
import mightypork.gamecore.core.App;
import mightypork.gamecore.graphics.Renderable;
import mightypork.gamecore.gui.events.LayoutChangeEvent;
import mightypork.gamecore.gui.events.LayoutChangeListener;
import mightypork.gamecore.input.KeyBinder;
import mightypork.gamecore.input.KeyBindingPool;
import mightypork.gamecore.input.KeyStroke;
import mightypork.gamecore.input.Trigger;
import mightypork.utils.annotations.Stub;
import mightypork.utils.eventbus.clients.BusNode;
import mightypork.utils.math.constraints.rect.Rect;
import mightypork.utils.math.constraints.rect.RectBound;
/**
* Screen class.
*
* @author Ondřej Hruška (MightyPork)
*/
public abstract class Screen extends BusNode implements Renderable, RectBound, KeyBinder, LayoutChangeListener {
private final KeyBindingPool keybindings = new KeyBindingPool();
private volatile boolean active;
private volatile boolean needSetupViewport = false;
public Screen() {
// disable events initially
setListening(false);
addChildClient(keybindings);
}
private void fireLayoutChangeEvent()
{
App.bus().sendDirectToChildren(this, new LayoutChangeEvent());
}
@Override
public final void bindKey(KeyStroke stroke, Trigger edge, Runnable task)
{
keybindings.bindKey(stroke, edge, task);
}
@Override
public final void unbindKey(KeyStroke stroke)
{
keybindings.unbindKey(stroke);
}
/**
* Prepare for being shown
*
* @param shown true to show, false to hide
*/
public final void setActive(boolean shown)
{
if (shown) {
active = true;
needSetupViewport = true;
fireLayoutChangeEvent();
onScreenEnter();
// enable events
setListening(true);
} else {
onScreenLeave();
active = false;
// disable events
setListening(false);
}
}
/**
* @return true if screen is the current screen
*/
public final boolean isActive()
{
return active;
}
@Override
public void onLayoutChanged()
{
if (!isActive()) return;
needSetupViewport = true;
}
@Override
public final Rect getRect()
{
return App.gfx().getRect();
}
@Override
public void render()
{
if (!isActive()) return;
if (needSetupViewport) {
App.gfx().setupProjection();
}
App.gfx().pushState();
renderScreen();
App.gfx().popState();
}
/**
* Called when the screen becomes active
*/
@Stub
protected void onScreenEnter()
{
}
/**
* Called when the screen is no longer active
*/
@Stub
protected void onScreenLeave()
{
}
/**
* Render screen contents (context is ready for 2D rendering)
*/
protected abstract void renderScreen();
}

@ -0,0 +1,50 @@
package mightypork.gamecore.gui.screens;
import mightypork.utils.annotations.Stub;
/**
* Screen display layer
*
* @author Ondřej Hruška (MightyPork)
*/
public abstract class ScreenLayer extends Overlay {
private final Screen screen;
/**
* @param screen parent screen
*/
public ScreenLayer(Screen screen) {
this.screen = screen;
}
/**
* @return parent screen instance
*/
protected final Screen getScreen()
{
return screen;
}
/**
* Called when the screen becomes active
*/
@Stub
protected void onScreenEnter()
{
}
/**
* Called when the screen is no longer active
*/
@Stub
protected void onScreenLeave()
{
}
}

@ -0,0 +1,106 @@
package mightypork.gamecore.gui.screens;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeSet;
import mightypork.gamecore.core.App;
import mightypork.gamecore.graphics.Renderable;
import mightypork.gamecore.gui.events.LayoutChangeEvent;
import mightypork.gamecore.gui.events.ScreenRequestListener;
import mightypork.gamecore.gui.events.ViewportChangeEvent;
import mightypork.gamecore.gui.events.ViewportChangeListener;
import mightypork.utils.eventbus.clients.BusNode;
import mightypork.utils.logging.Log;
/**
* Game screens holder; Takes care of rendering and screen requests.
*
* @author Ondřej Hruška (MightyPork)
*/
public class ScreenRegistry extends BusNode implements ScreenRequestListener, ViewportChangeListener, Renderable {
private final Map<String, Screen> screens = new HashMap<>();
private final Collection<Overlay> overlays = new TreeSet<>();
private volatile Screen active = null;
/**
* Add a screen
*
* @param name screen key for calling
* @param screen added screen
*/
public void addScreen(String name, Screen screen)
{
screens.put(name, screen);
addChildClient(screen);
}
/**
* Add an overlay
*
* @param overlay added overlay
*/
public void addOverlay(Overlay overlay)
{
overlays.add(overlay);
addChildClient(overlay);
}
@Override
public void showScreen(String key)
{
Log.f3("Request to show screen \"" + key + "\"");
// find screen to show
final Screen toShow = screens.get(key);
if (toShow == null) {
throw new RuntimeException("Screen " + key + " not defined.");
}
// deactivate last screen
if (active != null) {
active.setActive(false);
}
// activate new screen
toShow.setActive(true);
active = toShow;
fireLayoutUpdateEvent();
}
@Override
public void render()
{
if (active != null) {
active.render();
for (final Overlay overlay : overlays) {
if (overlay.isVisible()) overlay.render();
}
}
}
@Override
public void onViewportChanged(ViewportChangeEvent event)
{
if (active != null) fireLayoutUpdateEvent();
}
private void fireLayoutUpdateEvent()
{
App.bus().sendDirectToChildren(this, new LayoutChangeEvent());
}
}

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

Loading…
Cancel
Save