25 Commits
Author SHA1 Message Date
MightyPork 4faabc203d improved readme 2014-07-29 14:22:02 +02:00
MightyPork cf096bcce5 Reflecting engine changes, some init task improvements 2014-07-29 14:14:24 +02:00
MightyPork e368877b6e refactoring & tmp workaround to allow close. 2014-07-29 04:06:50 +02:00
MightyPork 60e76ecfcc Adapted to use new gamecore. UGLY. to be done. Also no sounds. 2014-07-29 03:48:03 +02:00
MightyPork f0401d0184 Changes reflecting refactoring of related projects 2014-07-29 01:00:40 +02:00
MightyPork 137dbc21da cleanup. 2014-07-28 14:28:51 +02:00
MightyPork 84789ccf47 Fixed what could be fixed easily 2014-07-28 01:41:46 +02:00
MightyPork 23fe35db4a Removed LWJGL GameCore Backend and LWJGL from project 2014-07-28 01:17:55 +02:00
MightyPork da0737a4c6 Removed gamecore, added GameCore project dependency 2014-07-28 01:08:04 +02:00
MightyPork dda4aa171e Added a warning to readme 2014-07-27 19:38:55 +02:00
MightyPork ae57315df9 Some more cleaning of modules and App shutdown method. 2014-07-27 19:34:12 +02:00
MightyPork 3987b234bb Beautiful keys abstraction (decoupled from lwjgl key constants) 2014-07-27 19:04:14 +02:00
MightyPork a9b38b9417 Base of InputModule refactoring 2014-07-27 16:16:32 +02:00
MightyPork a4108f2caf cleanup and refactoring, package reorganization 2014-07-27 00:02:54 +02:00
MightyPork 50cbc9479b reorganization 2014-07-26 23:21:30 +02:00
MightyPork cafcf78cd2 Reflecting changes in Utils 2014-07-26 23:17:45 +02:00
MightyPork 87d0c9d500 some shifting around and refactoring. 2014-07-26 22:42:07 +02:00
MightyPork c5fa77d395 some javadoc fixes 2014-07-26 14:49:55 +02:00
MightyPork d3f392d689 audio module 2014-07-26 14:43:25 +02:00
MightyPork 3c0763a4c8 Unfinished huge refactoring towards backend modules system.
Done: Audio, Graphics.
Tbd: Input, cleanup Config, cleanup old BaseApp etc
2014-07-24 21:44:35 +02:00
MightyPork eee9ed14dc Some refactoring 2014-07-23 20:14:52 +02:00
MightyPork ec64ad21fc More refactoring towards backend system 2014-07-23 17:30:07 +02:00
MightyPork 7024fb4402 Refactoring of config inner classes 2014-07-22 17:02:55 +02:00
MightyPork 4bc4af553e Renamed save dir to .rogue 2014-07-21 08:55:31 +02:00
MightyPork f48797feb5 Update README.md 2014-06-05 20:37:40 +02:00
262 changed files with 1031 additions and 11443 deletions
+2 -30
View File
@@ -2,36 +2,8 @@
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
<classpathentry kind="lib" path="lib/jinput.jar" sourcepath="lib/lwjgl-source-2.8.4.zip">
<attributes>
<attribute name="org.eclipse.jdt.launching.CLASSPATH_ATTR_LIBRARY_PATH_ENTRY" value="Rogue/lib"/>
</attributes>
</classpathentry>
<classpathentry kind="lib" path="lib/jogg-0.0.7.jar">
<attributes>
<attribute name="org.eclipse.jdt.launching.CLASSPATH_ATTR_LIBRARY_PATH_ENTRY" value="Rogue/lib"/>
</attributes>
</classpathentry>
<classpathentry kind="lib" path="lib/jorbis-0.0.15.jar">
<attributes>
<attribute name="org.eclipse.jdt.launching.CLASSPATH_ATTR_LIBRARY_PATH_ENTRY" value="Rogue/lib"/>
</attributes>
</classpathentry>
<classpathentry kind="lib" path="lib/lwjgl_util.jar" sourcepath="lib/lwjgl-source-2.8.4.zip">
<attributes>
<attribute name="org.eclipse.jdt.launching.CLASSPATH_ATTR_LIBRARY_PATH_ENTRY" value="Rogue/lib"/>
</attributes>
</classpathentry>
<classpathentry kind="lib" path="lib/lwjgl.jar" sourcepath="lib/lwjgl-source-2.8.4.zip">
<attributes>
<attribute name="org.eclipse.jdt.launching.CLASSPATH_ATTR_LIBRARY_PATH_ENTRY" value="Rogue/lib"/>
</attributes>
</classpathentry>
<classpathentry kind="lib" path="lib/slick-util.jar" sourcepath="lib/slick-util-src.zip">
<attributes>
<attribute name="org.eclipse.jdt.launching.CLASSPATH_ATTR_LIBRARY_PATH_ENTRY" value="Rogue/lib"/>
</attributes>
</classpathentry>
<classpathentry combineaccessrules="false" kind="src" path="/MightyUtils"/>
<classpathentry combineaccessrules="false" kind="src" path="/GameCore"/>
<classpathentry combineaccessrules="false" kind="src" path="/GameCore-LWJGL"/>
<classpathentry kind="output" path="bin"/>
</classpath>
+1
View File
@@ -2,6 +2,7 @@
/target/
/~local/
/.rogue-save/
/rogue-workdir/
/build/out/*.jar
/build/in/*.jar
*.log
+18 -18
View File
@@ -2,24 +2,30 @@
**Read the [MANUAL](http://goo.gl/AU0IdI) if you want to play the game.**
It explains it way better than this text file.
NOTE: Master has experimental code
----------------------------------
The Master branch holds Rogue based on the new GameCore implementation (see "dependencies" below for link).
The latest **stable** version is in the branch `v5stable`. That branch is stable, debugged and has no dependencies. It's a standalone Eclipse project.
You can use `v5stable` to see the original source and try to build it, but further development of that branch is stopped. The master means the future.
DEPENDENCIES:
-------------
DEPENDENCIES
------------
- [MightyPork/ion](https://github.com/MightyPork/ion)
- [MightyPork/dynmath](https://github.com/MightyPork/dynmath)
If you intend to **build it from source**, you will need those Eclipse projects in your workspace:
- [MightyPork/gamecore](https://github.com/MightyPork/gamecore) - The "GameCore" game engine
- [MightyPork/gamecore-lwjgl](https://github.com/MightyPork/gamecore-lwjgl) - LWJGL backend for GameCore
- [MightyPork/mightyutils](https://github.com/MightyPork/mightyutils) - Game utils
---
The package `mightypork.gamecore` contains the framework part of the project.
The following is the original readme, applicable to `v5stable` version.
It is not yet ready to be published on it's own, but you can have a look at the
source if you're interested. The actual library repo will be set up later, with
more polished code, better docs, and cleaner hierarchy.
---
## Description
@@ -55,13 +61,7 @@ more polished code, better docs, and cleaner hierarchy.
## BUILDING FROM SOURCE
1. Export a jar with the following (all compiled sources and res) into `build/in/build.jar`:
- `mightypork.gamecore.*`
- `mightypork.rogue.*`
- `mightypork.ion.*`
- `mightypork.dynmath.*`
- `res` folder with Rogue resources
1. Export a jar with the compiled sources and /res into `build/in/build.jar`
2. Run `make` to create a stand-alone executable jar in `build/out/release.jar`
3. Use `make run` to execute it
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
-246
View File
@@ -1,246 +0,0 @@
package mightypork.gamecore.core;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import mightypork.gamecore.input.KeyStroke;
import mightypork.gamecore.input.Keys;
import mightypork.utils.files.config.Property;
import mightypork.utils.files.config.PropertyManager;
import mightypork.utils.logging.Log;
/**
* Static application configuration
*
* @author Ondřej Hruška (MightyPork)
*/
public class Config {
/**
* Config setup. Used to populate the config file.
*/
public static interface ConfigSetup {
void addOptions(PropertyManager prop);
}
/**
* Key configurator access
*/
public static class KeyOpts {
private KeyOpts()
{
}
public void add(String cfgKey, String dataString)
{
add(cfgKey, dataString, null);
}
public void add(String cfgKey, String dataString, String comment)
{
final KeyProperty kprop = new KeyProperty(prefixKey(cfgKey), KeyStroke.createFromDataString(dataString), comment);
strokes.put(prefixKey(cfgKey), kprop);
cfg.putProperty(kprop);
}
}
/**
* Key configurator
*/
public static interface KeySetup {
public void addKeys(KeyOpts keys);
}
/**
* Key property.<br>
* The stored value must be invariant ({@link KeyStroke} is mutable).
*
* @author Ondřej Hruška (MightyPork)
*/
public static class KeyProperty extends Property<KeyStroke> {
public KeyProperty(String key, KeyStroke defaultValue, String comment)
{
super(key, defaultValue, comment);
}
@Override
public KeyStroke decode(String string, KeyStroke defval)
{
if (string != null) {
// keep it invariant
final int backup_key = getValue().getKey();
final int backup_mod = getValue().getMod();
getValue().fromDataString(string);
if (getValue().getKey() == Keys.NONE) {
getValue().setTo(backup_key, backup_mod);
}
}
return getValue();
}
@Override
public String encode(KeyStroke value)
{
return value.toDataString();
}
@Override
public void setValue(Object value)
{
// keep it invariant
getValue().setTo(((KeyStroke) value).getKey(), ((KeyStroke) value).getMod());
}
}
public static Config.KeyOpts keyOpts = new Config.KeyOpts();
public static Map<String, KeyProperty> strokes = new HashMap<>();
private static PropertyManager cfg;
/**
* Initialize property manger for a file
*
* @param file config file
* @param comment file comment
*/
public static void init(File file, String comment)
{
cfg = new PropertyManager(file, comment);
cfg.cfgNewlineBeforeComments(true);
cfg.cfgSeparateSections(true);
}
/**
* populate config with keys from a key setup
*
* @param keys key setup to add
*/
public static void registerKeys(KeySetup keys)
{
keys.addKeys(Config.keyOpts);
}
/**
* Populate config with options from a config setup
*
* @param conf config setup to add
*/
public static void registerOptions(ConfigSetup conf)
{
conf.addOptions(Config.cfg);
}
/**
* Load config from file
*/
public static void load()
{
cfg.load();
}
/**
* Save config to file
*/
public static void save()
{
Log.f3("Saving config.");
cfg.save();
}
/**
* Get an option for key
*
* @param key
* @return option value
*/
public static <T> T getValue(String key)
{
try {
if (cfg.getProperty(key) == null) {
throw new IllegalArgumentException("No such property: " + key);
}
return cfg.getValue(key);
} catch (final ClassCastException cce) {
throw new RuntimeException("Property of incompatible type: " + key);
}
}
/**
* Set option to a value
*
* @param key option key
* @param value value to set
*/
public static <T> void setValue(String key, T value)
{
if (cfg.getProperty(key) == null) {
throw new IllegalArgumentException("No such property: " + key);
}
cfg.setValue(key, value);
}
private static String prefixKey(String cfgKey)
{
return "key." + cfgKey;
}
/**
* Get keystroke for name
*
* @param cfgKey stroke identifier in config file
* @return the stroke
*/
public static KeyStroke getKey(String cfgKey)
{
final KeyProperty kp = strokes.get(prefixKey(cfgKey));
if (kp == null) {
throw new IllegalArgumentException("No such stroke: " + cfgKey);
}
return kp.getValue();
}
/**
* Set a keystroke for name
*
* @param cfgKey stroke identifier in config file
* @param key stroke key
* @param mod stroke modifiers
*/
public static void setKey(String cfgKey, int key, int mod)
{
final Config.KeyProperty kp = strokes.get(prefixKey(cfgKey));
if (kp == null) {
throw new IllegalArgumentException("No such stroke: " + cfgKey);
}
kp.getValue().setTo(key, mod);
}
}
-117
View File
@@ -1,117 +0,0 @@
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 {
/**
* Route configurator.
*/
public static interface RouteSetup {
public void addRoutes(RouteOpts routeOpts);
}
/**
* Route configurator access
*/
public static class RouteOpts {
public void addPath(String alias, String path)
{
WorkDir.addPath(alias, path);
}
}
public static RouteOpts routeOpts = new RouteOpts();
private static File workdir;
private static Map<String, String> namedPaths = new HashMap<>();
public static void init(File workdir)
{
WorkDir.workdir = workdir;
}
/**
* Add a path alias (dir or file)
*
* @param alias path alias
* @param path path relative to workdir
*/
public static void addPath(String alias, String path)
{
namedPaths.put(alias, path);
}
public static void registerRoutes(RouteSetup rs)
{
rs.addRoutes(routeOpts);
}
/**
* Get workdir folder, create if not exists.
*
* @param path dir path relative to workdir
* @return dir file
*/
public static File getDir(String path)
{
if (namedPaths.containsKey(path)) path = namedPaths.get(path);
final File f = new File(workdir, path);
if (!f.exists()) {
if (!f.mkdirs()) {
Log.w("Could not create a directory: " + f + " (path: " + path + ")");
}
}
return f;
}
/**
* Get workdir file, create parent if not exists.
*
* @param path dir path relative to workdir
* @return dir file
*/
public static File getFile(String path)
{
if (namedPaths.containsKey(path)) path = namedPaths.get(path);
final File f = new File(workdir, path);
// create the parent dir
if (!f.getParent().equals(workdir)) {
f.getParentFile().mkdirs();
}
return f;
}
/**
* @return the workdir File
*/
public static File getWorkDir()
{
return workdir;
}
}
@@ -1,37 +0,0 @@
package mightypork.gamecore.core.events;
import mightypork.gamecore.core.modules.MainLoop;
import mightypork.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
* @param priority if true, skip other tasks in queue
*/
public MainLoopRequest(Runnable task, boolean priority)
{
this.task = task;
this.priority = priority;
}
@Override
public void handleBy(MainLoop handler)
{
handler.queueTask(task, priority);
}
}
@@ -1,32 +0,0 @@
package mightypork.gamecore.core.events;
import mightypork.gamecore.core.modules.MainLoop;
import mightypork.utils.eventbus.BusEvent;
import mightypork.utils.eventbus.events.flags.NonConsumableEvent;
import mightypork.utils.eventbus.events.flags.SingleReceiverEvent;
/**
* Shutdown request, non-interactive. Shutdown needs to execute on GL thread for
* display to deinit properly.
*
* @author Ondřej Hruška (MightyPork)
*/
@SingleReceiverEvent
@NonConsumableEvent
public class ShudownRequest extends BusEvent<MainLoop> {
@Override
public void handleBy(final MainLoop handler)
{
handler.queueTask(new Runnable() {
@Override
public void run()
{
handler.shutdown();
}
}, true);
}
}
@@ -1,32 +0,0 @@
package mightypork.gamecore.core.events;
import mightypork.utils.eventbus.BusEvent;
import mightypork.utils.eventbus.EventBus;
/**
* User quit request. This event is triggered when user clicks the "close"
* titlebar button, and if no client consumes it, the application will be shut
* down.
*
* @author Ondřej Hruška (MightyPork)
*/
public class UserQuitRequest extends BusEvent<UserQuitRequestListener> {
@Override
protected void handleBy(UserQuitRequestListener handler)
{
handler.onQuitRequest(this);
}
@Override
public void onDispatchComplete(EventBus bus)
{
if (!isConsumed()) {
bus.send(new ShudownRequest());
}
}
}
@@ -1,18 +0,0 @@
package mightypork.gamecore.core.events;
/**
* Quit request listener; implementing client can abort shutdown.
*
* @author Ondřej Hruška (MightyPork)
*/
public interface UserQuitRequestListener {
/**
* Intercept quit request.<br>
* Consume the event to abort shutdown (ie. ask user to save)
*
* @param event quit request event.
*/
void onQuitRequest(UserQuitRequest event);
}
@@ -1,41 +0,0 @@
package mightypork.gamecore.core.modules;
import mightypork.gamecore.input.InputSystem;
import mightypork.gamecore.render.DisplaySystem;
import mightypork.gamecore.resources.audio.SoundSystem;
import mightypork.utils.eventbus.BusAccess;
/**
* App interface visible to subsystems
*
* @author Ondřej Hruška (MightyPork)
*/
public interface AppAccess extends BusAccess {
/**
* @return sound system
*/
abstract SoundSystem getSoundSystem();
/**
* @return input system
*/
abstract InputSystem getInput();
/**
* @return display system
*/
abstract DisplaySystem getDisplay();
/**
* Quit to OS<br>
* Destroy app & exit VM
*/
abstract void shutdown();
}
@@ -1,65 +0,0 @@
package mightypork.gamecore.core.modules;
import mightypork.gamecore.input.InputSystem;
import mightypork.gamecore.render.DisplaySystem;
import mightypork.gamecore.resources.audio.SoundSystem;
import mightypork.utils.eventbus.EventBus;
/**
* App access adapter (defualt {@link AppAccess} implementation)
*
* @author Ondřej Hruška (MightyPork)
*/
public class AppAccessAdapter implements AppAccess {
private final AppAccess app;
/**
* @param app app access
*/
public AppAccessAdapter(AppAccess app)
{
if (app == null) throw new NullPointerException("AppAccess instance cannot be null.");
this.app = app;
}
@Override
public final SoundSystem getSoundSystem()
{
return app.getSoundSystem();
}
@Override
public final InputSystem getInput()
{
return app.getInput();
}
@Override
public final DisplaySystem getDisplay()
{
return app.getDisplay();
}
@Override
public final EventBus getEventBus()
{
return app.getEventBus();
}
@Override
public final void shutdown()
{
app.shutdown();
}
}
@@ -1,61 +0,0 @@
package mightypork.gamecore.core.modules;
import mightypork.gamecore.input.InputSystem;
import mightypork.gamecore.render.DisplaySystem;
import mightypork.gamecore.resources.audio.SoundSystem;
import mightypork.utils.eventbus.clients.RootBusNode;
/**
* App event bus client, to be used for subsystems, screens and anything that
* needs access to the eventbus and other systems; Attached directly to bus.
*
* @author Ondřej Hruška (MightyPork)
*/
public abstract class AppModule extends RootBusNode implements AppAccess {
private final AppAccess app;
/**
* Create a module
*
* @param app access to app systems
*/
public AppModule(AppAccess app)
{
super(app);
this.app = app;
}
@Override
public final SoundSystem getSoundSystem()
{
return app.getSoundSystem();
}
@Override
public final InputSystem getInput()
{
return app.getInput();
}
@Override
public final DisplaySystem getDisplay()
{
return app.getDisplay();
}
@Override
public final void shutdown()
{
app.shutdown();
}
}
@@ -1,63 +0,0 @@
package mightypork.gamecore.core.modules;
import mightypork.gamecore.input.InputSystem;
import mightypork.gamecore.render.DisplaySystem;
import mightypork.gamecore.resources.audio.SoundSystem;
import mightypork.utils.eventbus.clients.BusNode;
import mightypork.utils.eventbus.clients.DelegatingClient;
import mightypork.utils.eventbus.clients.RootBusNode;
/**
* Delegating bus client, to be attached to any {@link DelegatingClient}, such
* as a {@link RootBusNode}.
*
* @author Ondřej Hruška (MightyPork)
*/
public class AppSubModule extends BusNode implements AppAccess {
private final AppAccess app;
/**
* Create submodule
*
* @param app access to app systems
*/
public AppSubModule(AppAccess app)
{
super(app);
this.app = app;
}
@Override
public final SoundSystem getSoundSystem()
{
return app.getSoundSystem();
}
@Override
public final InputSystem getInput()
{
return app.getInput();
}
@Override
public final DisplaySystem getDisplay()
{
return app.getDisplay();
}
@Override
public final void shutdown()
{
app.shutdown();
}
}
@@ -1,566 +0,0 @@
package mightypork.gamecore.core.modules;
import java.io.File;
import java.io.IOException;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import javax.swing.JOptionPane;
import mightypork.gamecore.core.Config;
import mightypork.gamecore.core.Config.ConfigSetup;
import mightypork.gamecore.core.Config.KeySetup;
import mightypork.gamecore.core.WorkDir;
import mightypork.gamecore.core.WorkDir.RouteSetup;
import mightypork.gamecore.gui.screens.ScreenRegistry;
import mightypork.gamecore.gui.screens.impl.CrossfadeOverlay;
import mightypork.gamecore.input.InputSystem;
import mightypork.gamecore.render.DisplaySystem;
import mightypork.gamecore.resources.AsyncResourceLoader;
import mightypork.gamecore.resources.Res;
import mightypork.gamecore.resources.ResourceLoader;
import mightypork.gamecore.resources.ResourceSetup;
import mightypork.gamecore.resources.audio.SoundSystem;
import mightypork.gamecore.util.SlickLogRedirector;
import mightypork.utils.annotations.DefaultImpl;
import mightypork.utils.eventbus.EventBus;
import mightypork.utils.eventbus.events.DestroyEvent;
import mightypork.utils.files.InstanceLock;
import mightypork.utils.ion.Ion;
import mightypork.utils.ion.IonInput;
import mightypork.utils.ion.IonOutput;
import mightypork.utils.ion.IonizerBinary;
import mightypork.utils.logging.Log;
import mightypork.utils.logging.writers.LogWriter;
import mightypork.utils.math.algo.Coord;
import mightypork.utils.math.algo.Move;
/**
* 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 implements AppAccess, UncaughtExceptionHandler {
/**
* Init options holder class
*/
public class AppInitOptions {
private String logDir = "log";
private String logFilePrefix = "runtime";
private String screenshotDir = "screenshots";
private int logArchiveCount = 0;
private boolean busLogging = false;
private String configFile = "settings.cfg";
private String configComment = "Main config file";
public String lockFile = ".lock";
private final List<ResourceSetup> resourceLists = new ArrayList<>();
private final List<Config.KeySetup> keyLists = new ArrayList<>();
private final List<ConfigSetup> configLists = new ArrayList<>();
private final List<RouteSetup> routeLists = new ArrayList<>();
private ResourceLoader resourceLoader = new AsyncResourceLoader();
private Level logLevel = Level.ALL;
public boolean sigleInstance;
private Level logSoutLevel;
public void setConfigFile(String filename, String comment)
{
configFile = filename;
configComment = comment;
}
public void addConfig(ConfigSetup cfg)
{
configLists.add(cfg);
}
public void addKeys(Config.KeySetup keys)
{
keyLists.add(keys);
}
public void addRoutes(RouteSetup keys)
{
routeLists.add(keys);
}
public void addResources(ResourceSetup res)
{
resourceLists.add(res);
}
public void setBusLogging(boolean yes)
{
busLogging = yes;
}
public void setLogOptions(String logDir, String filePrefix, int archivedCount, Level logLevel)
{
this.logDir = logDir;
this.logFilePrefix = filePrefix;
this.logArchiveCount = archivedCount;
this.logLevel = logLevel;
}
public void setResourceLoader(ResourceLoader resLoader)
{
resourceLoader = resLoader;
}
public void setScreenshotDir(String path)
{
this.screenshotDir = path;
}
public void setLockFile(String lockFile)
{
this.lockFile = lockFile;
}
public void setLogLevel(Level logLevel, Level soutLevel)
{
this.logLevel = logLevel;
this.logSoutLevel = soutLevel;
}
}
// modules
private InputSystem inputSystem;
private DisplaySystem displaySystem;
private SoundSystem soundSystem;
private EventBus eventBus;
private MainLoop gameLoop;
private ScreenRegistry screenRegistry;
private boolean started = false;
private boolean lockObtained = false;
// init opt holder
private final AppInitOptions opt = new AppInitOptions();
/**
* Get init options
*
* @return opt holder
*/
public AppInitOptions opt()
{
if (started) {
throw new IllegalStateException("Cannot alter init options after starting the App.");
}
return opt;
}
public BaseApp(File workdir, boolean singleInstance)
{
WorkDir.init(workdir);
opt.sigleInstance = singleInstance;
}
/**
* Start the application
*/
public final void start()
{
Thread.setDefaultUncaughtExceptionHandler(this);
initialize();
Log.i("Starting main loop...");
// open first screen
started = true;
gameLoop.start();
}
/**
* Init the app
*/
protected void initialize()
{
if (opt.sigleInstance) initLock();
lockObtained = true;
for (final RouteSetup rs : opt.routeLists) {
WorkDir.registerRoutes(rs);
}
WorkDir.addPath("_screenshot_dir", opt.screenshotDir);
// apply configurations
Config.init(WorkDir.getFile(opt.configFile), opt.configComment);
// add keys to config
for (final KeySetup l : opt.keyLists) {
Config.registerKeys(l);
}
// add options to config
for (final ConfigSetup c : opt.configLists) {
Config.registerOptions(c);
}
Config.load();
/*
* Setup logging
*/
final LogWriter log = Log.create(opt.logFilePrefix, new File(WorkDir.getDir(opt.logDir), opt.logFilePrefix + ".log"), opt.logArchiveCount);
Log.setMainLogger(log);
Log.setLevel(opt.logLevel);
Log.setSysoutLevel(opt.logSoutLevel);
// connect slickutil to the logger
org.newdawn.slick.util.Log.setLogSystem(new SlickLogRedirector(log));
writeLogHeader();
Log.i("=== Starting initialization sequence ===");
// pre-init hook
Log.f2("Calling pre-init hook...");
preInit();
/*
* Event bus
*/
Log.f2("Starting Event Bus...");
eventBus = new EventBus();
eventBus.subscribe(this);
eventBus.detailedLogging = opt.busLogging;
/*
* Ionizables
*/
Log.f3("Initializing ION save system...");
registerIonizables();
/*
* Display
*/
Log.f2("Initializing Display System...");
displaySystem = new DisplaySystem(this);
initDisplay(displaySystem);
/*
* Audio
*/
Log.f2("Initializing Sound System...");
soundSystem = new SoundSystem(this);
initSoundSystem(soundSystem);
/*
* Input
*/
Log.f2("Initializing Input System...");
inputSystem = new InputSystem(this);
initInputSystem(inputSystem);
/*
* Prepare main loop
*/
Log.f1("Creating Screen Registry and Game Loop...");
screenRegistry = new ScreenRegistry(this);
gameLoop = createMainLoop();
gameLoop.setRootRenderable(screenRegistry);
/*
* Load resources
*
* Resources should be registered to registries, and AsyncResourceLoader will load them.
*/
Log.f1("Loading resources...");
if (opt.resourceLoader != null) {
opt.resourceLoader.init(this);
}
Res.init(this);
for (final ResourceSetup rl : opt.resourceLists) {
Res.load(rl);
}
/*
* Screen registry
*
* Must be after resources, because screens can request them during instantiation.
*/
Log.f2("Registering screens...");
initScreens(screenRegistry);
postInit();
Log.i("=== Initialization sequence completed ===");
}
protected void writeLogHeader()
{
logSystemInfo();
}
protected void logSystemInfo()
{
String txt = "";
txt += "\n### SYSTEM INFO ###\n\n";
txt += " Platform ...... " + System.getProperty("os.name") + "\n";
txt += " Runtime ....... " + System.getProperty("java.runtime.name") + "\n";
txt += " Java .......... " + System.getProperty("java.version") + "\n";
txt += " Launch path ... " + System.getProperty("user.dir") + "\n";
try {
txt += " Workdir ....... " + WorkDir.getWorkDir().getCanonicalPath() + "\n";
} catch (final IOException e) {
Log.e(e);
}
Log.i(txt);
}
protected void registerIonizables()
{
Ion.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);
}
});
}
/**
* Called at the beginning of the initialization sequence, right after lock
* was obtained.
*/
@DefaultImpl
protected void preInit()
{
}
/**
* Called at the end of init sequence, before main loop starts.
*/
@DefaultImpl
protected void postInit()
{
}
/**
* Create window and configure display system
*
* @param display
*/
@DefaultImpl
protected void initDisplay(DisplaySystem display)
{
display.createMainWindow(800, 600, true, false, "LWJGL game");
display.setTargetFps(60);
}
/**
* Configure sound system (ie. adjust volume)
*
* @param audio
*/
@DefaultImpl
protected void initSoundSystem(SoundSystem audio)
{
}
/**
* Register game screens to the registry.
*
* @param screens
*/
protected void initScreens(ScreenRegistry screens)
{
screens.addOverlay(new CrossfadeOverlay(this));
}
/**
* Create game loop instance
*
* @return the game loop.
*/
protected MainLoop createMainLoop()
{
return new MainLoop(this);
}
/*
* Try to obtain lock.
*/
private void initLock()
{
final File lock = WorkDir.getFile(opt.lockFile);
if (!InstanceLock.onFile(lock)) {
onLockError();
return;
}
}
@DefaultImpl
protected void initInputSystem(InputSystem input)
{
}
/**
* Triggered when lock cannot be obtained.<br>
* App should terminate gracefully.
*/
protected void onLockError()
{
Log.e("Could not obtain lock file.\nOnly one instance can run at a time.");
//@formatter:off
JOptionPane.showMessageDialog(
null,
"Another instance is already running.\n(Delete the "+opt.lockFile +" file in the working directory to override)",
"Lock Error",
JOptionPane.ERROR_MESSAGE
);
//@formatter:on
shutdown();
}
@Override
public final SoundSystem getSoundSystem()
{
return soundSystem;
}
@Override
public final InputSystem getInput()
{
return inputSystem;
}
@Override
public final DisplaySystem getDisplay()
{
return displaySystem;
}
@Override
public final EventBus getEventBus()
{
return eventBus;
}
protected void beforeShutdown()
{
if (lockObtained) Config.save();
}
@Override
public final void uncaughtException(Thread t, Throwable e)
{
onCrash(e);
}
protected void onCrash(Throwable e)
{
Log.e("The game has crashed.", e);
shutdown();
}
@Override
public final void shutdown()
{
beforeShutdown();
Log.i("Shutting down subsystems...");
try {
if (getEventBus() != null) {
getEventBus().send(new DestroyEvent());
getEventBus().destroy();
}
} catch (final Throwable e) {
Log.e(e);
}
Log.i("Terminating...");
System.exit(0);
}
}
@@ -1,155 +0,0 @@
package mightypork.gamecore.core.modules;
import java.util.Deque;
import java.util.concurrent.ConcurrentLinkedDeque;
import mightypork.gamecore.gui.screens.ScreenRegistry;
import mightypork.gamecore.render.Renderable;
import mightypork.gamecore.render.TaskTakeScreenshot;
import mightypork.gamecore.render.events.ScreenshotRequestListener;
import mightypork.utils.Support;
import mightypork.utils.annotations.DefaultImpl;
import mightypork.utils.eventbus.events.UpdateEvent;
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 AppModule implements ScreenshotRequestListener {
private static final double MAX_TIME_TASKS = 1 / 30D; // (avoid queue from hogging timing)
private static final double MAX_DELTA = 1 / 20D; // (skip huge gaps caused by loading resources etc)
private final Deque<Runnable> tasks = new ConcurrentLinkedDeque<>();
private TimerDelta timer;
private Renderable rootRenderable;
private volatile boolean running = true;
/**
* @param app {@link AppAccess} instance
*/
public MainLoop(AppAccess app)
{
super(app);
}
/**
* Set primary renderable
*
* @param rootRenderable main {@link Renderable}, typically a
* {@link ScreenRegistry}
*/
public void setRootRenderable(Renderable rootRenderable)
{
this.rootRenderable = rootRenderable;
}
/**
* Start the loop
*/
public void start()
{
timer = new TimerDelta();
while (running) {
getDisplay().beginFrame();
double delta = timer.getDelta();
if (delta > MAX_DELTA) {
Log.f3("(timing) Cropping delta: was " + delta + " , limit " + MAX_DELTA);
delta = MAX_DELTA;
}
getEventBus().sendDirect(new UpdateEvent(delta));
Runnable r;
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();
getDisplay().endFrame();
}
}
/**
* Called before render
*/
@DefaultImpl
protected void beforeRender()
{
//
}
/**
* Called after render
*/
@DefaultImpl
protected void afterRender()
{
//
}
@Override
protected void deinit()
{
running = false;
}
/**
* Add a task to queue to be executed in the main loop (OpenGL thread)
*
* @param request task
* @param priority if true, skip other tasks
*/
public synchronized void queueTask(Runnable request, boolean priority)
{
if (priority) {
tasks.addFirst(request);
} else {
tasks.addLast(request);
}
}
@Override
public void onScreenshotRequest()
{
// ensure it's started in main thread
queueTask(new Runnable() {
@Override
public void run()
{
Support.runAsThread(new TaskTakeScreenshot());
}
}, false);
}
}
-54
View File
@@ -1,54 +0,0 @@
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();
}
@@ -1,38 +0,0 @@
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);
}
}
@@ -1,17 +0,0 @@
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);
}
-12
View File
@@ -1,12 +0,0 @@
package mightypork.gamecore.gui;
/**
* Horizontal align sides
*
* @author Ondřej Hruška (MightyPork)
*/
public enum AlignX
{
LEFT, CENTER, RIGHT;
}
-12
View File
@@ -1,12 +0,0 @@
package mightypork.gamecore.gui;
/**
* Vertical align sides
*
* @author Ondřej Hruška (MightyPork)
*/
public enum AlignY
{
TOP, CENTER, BOTTOM;
}
-15
View File
@@ -1,15 +0,0 @@
package mightypork.gamecore.gui;
/**
* Element that can be hidden or visible
*
* @author Ondřej Hruška (MightyPork)
*/
public interface Hideable {
void setVisible(boolean yes);
boolean isVisible();
}
@@ -1,170 +0,0 @@
package mightypork.gamecore.gui.components;
import mightypork.gamecore.gui.events.LayoutChangeEvent;
import mightypork.gamecore.gui.events.LayoutChangeListener;
import mightypork.gamecore.input.InputSystem;
import mightypork.gamecore.render.Renderable;
import mightypork.utils.Support;
import mightypork.utils.annotations.DefaultImpl;
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 InputSystem.getMousePos().isInside(this);
}
/**
* Draw the component (it's visible)
*/
protected abstract void renderComponent();
@Override
@DefaultImpl
public void updateLayout()
{
}
@Override
public void setEnabled(boolean yes)
{
enabled = yes;
}
@Override
public boolean isEnabled()
{
return enabled && isIndirectlyEnabled();
}
@Override
public final void setAlpha(Num alpha)
{
this.alphaMul = alpha;
}
@Override
public final void setAlpha(double alpha)
{
this.alphaMul = Num.make(alpha);
}
@Override
public void setIndirectlyEnabled(boolean yes)
{
if (!yes) {
indirectDisableLevel++;
} else {
if (indirectDisableLevel > 0) indirectDisableLevel--;
}
}
@Override
public boolean isIndirectlyEnabled()
{
return indirectDisableLevel == 0;
}
@Override
public boolean isDirectlyEnabled()
{
return enabled;
}
}
@@ -1,95 +0,0 @@
package mightypork.gamecore.gui.components;
import mightypork.gamecore.gui.Hideable;
import mightypork.utils.interfaces.Enableable;
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();
}
@@ -1,7 +0,0 @@
package mightypork.gamecore.gui.components;
public interface DynamicWidthComponent extends Component {
double computeWidth(double height);
}
@@ -1,15 +0,0 @@
package mightypork.gamecore.gui.components;
import mightypork.utils.eventbus.clients.ToggleableClient;
import mightypork.utils.interfaces.Enableable;
public abstract class InputComponent extends BaseComponent implements Enableable, ToggleableClient {
@Override
public boolean isListening()
{
return isEnabled();
}
}
@@ -1,162 +0,0 @@
package mightypork.gamecore.gui.components;
import java.util.Collection;
import java.util.LinkedList;
import mightypork.gamecore.core.modules.AppAccess;
import mightypork.gamecore.core.modules.AppSubModule;
import mightypork.gamecore.input.InputSystem;
import mightypork.gamecore.render.DisplaySystem;
import mightypork.gamecore.resources.audio.SoundSystem;
import mightypork.utils.eventbus.EventBus;
import mightypork.utils.eventbus.clients.ClientHub;
import mightypork.utils.math.constraints.rect.RectBound;
public abstract class LayoutComponent extends BaseComponent implements ClientHub, AppAccess {
private final AppSubModule subModule;
final LinkedList<Component> components = new LinkedList<>();
public LayoutComponent(AppAccess app, RectBound context)
{
this.subModule = new AppSubModule(app);
setRect(context);
enableCaching(true); // layout is typically updated only when screen resizes.
}
public LayoutComponent(AppAccess app)
{
this(app, null);
}
@Override
public EventBus getEventBus()
{
return subModule.getEventBus();
}
@Override
public Collection<Object> getChildClients()
{
return subModule.getChildClients();
}
@Override
public boolean doesDelegate()
{
return subModule.doesDelegate();
}
@Override
public boolean isListening()
{
return subModule.isListening();
}
@Override
public SoundSystem getSoundSystem()
{
return subModule.getSoundSystem();
}
@Override
public InputSystem getInput()
{
return subModule.getInput();
}
@Override
public DisplaySystem getDisplay()
{
return subModule.getDisplay();
}
@Override
public void shutdown()
{
subModule.shutdown();
}
@Override
public void addChildClient(Object client)
{
subModule.addChildClient(client);
}
@Override
public void removeChildClient(Object client)
{
subModule.removeChildClient(client);
}
@Override
public void setEnabled(boolean yes)
{
if (isDirectlyEnabled() != yes) {
super.setEnabled(yes);
for (final Component c : components) {
c.setIndirectlyEnabled(yes);
}
}
}
/**
* Connect to bus and add to element list
*
* @param component added component, whose context has already been set.
*/
protected final void attach(Component component)
{
if (component == null) return;
if (component == this) throw new IllegalArgumentException("Uruboros. (infinite recursion evaded)");
components.add(component);
addChildClient(component);
}
@Override
public void renderComponent()
{
for (final Component cmp : components) {
cmp.render();
}
}
@Override
public void updateLayout()
{
for (final Component cmp : components) {
cmp.updateLayout();
}
}
@Override
public void setIndirectlyEnabled(boolean yes)
{
super.setIndirectlyEnabled(yes);
for (final Component cmp : components) {
cmp.setIndirectlyEnabled(yes);
}
}
}
@@ -1,78 +0,0 @@
package mightypork.gamecore.gui.components;
import mightypork.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;
}
}
@@ -1,28 +0,0 @@
package mightypork.gamecore.gui.components;
import mightypork.gamecore.render.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);
}
@@ -1,49 +0,0 @@
package mightypork.gamecore.gui.components.input;
import mightypork.gamecore.gui.Action;
import mightypork.gamecore.gui.ActionTrigger;
import mightypork.gamecore.gui.components.InputComponent;
import mightypork.gamecore.input.events.MouseButtonEvent;
import mightypork.gamecore.input.events.MouseButtonHandler;
public abstract class ClickableComponent extends InputComponent implements ActionTrigger, MouseButtonHandler {
protected boolean btnDownOver;
private Action action;
@Override
public void setAction(Action action)
{
this.action = action;
}
protected void triggerAction()
{
if (action != null && isEnabled()) action.run();
}
@Override
public void receive(MouseButtonEvent event)
{
if (!event.isButtonEvent()) return;
if (event.isDown()) {
btnDownOver = event.isOver(this);
}
if (event.isUp()) {
if (btnDownOver && event.isOver(this)) {
triggerAction();
event.consume();
}
btnDownOver = false;
}
}
}
@@ -1,64 +0,0 @@
package mightypork.gamecore.gui.components.input;
import java.util.Collection;
import mightypork.gamecore.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);
}
}
@@ -1,78 +0,0 @@
package mightypork.gamecore.gui.components.input;
import mightypork.gamecore.gui.AlignX;
import mightypork.gamecore.gui.components.DynamicWidthComponent;
import mightypork.gamecore.gui.components.painters.TextPainter;
import mightypork.gamecore.input.InputSystem;
import mightypork.gamecore.resources.fonts.GLFont;
import mightypork.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(GLFont font, String text, Color color)
{
this.color = color;
this.textPainter = new TextPainter(font, AlignX.CENTER, this.color, text);
this.textPainter.setRect(this);
this.textPainter.setShadow(RGB.BLACK_30, offset);
textPainter.setVPaddingPercent(5);
}
@Override
protected void renderComponent()
{
if (isMouseOver()) {
if (InputSystem.isMouseButtonDown(0)) {
offset.setTo(offsetUnder);
} else {
offset.setTo(hoverMove ? offsetOver : offsetPassive);
}
} else {
offset.setTo(offsetPassive);
}
textPainter.render();
}
/**
* Disable offset change on hover
*/
public void disableHoverEffect()
{
hoverMove = false;
}
@Override
public double computeWidth(double height)
{
return textPainter.computeWidth(height);
}
}
@@ -1,45 +0,0 @@
package mightypork.gamecore.gui.components.layout;
import mightypork.gamecore.core.modules.AppAccess;
import mightypork.gamecore.gui.components.Component;
import mightypork.utils.math.constraints.rect.RectBound;
public class ColumnLayout extends GridLayout {
private int col = 0;
public ColumnLayout(AppAccess app, int rows)
{
this(app, null, rows);
}
public ColumnLayout(AppAccess app, RectBound context, int cols)
{
super(app, context, 1, cols);
}
public void add(final Component elem)
{
add(elem, 1);
}
public void add(final Component elem, int colSpan)
{
if (elem == null) return;
put(elem, 0, col, 1, colSpan);
col += colSpan;
}
public void skip(int cols)
{
col += cols;
}
}
@@ -1,40 +0,0 @@
package mightypork.gamecore.gui.components.layout;
import mightypork.gamecore.core.modules.AppAccess;
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(AppAccess app)
{
super(app);
}
public ConstraintLayout(AppAccess app, RectBound context)
{
super(app, 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);
}
}
@@ -1,90 +0,0 @@
package mightypork.gamecore.gui.components.layout;
import mightypork.gamecore.core.modules.AppAccess;
import mightypork.gamecore.gui.AlignX;
import mightypork.gamecore.gui.components.Component;
import mightypork.gamecore.gui.components.LayoutComponent;
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 app app access
* @param context context
* @param elementWidth width of all elements
* @param align component align. Legal values are LEFT and RIGHT.
*/
public FlowColumnLayout(AppAccess app, RectBound context, Num elementWidth, AlignX align)
{
super(app, 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 app app access
* @param elementWidth width of all elements
* @param align component align. Legal values are LEFT and RIGHT.
*/
public FlowColumnLayout(AppAccess app, Num elementWidth, AlignX align)
{
this(app, 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;
}
}
@@ -1,89 +0,0 @@
package mightypork.gamecore.gui.components.layout;
import mightypork.gamecore.core.modules.AppAccess;
import mightypork.gamecore.gui.AlignY;
import mightypork.gamecore.gui.components.Component;
import mightypork.gamecore.gui.components.LayoutComponent;
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 app app access
* @param context context
* @param elementHeight height of all elements
* @param align component align. Legal values are TOP and BOTTOM.
*/
public FlowRowLayout(AppAccess app, RectBound context, Num elementHeight, AlignY align)
{
super(app, 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 app app access
* @param elementHeight height of all elements
* @param align component align. Legal values are TOP and BOTTOM.
*/
public FlowRowLayout(AppAccess app, Num elementHeight, AlignY align)
{
this(app, 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;
}
}
@@ -1,83 +0,0 @@
package mightypork.gamecore.gui.components.layout;
import mightypork.gamecore.core.modules.AppAccess;
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 app app access
* @param context context
* @param rows number of rows
* @param cols number of columns
*/
public GridLayout(AppAccess app, RectBound context, int rows, int cols)
{
super(app, context);
this.tiler = tiles(cols, rows);
}
/**
* make a new holder.<br>
* Context must be assigned before rendering.
*
* @param app app access
* @param rows number of rows
* @param cols number of columns
*/
public GridLayout(AppAccess app, int rows, int cols)
{
this(app, 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);
}
}
@@ -1,18 +0,0 @@
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()
{
}
}
@@ -1,45 +0,0 @@
package mightypork.gamecore.gui.components.layout;
import mightypork.gamecore.core.modules.AppAccess;
import mightypork.gamecore.gui.components.Component;
import mightypork.utils.math.constraints.rect.RectBound;
public class RowLayout extends GridLayout {
private int row = 0;
public RowLayout(AppAccess app, int rows)
{
this(app, null, rows);
}
public RowLayout(AppAccess app, RectBound context, int rows)
{
super(app, 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;
}
}
@@ -1,79 +0,0 @@
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);
}
}
@@ -1,26 +0,0 @@
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));
}
}
@@ -1,90 +0,0 @@
package mightypork.gamecore.gui.components.layout.linear;
import mightypork.gamecore.core.modules.AppAccess;
import mightypork.gamecore.gui.AlignX;
import mightypork.gamecore.gui.components.DynamicWidthComponent;
import mightypork.gamecore.gui.components.LayoutComponent;
import mightypork.gamecore.gui.components.LinearComponent;
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;
public class LinearLayout extends LayoutComponent {
public LinearLayout(AppAccess app, AlignX align)
{
super(app);
this.align = align;
}
public LinearLayout(AppAccess app, RectBound context, AlignX align)
{
super(app, 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));
}
}
@@ -1,32 +0,0 @@
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();
}
}
@@ -1,21 +0,0 @@
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;
}
}
@@ -1,21 +0,0 @@
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);
}
}
@@ -1,47 +0,0 @@
package mightypork.gamecore.gui.components.painters;
import mightypork.gamecore.gui.components.BaseComponent;
import mightypork.gamecore.gui.components.DynamicWidthComponent;
import mightypork.gamecore.render.Render;
import mightypork.gamecore.resources.textures.TxQuad;
/**
* 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()
{
Render.quadTextured(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;
}
}
@@ -1,72 +0,0 @@
package mightypork.gamecore.gui.components.painters;
import mightypork.gamecore.gui.components.BaseComponent;
import mightypork.gamecore.render.Render;
import mightypork.utils.annotations.FactoryMethod;
import mightypork.utils.math.color.Color;
/**
* 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 Color colorHMinVMin;
private final Color colorHMaxVMin;
private final Color colorHMaxVMax;
private final Color colorHMinVMax;
/**
* Painter with solid color
*
* @param color
*/
public QuadPainter(Color color)
{
this.colorHMinVMin = color;
this.colorHMaxVMin = color;
this.colorHMaxVMax = color;
this.colorHMinVMax = color;
}
/**
* Painter with coloured vertices.
*
* @param colorHMinVMin
* @param colorHMaxVMin
* @param colorHMaxVMax
* @param colorHMinVMax
*/
public QuadPainter(Color colorHMinVMin, Color colorHMaxVMin, Color colorHMaxVMax, Color colorHMinVMax)
{
this.colorHMinVMin = colorHMinVMin;
this.colorHMaxVMin = colorHMaxVMin;
this.colorHMaxVMax = colorHMaxVMax;
this.colorHMinVMax = colorHMinVMax;
}
@Override
public void renderComponent()
{
Render.quadColor(getRect(), colorHMinVMin, colorHMaxVMin, colorHMaxVMax, colorHMinVMax);
}
}
@@ -1,170 +0,0 @@
package mightypork.gamecore.gui.components.painters;
import mightypork.gamecore.gui.AlignX;
import mightypork.gamecore.gui.components.BaseComponent;
import mightypork.gamecore.gui.components.DynamicWidthComponent;
import mightypork.gamecore.render.Render;
import mightypork.gamecore.resources.fonts.FontRenderer;
import mightypork.gamecore.resources.fonts.GLFont;
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(GLFont font)
{
this(font, AlignX.LEFT, RGB.WHITE);
}
public TextPainter(GLFont font, Color color, String text)
{
this(font, AlignX.LEFT, color, new StringWrapper(text));
}
public TextPainter(GLFont font, Color color, StringProvider text)
{
this(font, AlignX.LEFT, color, text);
}
public TextPainter(GLFont font, Color color)
{
this(font, AlignX.LEFT, color, (StringProvider) null);
}
public TextPainter(GLFont font, AlignX align, Color color, String text)
{
this(font, align, color, new StringWrapper(text));
}
public TextPainter(GLFont font, AlignX align, Color color, StringProvider text)
{
this.font = new FontRenderer(font);
this.color = color;
this.align = align;
this.text = text;
}
public TextPainter(GLFont 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) Render.quadColor(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));
}
}
@@ -1,31 +0,0 @@
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();
}
}
@@ -1,7 +0,0 @@
package mightypork.gamecore.gui.events;
public interface LayoutChangeListener {
public void onLayoutChanged();
}
@@ -1,34 +0,0 @@
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);
}
}
@@ -1,15 +0,0 @@
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);
}
@@ -1,118 +0,0 @@
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.gamecore.core.modules.AppAccess;
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();
/**
* @param app app access
*/
public LayeredScreen(AppAccess app)
{
super(app);
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();
}
}
}
@@ -1,225 +0,0 @@
package mightypork.gamecore.gui.screens;
import java.util.ArrayList;
import java.util.Collection;
import mightypork.gamecore.core.modules.AppAccess;
import mightypork.gamecore.core.modules.AppSubModule;
import mightypork.gamecore.gui.Hideable;
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.KeyStroke.Edge;
import mightypork.gamecore.render.Renderable;
import mightypork.utils.annotations.DefaultImpl;
import mightypork.utils.interfaces.Enableable;
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 AppSubModule 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(AppAccess app)
{
super(app);
this.mouse = getInput().getMousePos();
this.root = new ConstraintLayout(app, getDisplay());
addChildClient(root);
addChildClient(keybindings);
rendered.add(root);
}
@Override
public final void bindKey(KeyStroke stroke, Edge 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.
*/
@DefaultImpl
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
@DefaultImpl
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();
}
}
@@ -1,160 +0,0 @@
package mightypork.gamecore.gui.screens;
import mightypork.gamecore.core.modules.AppAccess;
import mightypork.gamecore.core.modules.AppSubModule;
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.KeyStroke.Edge;
import mightypork.gamecore.render.DisplaySystem;
import mightypork.gamecore.render.Render;
import mightypork.gamecore.render.Renderable;
import mightypork.utils.annotations.DefaultImpl;
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 AppSubModule implements Renderable, RectBound, KeyBinder, LayoutChangeListener {
private final KeyBindingPool keybindings = new KeyBindingPool();
private volatile boolean active;
private volatile boolean needSetupViewport = false;
/**
* @param app app access
*/
public Screen(AppAccess app)
{
super(app);
// disable events initially
setListening(false);
addChildClient(keybindings);
}
private void fireLayoutChangeEvent()
{
getEventBus().sendDirectToChildren(this, new LayoutChangeEvent());
}
@Override
public final void bindKey(KeyStroke stroke, Edge 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 getDisplay().getRect();
}
@Override
public void render()
{
if (!isActive()) return;
if (needSetupViewport) {
Render.setupOrtho(DisplaySystem.getSize());
}
Render.pushState();
renderScreen();
Render.popState();
}
/**
* Called when the screen becomes active
*/
@DefaultImpl
protected void onScreenEnter()
{
}
/**
* Called when the screen is no longer active
*/
@DefaultImpl
protected void onScreenLeave()
{
}
/**
* Render screen contents (context is ready for 2D rendering)
*/
protected abstract void renderScreen();
}
@@ -1,53 +0,0 @@
package mightypork.gamecore.gui.screens;
import mightypork.utils.annotations.DefaultImpl;
/**
* 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)
{
super(screen); // screen as AppAccess
this.screen = screen;
}
/**
* @return parent screen instance
*/
protected final Screen getScreen()
{
return screen;
}
/**
* Called when the screen becomes active
*/
@DefaultImpl
protected void onScreenEnter()
{
}
/**
* Called when the screen is no longer active
*/
@DefaultImpl
protected void onScreenLeave()
{
}
}
@@ -1,124 +0,0 @@
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.modules.AppAccess;
import mightypork.gamecore.core.modules.AppModule;
import mightypork.gamecore.gui.events.LayoutChangeEvent;
import mightypork.gamecore.gui.events.ScreenRequestListener;
import mightypork.gamecore.render.Renderable;
import mightypork.gamecore.render.events.ViewportChangeEvent;
import mightypork.gamecore.render.events.ViewportChangeListener;
import mightypork.utils.annotations.DefaultImpl;
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 AppModule implements ScreenRequestListener, ViewportChangeListener, Renderable {
private final Map<String, Screen> screens = new HashMap<>();
private final Collection<Overlay> overlays = new TreeSet<>();
private volatile Screen active = null;
/**
* @param app app access
*/
public ScreenRegistry(AppAccess app)
{
super(app);
}
/**
* 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
@DefaultImpl
protected void deinit()
{
//
}
@Override
public void onViewportChanged(ViewportChangeEvent event)
{
if (active != null) fireLayoutUpdateEvent();
}
private void fireLayoutUpdateEvent()
{
getEventBus().sendDirectToChildren(this, new LayoutChangeEvent());
}
}
@@ -1,87 +0,0 @@
package mightypork.gamecore.gui.screens.impl;
import mightypork.gamecore.core.events.ShudownRequest;
import mightypork.gamecore.core.modules.AppAccess;
import mightypork.gamecore.gui.components.painters.QuadPainter;
import mightypork.gamecore.gui.events.ScreenRequest;
import mightypork.gamecore.gui.screens.Overlay;
import mightypork.utils.math.animation.Easing;
import mightypork.utils.math.animation.NumAnimated;
import mightypork.utils.math.color.pal.RGB;
import mightypork.utils.math.timing.TimedTask;
/**
* Overlay used for cross-fading between screens
*
* @author Ondřej Hruška (MightyPork)
*/
public class CrossfadeOverlay extends Overlay {
private static final double T_IN = 0.4;
private static final double T_OUT = 0.6;
NumAnimated alpha = new NumAnimated(0);
String requestedScreenName;
TimedTask revealTask = new TimedTask() {
@Override
public void run()
{
if (requestedScreenName == null) {
getEventBus().send(new ShudownRequest());
} else {
getEventBus().send(new ScreenRequest(requestedScreenName));
}
alpha.setEasing(Easing.SINE_OUT);
alpha.fadeOut(T_OUT);
}
};
public CrossfadeOverlay(AppAccess app)
{
super(app);
final QuadPainter qp = new QuadPainter(RGB.BLACK); // TODO allow custom colors
qp.setRect(root);
root.add(qp);
updated.add(alpha);
updated.add(revealTask);
setAlpha(alpha);
}
@Override
public int getZIndex()
{
return 10000; // not too high, so app can put something on top
}
public void goToScreen(String screen, boolean fromDark)
{
requestedScreenName = screen;
if (screen == null) {
// going for halt
getSoundSystem().fadeOutAllLoops();
}
if (fromDark) {
alpha.setTo(1);
revealTask.run();
} else {
revealTask.start(T_IN);
alpha.setEasing(Easing.SINE_IN);
alpha.fadeIn(T_IN);
}
}
}
@@ -1,47 +0,0 @@
package mightypork.gamecore.gui.screens.impl;
import mightypork.utils.eventbus.BusEvent;
import mightypork.utils.eventbus.events.flags.SingleReceiverEvent;
/**
* @author Ondřej Hruška (MightyPork)
*/
@SingleReceiverEvent
public class CrossfadeRequest extends BusEvent<CrossfadeOverlay> {
private final String screen;
private final boolean fromDark;
/**
* @param screen screen key to show. Null = exit the app.
* @param fromDark true to fade from full black (ie. start of the game)
*/
public CrossfadeRequest(String screen, boolean fromDark)
{
super();
this.screen = screen;
this.fromDark = fromDark;
}
/**
* @param screen screen key to show. Null = exit the app.
*/
public CrossfadeRequest(String screen)
{
super();
this.screen = screen;
this.fromDark = false;
}
@Override
public void handleBy(CrossfadeOverlay handler)
{
handler.goToScreen(screen, fromDark);
}
}
@@ -1,153 +0,0 @@
package mightypork.gamecore.gui.screens.impl;
import mightypork.gamecore.gui.screens.Screen;
import mightypork.gamecore.gui.screens.ScreenLayer;
import mightypork.utils.annotations.DefaultImpl;
import mightypork.utils.math.animation.Easing;
import mightypork.utils.math.animation.NumAnimated;
import mightypork.utils.math.timing.TimedTask;
/**
* Layer that smoothly appears/disappears when shown/hidden
*
* @author Ondřej Hruška (MightyPork)
*/
public abstract class FadingLayer extends ScreenLayer {
private final NumAnimated numa;
private final TimedTask hideTimer = new TimedTask() {
@Override
public void run()
{
FadingLayer.super.hide();
fadingOut = false;
onHideFinished();
}
};
private final TimedTask showTimer = new TimedTask() {
@Override
public void run()
{
fadingIn = false;
onShowFinished();
}
};
private boolean fadingIn = false;
private boolean fadingOut = false;
/**
* Create with default fading time and effect
*
* @param screen
*/
public FadingLayer(Screen screen)
{
this(screen, new NumAnimated(1, Easing.QUADRATIC_OUT, 0.3));
}
/**
* @param screen
* @param easingAnim the animation num
*/
public FadingLayer(Screen screen, NumAnimated easingAnim)
{
super(screen);
numa = easingAnim;
updated.add(numa);
updated.add(hideTimer);
updated.add(showTimer);
setAlpha(numa);
}
/**
* Called after the fade-out was completed
*/
@DefaultImpl
protected void onHideFinished()
{
}
/**
* Called after the fade-in was completed
*/
@DefaultImpl
protected void onShowFinished()
{
}
/**
* Show with fading
*/
@Override
public void show()
{
if (fadingIn) return;
if (!isVisible() || fadingOut) {
super.show();
numa.fadeIn();
hideTimer.stop();
showTimer.start(numa.getDefaultDuration());
fadingOut = false;
fadingIn = true;
}
}
/**
* Hide without fading
*/
public void hideImmediate()
{
hideTimer.stop();
numa.setTo(0);
super.hide();
onHideFinished();
}
/**
* Show without fading
*/
public void showImmediate()
{
hideTimer.stop();
numa.setTo(1);
super.show();
onShowFinished();
}
/**
* Hide with fading
*/
@Override
public void hide()
{
if (fadingOut) return;
if (isVisible()) {
numa.fadeOut();
hideTimer.start(numa.getDefaultDuration());
fadingOut = true;
fadingIn = false;
}
}
}
@@ -1,32 +0,0 @@
package mightypork.gamecore.gui.screens.impl;
import mightypork.gamecore.gui.components.painters.QuadPainter;
import mightypork.gamecore.gui.screens.Screen;
import mightypork.gamecore.gui.screens.ScreenLayer;
import mightypork.utils.math.color.Color;
public class LayerColor extends ScreenLayer {
private final int zIndex;
public LayerColor(Screen screen, Color color, int zIndex)
{
super(screen);
final QuadPainter qp = new QuadPainter(color);
qp.setRect(root);
root.add(qp);
this.zIndex = zIndex;
}
@Override
public int getZIndex()
{
return this.zIndex;
}
}
@@ -1,267 +0,0 @@
package mightypork.gamecore.input;
import mightypork.gamecore.core.events.UserQuitRequest;
import mightypork.gamecore.core.modules.AppAccess;
import mightypork.gamecore.input.KeyStroke.Edge;
import mightypork.gamecore.input.events.InputReadyEvent;
import mightypork.gamecore.input.events.KeyEvent;
import mightypork.gamecore.input.events.MouseButtonEvent;
import mightypork.gamecore.input.events.MouseMotionEvent;
import mightypork.utils.eventbus.clients.RootBusNode;
import mightypork.utils.interfaces.Updateable;
import mightypork.utils.math.constraints.vect.Vect;
import mightypork.utils.math.constraints.vect.var.VectVar;
import org.lwjgl.LWJGLException;
import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.Display;
/**
* Input system
*
* @author Ondřej Hruška (MightyPork)
*/
public class InputSystem extends RootBusNode implements Updateable, KeyBinder {
private static boolean inited = false;
/** Current mouse position */
private static final Vect mousePos = new Vect() {
@Override
public double x()
{
if (!Mouse.isInsideWindow()) return Integer.MIN_VALUE;
return Mouse.getX();
}
@Override
public double y()
{
if (!Mouse.isInsideWindow()) return Integer.MIN_VALUE;
// flip Y axis
return Display.getHeight() - Mouse.getY();
}
};
private final KeyBindingPool keybindings;
private final VectVar mouseMove = Vect.makeVar();
private final VectVar mouseLastPos = Vect.makeVar();
/**
* @param app app access
*/
public InputSystem(AppAccess app)
{
super(app);
initDevices();
// global keybindings
keybindings = new KeyBindingPool();
addChildClient(keybindings);
getEventBus().send(new InputReadyEvent());
}
@Override
public final void deinit()
{
Mouse.destroy();
Keyboard.destroy();
}
private void initDevices()
{
if (inited) return;
inited = true;
try {
Mouse.create();
Keyboard.create();
Keyboard.enableRepeatEvents(false);
} catch (final LWJGLException e) {
throw new RuntimeException("Failed to initialize input devices.", e);
}
}
@Override
public final void bindKey(KeyStroke stroke, Edge edge, Runnable task)
{
keybindings.bindKey(stroke, edge, task);
}
@Override
public void unbindKey(KeyStroke stroke)
{
keybindings.unbindKey(stroke);
}
@Override
public synchronized void update(double delta)
{
// was destroyed or not initialized
if (!Display.isCreated()) return;
if (!Mouse.isCreated()) return;
if (!Keyboard.isCreated()) return;
Display.processMessages();
// sum the moves
mouseMove.reset();
mouseLastPos.reset();
boolean wasMouse = false;
while (Mouse.next()) {
onMouseEvent(mouseMove, mouseLastPos);
wasMouse = true;
}
if (wasMouse && !mouseMove.isZero()) {
getEventBus().send(new MouseMotionEvent(mouseLastPos, mouseMove));
}
while (Keyboard.next()) {
onKeyEvent();
}
if (Display.isCloseRequested()) {
getEventBus().send(new UserQuitRequest());
}
}
private void onMouseEvent(VectVar moveSum, VectVar lastPos)
{
final int button = Mouse.getEventButton();
final boolean down = Mouse.getEventButtonState();
final VectVar pos = Vect.makeVar(Mouse.getEventX(), Mouse.getEventY());
final VectVar move = Vect.makeVar(Mouse.getEventDX(), Mouse.getEventDY());
final int wheeld = Mouse.getEventDWheel();
// flip Y axis
pos.setY(Display.getHeight() - pos.y());
if (button != -1 || wheeld != 0) {
getEventBus().send(new MouseButtonEvent(pos.freeze(), button, down, wheeld));
}
moveSum.setTo(moveSum.add(move));
lastPos.setTo(pos);
}
private void onKeyEvent()
{
final int key = Keyboard.getEventKey();
final boolean down = Keyboard.getEventKeyState();
final char c = Keyboard.getEventCharacter();
getEventBus().send(new KeyEvent(key, c, down));
}
/**
* Get absolute mouse position. This vect is final and views at it can
* safely be made.
*
* @return mouse position
*/
public static Vect getMousePos()
{
return mousePos;
}
/**
* @return true if mouse is inside window.
*/
public static boolean isMouseInside()
{
return Mouse.isInsideWindow();
}
/**
* Trap mouse cursor in the window
*
* @param grab true to grab
*/
public static void grabMouse(boolean grab)
{
Mouse.setGrabbed(grab);
}
/**
* Check if key is down
*
* @param key key to check (constant from the {@link Keys} class)
* @return is down
*/
public static boolean isKeyDown(int key)
{
return Keyboard.isKeyDown(key);
}
/**
* Check mouse button state
*
* @param button button to test (0 left, 1 right, 2 middle)
* @return button is down
*/
public static boolean isMouseButtonDown(int button)
{
return Mouse.isButtonDown(button);
}
/**
* @return bit mask of active mod keys
*/
public static int getActiveModKeys()
{
int mods = 0;
if (Keyboard.isKeyDown(Keys.L_ALT) || Keyboard.isKeyDown(Keys.R_ALT)) {
mods |= Keys.MOD_ALT;
}
if (Keyboard.isKeyDown(Keys.L_SHIFT) || Keyboard.isKeyDown(Keys.R_SHIFT)) {
mods |= Keys.MOD_SHIFT;
}
if (Keyboard.isKeyDown(Keys.L_CONTROL) || Keyboard.isKeyDown(Keys.R_CONTROL)) {
mods |= Keys.MOD_CONTROL;
}
if (Keyboard.isKeyDown(Keys.L_META) || Keyboard.isKeyDown(Keys.R_META)) {
mods |= Keys.MOD_META;
}
return mods;
}
/**
* @return true if the system is initialized
*/
public static boolean isReady()
{
return inited;
}
}
@@ -1,32 +0,0 @@
package mightypork.gamecore.input;
import mightypork.gamecore.gui.Action;
import mightypork.gamecore.input.KeyStroke.Edge;
/**
* Can bind events to keys.
*
* @author Ondřej Hruška (MightyPork)
*/
public interface KeyBinder {
/**
* Bind handler to a keystroke, replace current handler if any
*
* @param edge trigger edge
* @param stroke trigger keystroke
* @param task handler; can be {@link Runnable} or {@link Action}
*/
void bindKey(KeyStroke stroke, Edge edge, Runnable task);
/**
* Remove handler from a keystroke (id any)
*
* @param stroke stroke
*/
void unbindKey(KeyStroke stroke);
}
@@ -1,82 +0,0 @@
package mightypork.gamecore.input;
import mightypork.gamecore.input.KeyStroke.Edge;
import mightypork.gamecore.input.events.InputReadyListener;
import mightypork.gamecore.input.events.KeyEvent;
import mightypork.gamecore.input.events.KeyEventHandler;
/**
* Key binding, trigger activated by a keystroke event
*
* @author Ondřej Hruška (MightyPork)
*/
public class KeyBinding implements KeyEventHandler, InputReadyListener {
private final KeyStroke keystroke;
private Runnable handler;
private final Edge edge;
private boolean wasDown = false;
/**
* @param edge trigger edge
* @param stroke trigger keystroke
* @param handler action
*/
public KeyBinding(KeyStroke stroke, Edge edge, Runnable handler)
{
this.keystroke = stroke;
this.handler = handler;
this.edge = edge;
if (InputSystem.isReady()) wasDown = stroke.isDown();
}
/**
* Check for equality of keystroke
*
* @param stroke other keystroke
* @return true if keystrokes are equal (cannot co-exist)
*/
public boolean matches(KeyStroke stroke)
{
return this.keystroke.equals(stroke);
}
/**
* @param handler event handler
*/
public void setHandler(Runnable handler)
{
this.handler = handler;
}
@Override
public void receive(KeyEvent event)
{
final boolean nowDown = keystroke.isDown();
boolean trigger = false;
trigger |= (edge == Edge.FALLING && (!wasDown && nowDown));
trigger |= (edge == Edge.RISING && (wasDown && !nowDown));
wasDown = nowDown;
// run handler when event was met
if (trigger) {
handler.run();
}
}
@Override
public void onInputReady()
{
wasDown = keystroke.isDown();
}
}
@@ -1,72 +0,0 @@
package mightypork.gamecore.input;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import mightypork.gamecore.input.KeyStroke.Edge;
import mightypork.gamecore.input.events.KeyEvent;
import mightypork.gamecore.input.events.KeyEventHandler;
import mightypork.utils.logging.Log;
/**
* Key binding pool
*
* @author Ondřej Hruška (MightyPork)
*/
public class KeyBindingPool implements KeyBinder, KeyEventHandler {
private final Set<KeyBinding> bindings = new HashSet<>();
/**
* Bind handler to a keystroke, replace current handler if any
*
* @param stroke trigger keystroke
* @param task handler
*/
@Override
public void bindKey(KeyStroke stroke, Edge edge, Runnable task)
{
for (final KeyBinding kb : bindings) {
if (kb.matches(stroke)) {
Log.w("Duplicate KeyBinding (" + stroke + "), replacing handler.");
kb.setHandler(task);
return;
}
}
bindings.add(new KeyBinding(stroke, edge, task));
}
/**
* Remove handler from keystroke (id any)
*
* @param stroke stroke
*/
@Override
public void unbindKey(KeyStroke stroke)
{
final Iterator<KeyBinding> iter = bindings.iterator();
while (iter.hasNext()) {
final KeyBinding kb = iter.next();
if (kb.matches(stroke)) {
iter.remove();
return;
}
}
}
@Override
public void receive(KeyEvent event)
{
for (final KeyBinding kb : bindings) {
kb.receive(event);
}
}
}
@@ -1,145 +0,0 @@
package mightypork.gamecore.input;
import mightypork.utils.string.StringUtil;
import org.lwjgl.input.Keyboard;
/**
* Key stroke trigger
*
* @author Ondřej Hruška (MightyPork)
*/
public class KeyStroke { //implements Pollable
public static enum Edge
{
FALLING, RISING;
}
private int mod;
private int key;
/**
* KeyStroke
*
* @param key key code
* @param mod_mask mods mask
*/
public KeyStroke(int key, int mod_mask)
{
setTo(key, mod_mask);
}
/**
* Rising edge keystroke
*
* @param key key code
*/
public KeyStroke(int key)
{
this(key, Keys.MOD_NONE);
}
/**
* @return true if the keystroke is currently down & modifiers match.
*/
public boolean isDown()
{
boolean st = Keyboard.isKeyDown(key);
st &= (InputSystem.getActiveModKeys() == mod);
return st;
}
public void setTo(int key, int mod_mask)
{
this.key = key;
this.mod = mod_mask | Keys.keyToMod(key); // for mods alone
}
public String toDataString()
{
String s = "";
if (mod != Keys.MOD_NONE) s = Keys.modToString(mod);
s += Keys.keyToString(key);
return s;
}
public static KeyStroke createFromDataString(String dataString)
{
final KeyStroke ks = new KeyStroke(Keys.NONE, Keys.MOD_NONE);
ks.fromDataString(dataString);
return ks;
}
public void fromDataString(String dataString)
{
final String dataString1 = dataString.toUpperCase().replace('-', '+').replaceAll("[^A-Z0-9_+]", "");
if (dataString1.contains("+")) {
final String keyStr = StringUtil.fromLastChar(dataString1, '+');
final String modStr = StringUtil.toLastChar(dataString1, '+');
setTo(Keys.keyFromString(keyStr), Keys.modFromString(modStr));
} else {
setTo(Keys.keyFromString(dataString1), Keys.MOD_NONE);
}
}
public int getKey()
{
return key;
}
public int getMod()
{
return mod;
}
@Override
public String toString()
{
return toDataString();
}
@Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result + key;
result = prime * result + mod;
return result;
}
@Override
public boolean equals(Object obj)
{
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
final KeyStroke other = (KeyStroke) obj;
if (key != other.key) return false;
if (mod != other.mod) return false;
return true;
}
}
-265
View File
@@ -1,265 +0,0 @@
package mightypork.gamecore.input;
import java.util.HashMap;
import mightypork.utils.logging.Log;
import org.lwjgl.input.Keyboard;
/**
* Key constants, from LWJGL {@link Keyboard}
*
* @author Ondřej Hruška (MightyPork)
*/
public class Keys {
//@formatter:off
public static final int NONE = Keyboard.KEY_NONE;
public static final int ESCAPE = Keyboard.KEY_ESCAPE;
public static final int NUM_1 = Keyboard.KEY_1;
public static final int NUM_2 = Keyboard.KEY_2;
public static final int NUM_3 = Keyboard.KEY_3;
public static final int NUM_4 = Keyboard.KEY_4;
public static final int NUM_5 = Keyboard.KEY_5;
public static final int NUM_6 = Keyboard.KEY_6;
public static final int NUM_7 = Keyboard.KEY_7;
public static final int NUM_8 = Keyboard.KEY_8;
public static final int NUM_9 = Keyboard.KEY_9;
public static final int NUM_0 = Keyboard.KEY_0;
public static final int Q = Keyboard.KEY_Q;
public static final int W = Keyboard.KEY_W;
public static final int E = Keyboard.KEY_E;
public static final int R = Keyboard.KEY_R;
public static final int T = Keyboard.KEY_T;
public static final int Y = Keyboard.KEY_Y;
public static final int U = Keyboard.KEY_U;
public static final int I = Keyboard.KEY_I;
public static final int O = Keyboard.KEY_O;
public static final int P = Keyboard.KEY_P;
public static final int A = Keyboard.KEY_A;
public static final int S = Keyboard.KEY_S;
public static final int D = Keyboard.KEY_D;
public static final int F = Keyboard.KEY_F;
public static final int G = Keyboard.KEY_G;
public static final int H = Keyboard.KEY_H;
public static final int J = Keyboard.KEY_J;
public static final int K = Keyboard.KEY_K;
public static final int L = Keyboard.KEY_L;
public static final int Z = Keyboard.KEY_Z;
public static final int X = Keyboard.KEY_X;
public static final int C = Keyboard.KEY_C;
public static final int V = Keyboard.KEY_V;
public static final int B = Keyboard.KEY_B;
public static final int N = Keyboard.KEY_N;
public static final int M = Keyboard.KEY_M;
public static final int MINUS = Keyboard.KEY_MINUS;
public static final int EQUALS = Keyboard.KEY_EQUALS;
public static final int SLASH = Keyboard.KEY_SLASH;
public static final int BACKSLASH = Keyboard.KEY_BACKSLASH;
public static final int L_BRACKET = Keyboard.KEY_LBRACKET;
public static final int R_BRACKET = Keyboard.KEY_RBRACKET;
public static final int SEMICOLON = Keyboard.KEY_SEMICOLON;
public static final int APOSTROPHE = Keyboard.KEY_APOSTROPHE;
public static final int GRAVE = Keyboard.KEY_GRAVE;
public static final int COMMA = Keyboard.KEY_COMMA;
public static final int PERIOD = Keyboard.KEY_PERIOD;
public static final int SPACE = Keyboard.KEY_SPACE;
public static final int BACKSPACE = Keyboard.KEY_BACK;
public static final int TAB = Keyboard.KEY_TAB;
public static final int F1 = Keyboard.KEY_F1;
public static final int F2 = Keyboard.KEY_F2;
public static final int F3 = Keyboard.KEY_F3;
public static final int F4 = Keyboard.KEY_F4;
public static final int F5 = Keyboard.KEY_F5;
public static final int F6 = Keyboard.KEY_F6;
public static final int F7 = Keyboard.KEY_F7;
public static final int F8 = Keyboard.KEY_F8;
public static final int F9 = Keyboard.KEY_F9;
public static final int F10 = Keyboard.KEY_F10;
public static final int F11 = Keyboard.KEY_F11;
public static final int F12 = Keyboard.KEY_F12;
public static final int F13 = Keyboard.KEY_F13;
public static final int F14 = Keyboard.KEY_F14;
public static final int F15 = Keyboard.KEY_F15;
public static final int CAPS_LOCK = Keyboard.KEY_CAPITAL;
public static final int SCROLL_LOCK = Keyboard.KEY_SCROLL;
public static final int NUM_LOCK = Keyboard.KEY_NUMLOCK;
public static final int NUMPAD_MINUS = Keyboard.KEY_SUBTRACT;
public static final int NUMPAD_PLUSS = Keyboard.KEY_ADD;
public static final int NUMPAD_0 = Keyboard.KEY_NUMPAD0;
public static final int NUMPAD_1 = Keyboard.KEY_NUMPAD1;
public static final int NUMPAD_2 = Keyboard.KEY_NUMPAD2;
public static final int NUMPAD_3 = Keyboard.KEY_NUMPAD3;
public static final int NUMPAD_4 = Keyboard.KEY_NUMPAD4;
public static final int NUMPAD_5 = Keyboard.KEY_NUMPAD5;
public static final int NUMPAD_6 = Keyboard.KEY_NUMPAD6;
public static final int NUMPAD_7 = Keyboard.KEY_NUMPAD7;
public static final int NUMPAD_8 = Keyboard.KEY_NUMPAD8;
public static final int NUMPAD_9 = Keyboard.KEY_NUMPAD9;
public static final int NUMPAD_DECIMAL = Keyboard.KEY_DECIMAL;
public static final int NUMPAD_ENTER = Keyboard.KEY_NUMPADENTER;
public static final int NUMPAD_DIVIDE = Keyboard.KEY_DIVIDE;
public static final int NUMPAD_MULTIPLY = Keyboard.KEY_MULTIPLY;
public static final int L_CONTROL = Keyboard.KEY_LCONTROL;
public static final int R_CONTROL = Keyboard.KEY_RCONTROL;
public static final int L_ALT = Keyboard.KEY_LMENU;
public static final int R_ALT = Keyboard.KEY_RMENU;
public static final int L_SHIFT = Keyboard.KEY_LSHIFT;
public static final int R_SHIFT = Keyboard.KEY_RSHIFT;
public static final int L_META = Keyboard.KEY_LMETA;
public static final int R_META = Keyboard.KEY_RMETA;
public static final int UP = Keyboard.KEY_UP;
public static final int DOWN = Keyboard.KEY_DOWN;
public static final int LEFT = Keyboard.KEY_LEFT;
public static final int RIGHT = Keyboard.KEY_RIGHT;
public static final int HOME = Keyboard.KEY_HOME;
public static final int END = Keyboard.KEY_END;
public static final int PAGE_UP = Keyboard.KEY_PRIOR;
public static final int PAGE_DOWN = Keyboard.KEY_NEXT;
public static final int RETURN = Keyboard.KEY_RETURN;
public static final int PAUSE = Keyboard.KEY_PAUSE;
public static final int INSERT = Keyboard.KEY_INSERT;
public static final int DELETE = Keyboard.KEY_DELETE;
public static final byte MOD_NONE = 0;
public static final byte MOD_ALT = 1;
public static final byte MOD_CONTROL = 2;
public static final byte MOD_SHIFT = 4;
public static final byte MOD_META = 8;
//@formatter:on
private static HashMap<String, String> loadAliasMap = new HashMap<>();
private static HashMap<String, String> saveAliasMap = new HashMap<>();
static {
// init maps
loadAliasMap.put("ENTER", "RETURN");
loadAliasMap.put("PGDN", "NEXT");
loadAliasMap.put("PGUP", "PRIOR");
loadAliasMap.put("PAGE_DOWN", "NEXT");
loadAliasMap.put("PAGE_UP", "PRIOR");
loadAliasMap.put("SPACEBAR", "SPACE");
loadAliasMap.put("ESC", "ESCAPE");
loadAliasMap.put("NUMPAD_DIVIDE", "DIVIDE");
loadAliasMap.put("NUMPAD_MULTIPLY", "MULTIPLY");
loadAliasMap.put("NUMPAD_ADD", "ADD");
loadAliasMap.put("NUMPAD_SUBTRACT", "SUBTRACT");
loadAliasMap.put("CAPS_LOCK", "CAPITAL");
loadAliasMap.put("SCROLL_LOCK", "SROLL");
loadAliasMap.put("NUM_LOCK", "NUMLOCK");
loadAliasMap.put("BACKSPACE", "BACK");
saveAliasMap.put("RETURN", "ENTER");
saveAliasMap.put("ESCAPE", "ESC");
saveAliasMap.put("NEXT", "PGDN");
saveAliasMap.put("PRIOR", "PGUP");
saveAliasMap.put("DIVIDE", "NUMPAD_DIVIDE");
saveAliasMap.put("MULTIPLY", "NUMPAD_MULTIPLY");
saveAliasMap.put("ADD", "NUMPAD_ADD");
saveAliasMap.put("SUBTRACT", "NUMPAD_SUBTRACT");
saveAliasMap.put("CAPITAL", "CAPS_LOCK");
saveAliasMap.put("SROLL", "SCROLL_LOCK");
saveAliasMap.put("NUMLOCK", "NUM_LOCK");
saveAliasMap.put("BACK", "BACKSPACE");
}
public static int keyFromString(String key)
{
String key1 = key;
if (loadAliasMap.containsKey(key1)) key1 = loadAliasMap.get(key1);
final int index = Keyboard.getKeyIndex(key1);
if (index == Keys.NONE && !key1.equals("NONE")) {
Log.w("Could not parse key: " + key + " (" + key1 + ")");
}
return index;
}
public static int modFromString(String mod)
{
int mod_mask = Keys.MOD_NONE;
if (mod.contains("CTRL")) {
mod_mask |= Keys.MOD_CONTROL;
}
if (mod.contains("ALT")) {
mod_mask |= Keys.MOD_ALT;
}
if (mod.contains("SHIFT")) {
mod_mask |= Keys.MOD_SHIFT;
}
if (mod.contains("META") || mod.contains("WIN")) {
mod_mask |= Keys.MOD_META;
}
return mod_mask;
}
public static String modToString(int mod)
{
String s = "";
if ((mod & Keys.MOD_CONTROL) != 0) {
s += "CTRL+";
}
if ((mod & Keys.MOD_ALT) != 0) {
s += "ALT+";
}
if ((mod & Keys.MOD_SHIFT) != 0) {
s += "SHIFT+";
}
if ((mod & Keys.MOD_META) != 0) {
s += "META+";
}
return s;
}
public static String keyToString(int key)
{
String s = Keyboard.getKeyName(key);
if (saveAliasMap.containsKey(s)) s = saveAliasMap.get(s);
if (s == null) {
Log.w("Could not stringify key: " + key);
s = "NONE";
}
return s.toUpperCase();
}
public static int keyToMod(int key)
{
if (key == L_SHIFT || key == R_SHIFT) return MOD_SHIFT;
if (key == L_CONTROL || key == R_CONTROL) return MOD_CONTROL;
if (key == L_ALT || key == R_ALT) return MOD_ALT;
if (key == L_META || key == R_META) return MOD_META;
return MOD_NONE;
}
}
@@ -1,15 +0,0 @@
package mightypork.gamecore.input.events;
import mightypork.utils.eventbus.BusEvent;
public class InputReadyEvent extends BusEvent<InputReadyListener> {
@Override
protected void handleBy(InputReadyListener handler)
{
handler.onInputReady();
}
}
@@ -1,7 +0,0 @@
package mightypork.gamecore.input.events;
public interface InputReadyListener {
void onInputReady();
}
@@ -1,85 +0,0 @@
package mightypork.gamecore.input.events;
import mightypork.utils.eventbus.BusEvent;
import mightypork.utils.eventbus.events.flags.NotLoggedEvent;
import org.lwjgl.input.Keyboard;
/**
* A keyboard event
*
* @author Ondřej Hruška (MightyPork)
*/
@NotLoggedEvent
public class KeyEvent extends BusEvent<KeyEventHandler> {
private final int key;
private final boolean down;
private final char c;
/**
* @param key key that triggered the event. Can be KEY_NONE.
* @param c typed char (can be zero char)
* @param down true = pressed, false = released.
*/
public KeyEvent(int key, char c, boolean down)
{
this.key = key;
this.c = c;
this.down = down;
}
/**
* @return key code (see {@link org.lwjgl.input.Keyboard})
*/
public int getKey()
{
return key;
}
/**
* @return true if key was just pressed
*/
public boolean isDown()
{
return down;
}
/**
* @return true if key was just released
*/
public boolean isUp()
{
return !down;
}
/**
* @return event character (if any)
*/
public char getChar()
{
return c;
}
@Override
public void handleBy(KeyEventHandler keh)
{
keh.receive(this);
}
@Override
public String toString()
{
return Keyboard.getKeyName(key) + ":" + (down ? "DOWN" : "UP");
}
}
@@ -1,17 +0,0 @@
package mightypork.gamecore.input.events;
/**
* {@link KeyEvent} listener
*
* @author Ondřej Hruška (MightyPork)
*/
public interface KeyEventHandler {
/**
* Handle an event
*
* @param event event
*/
void receive(KeyEvent event);
}
@@ -1,126 +0,0 @@
package mightypork.gamecore.input.events;
import mightypork.utils.eventbus.BusEvent;
import mightypork.utils.eventbus.events.flags.NotLoggedEvent;
import mightypork.utils.math.constraints.rect.RectBound;
import mightypork.utils.math.constraints.vect.Vect;
import mightypork.utils.math.constraints.vect.VectConst;
/**
* Mouse button / wheel event triggered
*
* @author Ondřej Hruška (MightyPork)
*/
@NotLoggedEvent
public class MouseButtonEvent extends BusEvent<MouseButtonHandler> {
public static final int BUTTON_LEFT = 0;
public static final int BUTTON_MIDDLE = 1;
public static final int BUTTON_RIGHT = 2;
private final int button;
private final int wheeld;
private final VectConst pos;
private final boolean down;
/**
* Mouse button event
*
* @param pos event position
* @param button button id
* @param down button pressed
* @param wheeld wheel change
*/
public MouseButtonEvent(Vect pos, int button, boolean down, int wheeld)
{
this.button = button;
this.down = down;
this.pos = pos.freeze();
this.wheeld = wheeld;
}
/**
* @return true if the event was caused by a button state change
*/
public boolean isButtonEvent()
{
return button != -1;
}
/**
* @return true if the event was caused by a wheel change
*/
public boolean isWheelEvent()
{
return wheeld != 0;
}
/**
* @return button id or -1 if none was pressed
*/
public int getButton()
{
return button;
}
/**
* @return number of steps the wheel changed since last event
*/
public int getWheelDelta()
{
return wheeld;
}
/**
* @return mouse position when the event occurred
*/
public VectConst getPos()
{
return pos;
}
/**
* @return true if button was just pressed
*/
public boolean isDown()
{
return button != -1 && down;
}
/**
* @return true if button was just released
*/
public boolean isUp()
{
return button != -1 && !down;
}
/**
* Get if event happened over a rect
*
* @param rect rect region
* @return was over
*/
public boolean isOver(RectBound rect)
{
return rect.getRect().contains(pos);
}
@Override
public void handleBy(MouseButtonHandler handler)
{
handler.receive(this);
}
}
@@ -1,17 +0,0 @@
package mightypork.gamecore.input.events;
/**
* {@link MouseButtonEvent} listener
*
* @author Ondřej Hruška (MightyPork)
*/
public interface MouseButtonHandler {
/**
* Handle an event
*
* @param event event
*/
void receive(MouseButtonEvent event);
}
@@ -1,57 +0,0 @@
package mightypork.gamecore.input.events;
import mightypork.utils.eventbus.BusEvent;
import mightypork.utils.eventbus.events.flags.NotLoggedEvent;
import mightypork.utils.math.constraints.vect.Vect;
import mightypork.utils.math.constraints.vect.VectConst;
/**
* Mouse moved
*
* @author Ondřej Hruška (MightyPork)
*/
@NotLoggedEvent
public class MouseMotionEvent extends BusEvent<MouseMotionHandler> {
private final VectConst move;
private final VectConst pos;
/**
* @param pos end pos
* @param move move vector
*/
public MouseMotionEvent(Vect pos, Vect move)
{
this.move = move.freeze();
this.pos = pos.freeze();
}
/**
* @return movement since last {@link MouseMotionEvent}
*/
public VectConst getMove()
{
return move;
}
/**
* @return current mouse position
*/
public VectConst getPos()
{
return pos;
}
@Override
public void handleBy(MouseMotionHandler keh)
{
keh.receive(this);
}
}
@@ -1,17 +0,0 @@
package mightypork.gamecore.input.events;
/**
* {@link MouseMotionEvent} listener
*
* @author Ondřej Hruška (MightyPork)
*/
public interface MouseMotionHandler {
/**
* Handle an event
*
* @param event event
*/
void receive(MouseMotionEvent event);
}
@@ -1,298 +0,0 @@
package mightypork.gamecore.render;
import static org.lwjgl.opengl.GL11.*;
import java.nio.ByteBuffer;
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)
*/
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 Screenshot 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 Screenshot sc = new Screenshot(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();
}
}
-541
View File
@@ -1,541 +0,0 @@
package mightypork.gamecore.render;
import static org.lwjgl.opengl.GL11.*;
import java.io.IOException;
import mightypork.gamecore.resources.textures.FilterMode;
import mightypork.gamecore.resources.textures.GLTexture;
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)
*/
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(GLTexture texture)
{
texture.bind();
glBegin(GL11.GL_QUADS);
batchTexturedQuadMode = true;
}
public static void leaveBatchTexturedQuadMode()
{
glEnd();
batchTexturedQuadMode = false;
}
}

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