diff --git a/.classpath b/.classpath
index 2f6c713..bbb3dda 100644
--- a/.classpath
+++ b/.classpath
@@ -12,22 +12,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
-
-
-
-
-
-
-
+
diff --git a/.project b/.project
index c1e561a..522f172 100644
--- a/.project
+++ b/.project
@@ -2,22 +2,21 @@
SimpleJavaEngine
-
-
-
-
- org.eclipse.jdt.core.javabuilder
-
-
-
-
- org.eclipse.buildship.core.gradleprojectbuilder
-
-
-
-
+
org.eclipse.jdt.core.javanature
org.eclipse.buildship.core.gradleprojectnature
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+ org.eclipse.buildship.core.gradleprojectbuilder
+
+
+
+
+
diff --git a/build.gradle b/build.gradle
index cdb1f82..6be4139 100644
--- a/build.gradle
+++ b/build.gradle
@@ -49,7 +49,7 @@ repositories {
name = "Speiger Maven"
url = "https://maven.speiger.com/repository/main"
}
- maven { url "https://central.sonatype.com/repository/maven-snapshots" }
+ maven { url = "https://central.sonatype.com/repository/maven-snapshots" }
}
@@ -93,15 +93,12 @@ dependencies {
}
jar {
- manifest {
- attributes "Main-Class": 'speiger.src.coreengine.NewRenderEngineTest'
- }
sourceSets.each { ss ->
from ss.output
}
- from {
+ from {
configurations.runtimeClasspath.collect {
- it.isDirectory() ? it : zipTree(it)
+ !it.exists() ? null : (it.isDirectory() ? it : zipTree(it))
}
}
duplicatesStrategy = DuplicatesStrategy.INCLUDE
@@ -113,11 +110,6 @@ task srcJar(type: Jar) {
sourceSets.each { ss ->
from ss.allSource
}
- from {
- configurations.runtimeClasspath.collect {
- it.isDirectory() ? it : zipTree(it)
- }
- }
duplicatesStrategy = DuplicatesStrategy.INCLUDE
}
diff --git a/src/events/java/speiger/src/coreengine/core/events/api/Event.java b/src/events/java/speiger/src/coreengine/core/events/api/Event.java
index 51123b9..3d61d39 100644
--- a/src/events/java/speiger/src/coreengine/core/events/api/Event.java
+++ b/src/events/java/speiger/src/coreengine/core/events/api/Event.java
@@ -1,5 +1,6 @@
package speiger.src.coreengine.core.events.api;
public class Event {
-
+ boolean canceled = false;
+ public final Event self() { return this; }
}
diff --git a/src/events/java/speiger/src/coreengine/core/events/api/EventPriority.java b/src/events/java/speiger/src/coreengine/core/events/api/EventPriority.java
index 205187f..1cc8b0d 100644
--- a/src/events/java/speiger/src/coreengine/core/events/api/EventPriority.java
+++ b/src/events/java/speiger/src/coreengine/core/events/api/EventPriority.java
@@ -2,23 +2,7 @@ package speiger.src.coreengine.core.events.api;
public enum EventPriority
{
- HIGH(0),
- MEDIUM(1),
- LOW(2);
-
- int priority;
-
- static final EventPriority[] PRIORITIES = new EventPriority[] {HIGH, MEDIUM, LOW};
-
- private EventPriority(int id) {
- priority = id;
- }
-
- public int getPriority() {
- return priority;
- }
-
- public static EventPriority[] getPriorities() {
- return PRIORITIES;
- }
+ HIGH,
+ MEDIUM,
+ LOW;
}
diff --git a/src/events/java/speiger/src/coreengine/core/events/api/ICancelableEvent.java b/src/events/java/speiger/src/coreengine/core/events/api/ICancelableEvent.java
index 674ec7a..0bccf4e 100644
--- a/src/events/java/speiger/src/coreengine/core/events/api/ICancelableEvent.java
+++ b/src/events/java/speiger/src/coreengine/core/events/api/ICancelableEvent.java
@@ -1,6 +1,8 @@
package speiger.src.coreengine.core.events.api;
public interface ICancelableEvent {
- void setCanceled(boolean value);
- boolean isCanceled();
+ Event self();
+ default boolean isCancelable() { return true; }
+ default void setCanceled(boolean value) { self().canceled = value; }
+ default boolean isCanceled() { return self().canceled; }
}
diff --git a/src/events/java/speiger/src/coreengine/core/events/bus/BusBuilder.java b/src/events/java/speiger/src/coreengine/core/events/bus/BusBuilder.java
index 0e60485..4c82ce6 100644
--- a/src/events/java/speiger/src/coreengine/core/events/bus/BusBuilder.java
+++ b/src/events/java/speiger/src/coreengine/core/events/bus/BusBuilder.java
@@ -5,14 +5,15 @@ import java.util.Optional;
import java.util.function.Predicate;
import speiger.src.coreengine.core.events.api.Event;
+import speiger.src.coreengine.core.events.bus.EventBus.ClassFilter;
import speiger.src.coreengine.core.events.utility.IEventDispatcher;
import speiger.src.coreengine.core.events.utility.IEventExceptionHandler;
public class BusBuilder {
IEventDispatcher dispatcher = IEventDispatcher.DIRECT;
Optional exceptions = Optional.empty();
- Predicate> filter = _ -> true;
- boolean checkType = false;
+ ClassFilter typeCheck = new ClassFilter(false, _ -> true, "");
+ boolean running = true;
public BusBuilder dispatcher(IEventDispatcher dispatcher) {
this.dispatcher = Objects.requireNonNull(dispatcher, "A Dispatcher is required");
@@ -24,9 +25,13 @@ public class BusBuilder {
return this;
}
- public BusBuilder filter(Predicate> filter) {
- this.filter = Objects.requireNonNull(filter, "A Filter is required");
- checkType = true;
+ public BusBuilder filter(Predicate> filter, String filterMessage) {
+ this.typeCheck = new ClassFilter(true, Objects.requireNonNull(filter, "A Filter is required"), Objects.requireNonNull(filterMessage, "A Filter Message is required"));
+ return this;
+ }
+
+ public BusBuilder beginShutdown() {
+ running = false;
return this;
}
diff --git a/src/events/java/speiger/src/coreengine/core/events/bus/EventBus.java b/src/events/java/speiger/src/coreengine/core/events/bus/EventBus.java
index 2dad2a3..1996ee4 100644
--- a/src/events/java/speiger/src/coreengine/core/events/bus/EventBus.java
+++ b/src/events/java/speiger/src/coreengine/core/events/bus/EventBus.java
@@ -28,8 +28,7 @@ public class EventBus implements IEventExceptionHandler {
private static final Lookup LOOKUP = MethodHandles.lookup();
IEventDispatcher dispatcher;
IEventExceptionHandler exceptions;
- Predicate> filter;
- boolean checkType;
+ ClassFilter typeCheck;
boolean shutdown = false;
Map, Listeners> listeners = new Object2ObjectConcurrentOpenHashMap<>();
@@ -37,8 +36,8 @@ public class EventBus implements IEventExceptionHandler {
EventBus(BusBuilder builder) {
dispatcher = builder.dispatcher;
exceptions = builder.exceptions.orElse(this);
- checkType = builder.checkType;
- filter = builder.filter;
+ typeCheck = builder.typeCheck;
+ shutdown = !builder.running;
}
public static BusBuilder builder() {
@@ -49,8 +48,16 @@ public class EventBus implements IEventExceptionHandler {
return new Subscriptions(this);
}
+ public void start() {
+ shutdown = false;
+ }
+
+ public void shutdown() {
+ shutdown = true;
+ }
+
public T post(T event) {
- if(shutdown) throw new IllegalStateException("Bus is shutdown");
+ if(shutdown) return event;
validateType(event.getClass());
Listeners listener = listeners.get(event.getClass());
if(listener == null) return event;
@@ -59,12 +66,12 @@ public class EventBus implements IEventExceptionHandler {
}
public CompletableFuture postAsync(T event) {
- if(shutdown) throw new IllegalStateException("Bus is shutdown");
+ if(shutdown) return CompletableFuture.completedFuture(event);
return CompletableFuture.supplyAsync(() -> post(event));
}
public CompletableFuture postAsync(T event, Executor executor) {
- if(shutdown) throw new IllegalStateException("Bus is shutdown");
+ if(shutdown) return CompletableFuture.completedFuture(event);
return CompletableFuture.supplyAsync(() -> post(event), executor);
}
@@ -157,6 +164,12 @@ public class EventBus implements IEventExceptionHandler {
}
}
+ public static record ClassFilter(boolean enabled, Predicate> event, String message) {
+ public boolean isInvalid(Class extends Event> filter) {
+ return enabled() && !event().test(filter);
+ }
+ }
+
private void register(SubscribeEvent data, Object obj, List listeners) {
if(data == null || !(obj instanceof Consumer)) return;
validateType(data.value());
@@ -173,7 +186,7 @@ public class EventBus implements IEventExceptionHandler {
}
private void validateType(Class extends Event> type) {
- if(checkType && !filter.test(type)) throw new IllegalStateException("Event ["+type.getSimpleName()+"] is not allowed with this EventBus");
+ if(typeCheck.isInvalid(type)) throw new IllegalStateException(String.format(typeCheck.message(), type.getSimpleName()));
}
@SuppressWarnings("unchecked")
diff --git a/src/events/java/speiger/src/coreengine/core/events/utility/Listeners.java b/src/events/java/speiger/src/coreengine/core/events/utility/Listeners.java
index 62d941a..502d246 100644
--- a/src/events/java/speiger/src/coreengine/core/events/utility/Listeners.java
+++ b/src/events/java/speiger/src/coreengine/core/events/utility/Listeners.java
@@ -1,27 +1,27 @@
package speiger.src.coreengine.core.events.utility;
import java.util.List;
-import java.util.Set;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.function.Consumer;
import speiger.src.collections.objects.lists.ObjectArrayList;
-import speiger.src.collections.objects.sets.ObjectLinkedOpenHashSet;
import speiger.src.coreengine.core.events.api.Event;
import speiger.src.coreengine.core.events.api.EventPriority;
public class Listeners
{
@SuppressWarnings("unchecked")
- Set>[] unsortedListeners = new Set[EventPriority.getPriorities().length];
+ Queue>[] unsortedListeners = new Queue[EventPriority.values().length];
@SuppressWarnings("unchecked")
Consumer[] listeners = new Consumer[0];
- boolean rebuild = true;
+ volatile boolean rebuild = true;
Listeners parent;
List childs = null;
public Listeners() {
for(int i = 0;i>();
+ unsortedListeners[i] = new ConcurrentLinkedQueue<>();
}
}
@@ -37,23 +37,17 @@ public class Listeners
}
public void addListener(EventPriority priority, Consumer listener) {
- synchronized(unsortedListeners) {
- if(unsortedListeners[priority.getPriority()].add(listener)) markDirty();
- }
+ if(unsortedListeners[priority.ordinal()].add(listener)) markDirty();
}
public void removeListeners(Consumer listener) {
- synchronized(unsortedListeners) {
- for(int i = 0,m=unsortedListeners.length;i> events) {
- synchronized(unsortedListeners) {
- events.addAll(unsortedListeners[entry.getPriority()]);
- }
+ events.addAll(unsortedListeners[entry.ordinal()]);
if(parent != null) parent.getListeners(entry, events);
}
@@ -78,11 +72,11 @@ public class Listeners
@SuppressWarnings("unchecked")
public void rebuildListeners() {
- rebuild = false;
List> result = new ObjectArrayList<>();
- for(EventPriority entry : EventPriority.getPriorities()) {
+ for(EventPriority entry : EventPriority.values()) {
getListeners(entry, result);
}
listeners = result.toArray(new Consumer[result.size()]);
+ rebuild = false;
}
}
diff --git a/src/graphics/java/speiger/src/coreengine/platform/graphics/api/texture/atlas/AtlasStitcher.java b/src/graphics/java/speiger/src/coreengine/platform/graphics/api/texture/atlas/AtlasStitcher.java
new file mode 100644
index 0000000..98f7694
--- /dev/null
+++ b/src/graphics/java/speiger/src/coreengine/platform/graphics/api/texture/atlas/AtlasStitcher.java
@@ -0,0 +1,210 @@
+package speiger.src.coreengine.platform.graphics.api.texture.atlas;
+
+import java.util.List;
+
+import speiger.src.collections.objects.lists.ObjectArrayList;
+import speiger.src.collections.objects.utils.ObjectIterables;
+import speiger.src.collections.utils.HashUtil;
+import speiger.src.coreengine.assets.api.ID;
+import speiger.src.coreengine.platform.graphics.api.texture.atlas.AtlasStitcher.Entry;
+
+/**
+ * Inspired by: AtlasGenerator
+ */
+public class AtlasStitcher {
+ final int maxWidth;
+ final int maxHeight;
+ int width;
+ int height;
+ int pixelsUsed;
+ boolean valid = true;
+ List> toStitch = new ObjectArrayList<>();
+ Slot slot = null;
+
+ public AtlasStitcher(int bounds) {
+ this(bounds, bounds);
+ }
+
+ public AtlasStitcher(int maxWidth, int maxHeight) {
+ this.maxWidth = maxWidth;
+ this.maxHeight = maxHeight;
+ if(maxWidth < 256 || maxHeight < 256) throw new IndexOutOfBoundsException("Minimum Size of the Maximum Size has to be 256x256 pixels");
+ }
+
+ public int width() { return width; }
+ public int height() { return height; }
+ public boolean isValid() { return valid; }
+
+ public void add(T entry) { add(new Record<>(entry)); }
+ public void addAll(Iterable extends T> iterables) { ObjectIterables.map(iterables, Record::new).forEach(this::add); }
+ @SuppressWarnings("unchecked")
+ public void addAll(T...entries) {
+ for(T entry : entries) {
+ add(new Record<>(entry));
+ }
+ }
+
+ private void add(Record entry) {
+ toStitch.add(entry);
+ pixelsUsed += entry.pixels();
+ }
+
+ public void stitch() {
+ toStitch.sort(null);
+ int expected = HashUtil.nextPowerOfTwo((int)Math.sqrt(pixelsUsed)) >> 1;
+ if(expected > maxWidth && expected > maxHeight) {
+ valid = false;
+ return;
+ }
+ for(Record entry : toStitch) {
+ if(!addToSlot(entry)) {
+ valid = false;
+ return;
+ }
+ }
+ }
+
+ public void process(IAtlasScanner scanner) {
+ if(slot == null) return;
+ slot.scan(scanner);
+ }
+
+ private boolean addToSlot(Record entry) {
+ if(slot != null && slot.insert(entry)) return true;
+ if(expand(entry)) {
+ width = slot.width;
+ height = slot.height;
+ return true;
+ }
+ return false;
+ }
+
+ private boolean expand(Record entry) {
+ int width = HashUtil.nextPowerOfTwo(entry.width());
+ int height = HashUtil.nextPowerOfTwo(entry.height());
+ if(width > maxWidth || height > maxHeight) return false;
+ if(slot == null) {
+ int min = HashUtil.nextPowerOfTwo((int)Math.sqrt(pixelsUsed)) / 2;
+ slot = new Slot<>(0, 0, Math.max(width, min), Math.max(height, min));
+ return slot.insert(entry);
+ }
+ if(width > slot.width) {
+ if(slot.height * 2 > maxHeight) return false;
+ slot = new Slot<>(slot, width - slot.width, slot.height);
+ return slot.insert(entry);
+ }
+ slot = slot.height >= slot.width ? new Slot<>(slot, slot.width, 0) : new Slot<>(slot, 0, slot.height);
+ return slot.insert(entry);
+ }
+
+ private static record Record(T entry, int width, int height) implements Comparable> {
+ public Record(T entry) {
+ this(entry, entry.width(), entry.height());
+ }
+
+ public int pixels() { return width() * height(); }
+
+ @Override
+ public int compareTo(Record o) {
+ int comp = Integer.compare(o.height, height);
+ if(comp != 0) return comp;
+ comp = Integer.compare(o.width, width);
+ if(comp != 0) return comp;
+ return entry.id().compareTo(o.entry.id());
+ }
+ }
+
+ private static class Slot {
+ int x;
+ int y;
+ int width;
+ int height;
+ Record record;
+ Slot[] children = null;
+
+ public Slot(int x, int y, int width, int height) {
+ this.x = x;
+ this.y = y;
+ this.width = width;
+ this.height = height;
+ }
+
+ @SuppressWarnings("unchecked")
+ public Slot(Slot slot, int eWidth, int eHeight) {
+ x = 0;
+ y = 0;
+ width = slot.width + eWidth;
+ height = slot.height + eHeight;
+ children = new Slot[eWidth > 0 && eHeight > 0 ? 3 : 2];
+ children[0] = slot;
+ expandSlot(slot.width, slot.height, eWidth, eHeight);
+ }
+
+ public boolean isLeaf() { return children == null; }
+
+ public void scan(IAtlasScanner scanner) {
+ if(record != null) {
+ scanner.accept(record.entry(), x, y);
+ return;
+ }
+ if(children != null) {
+ for(int i = 0,m = children.length;i < m;i++) {
+ children[i].scan(scanner);
+ }
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public boolean insert(Record record) {
+ if(isLeaf()) {
+ int rw = record.width();
+ int rh = record.height();
+ if(this.record != null || rw > width || rh > height) return false;
+ if(rw == width && rh == height) {
+ this.record = record;
+ return true;
+ }
+ int dw = width - rw;
+ int dh = height - rh;
+ children = new Slot[dw > 0 && dh > 0 ? 3 : 2];
+ children[0] = new Slot<>(x, y, rw, rh);
+ expandSlot(rw, rh, dw, dh);
+ return children[0].insert(record);
+ }
+ for(int i = 0,m = children.length;i < m;i++) {
+ if(children[i].insert(record)) return true;
+ }
+ return false;
+ }
+
+ private void expandSlot(int rw, int rh, int dw, int dh) {
+ if(dw > 0 && dh > 0) {
+ if(dw >= dh) {
+ children[1] = new Slot<>(x + rw, y, dw, rh);
+ children[2] = new Slot<>(x, y + rh, width, dh);
+ }
+ else {
+ children[1] = new Slot<>(x, y + rh, rw, dh);
+ children[2] = new Slot<>(x + rw, y, dw, height);
+ }
+ }
+ else if(dw == 0) children[1] = new Slot<>(x, y + rh, rw, dh);
+ else if(dh == 0) children[1] = new Slot<>(x + rw, y, dw, rh);
+ }
+
+ @Override
+ public String toString() {
+ return "Slot[x="+x+", y="+y+", w="+width+", h="+height+"]";
+ }
+ }
+
+ public static interface IAtlasScanner {
+ public void accept(T entry, int x, int y);
+ }
+
+ public static interface Entry {
+ public ID id();
+ public int width();
+ public int height();
+ }
+}
diff --git a/src/graphics/java/speiger/src/coreengine/platform/input/device/AbstractDevice.java b/src/graphics/java/speiger/src/coreengine/platform/input/device/AbstractDevice.java
index f07c7ff..325245c 100644
--- a/src/graphics/java/speiger/src/coreengine/platform/input/device/AbstractDevice.java
+++ b/src/graphics/java/speiger/src/coreengine/platform/input/device/AbstractDevice.java
@@ -7,10 +7,11 @@ import java.util.concurrent.ConcurrentLinkedDeque;
import speiger.src.collections.longs.collections.LongIterable;
import speiger.src.collections.longs.maps.impl.concurrent.Long2ObjectConcurrentOpenHashMap;
import speiger.src.collections.longs.maps.interfaces.Long2ObjectMap;
-import speiger.src.coreengine.platform.input.window.Window;
+import speiger.src.coreengine.core.events.api.Event;
+import speiger.src.coreengine.core.events.api.ICancelableEvent;
+import speiger.src.coreengine.core.events.bus.EventBus;
import speiger.src.coreengine.platform.input.window.IWindowListener.Reason;
-import speiger.src.coreengine.utils.eventbus.Event;
-import speiger.src.coreengine.utils.eventbus.EventBus;
+import speiger.src.coreengine.platform.input.window.Window;
public abstract class AbstractDevice implements InputDevice {
protected Long2ObjectMap> queues = new Long2ObjectConcurrentOpenHashMap<>();
@@ -51,7 +52,7 @@ public abstract class AbstractDevice implements InputDevice {
protected boolean pushEvent(Event event) {
if(bus == null) return true;
bus.post(event);
- return event.isCancelable() && event.isCanceled();
+ return event instanceof ICancelableEvent cancel && cancel.isCanceled();
}
protected void push(long windowId, E task) {
diff --git a/src/graphics/java/speiger/src/coreengine/platform/input/device/Joystick.java b/src/graphics/java/speiger/src/coreengine/platform/input/device/Joystick.java
index bafa2cd..72e0520 100644
--- a/src/graphics/java/speiger/src/coreengine/platform/input/device/Joystick.java
+++ b/src/graphics/java/speiger/src/coreengine/platform/input/device/Joystick.java
@@ -16,11 +16,11 @@ import speiger.src.collections.ints.sets.IntSet;
import speiger.src.collections.longs.collections.LongIterator;
import speiger.src.collections.longs.sets.LongOpenHashSet;
import speiger.src.collections.longs.sets.LongSet;
+import speiger.src.coreengine.core.events.bus.EventBus;
import speiger.src.coreengine.platform.input.device.Joystick.JoyStickData;
import speiger.src.coreengine.platform.input.device.Joystick.JoyStickTask;
import speiger.src.coreengine.platform.input.events.JoystickEvent;
import speiger.src.coreengine.platform.input.window.WindowManager;
-import speiger.src.coreengine.utils.eventbus.EventBus;
public class Joystick extends AbstractDevice {
public static final Joystick INSTANCE = new Joystick();
diff --git a/src/graphics/java/speiger/src/coreengine/platform/input/events/JoystickEvent.java b/src/graphics/java/speiger/src/coreengine/platform/input/events/JoystickEvent.java
index 086549b..a1422c4 100644
--- a/src/graphics/java/speiger/src/coreengine/platform/input/events/JoystickEvent.java
+++ b/src/graphics/java/speiger/src/coreengine/platform/input/events/JoystickEvent.java
@@ -1,6 +1,6 @@
package speiger.src.coreengine.platform.input.events;
-import speiger.src.coreengine.utils.eventbus.Event;
+import speiger.src.coreengine.core.events.api.Event;
public class JoystickEvent extends Event {
final long window;
diff --git a/src/graphics/java/speiger/src/coreengine/platform/input/events/KeyEvent.java b/src/graphics/java/speiger/src/coreengine/platform/input/events/KeyEvent.java
index 65be2f7..ce35e5d 100644
--- a/src/graphics/java/speiger/src/coreengine/platform/input/events/KeyEvent.java
+++ b/src/graphics/java/speiger/src/coreengine/platform/input/events/KeyEvent.java
@@ -1,16 +1,15 @@
package speiger.src.coreengine.platform.input.events;
-import speiger.src.coreengine.utils.eventbus.Event;
+import speiger.src.coreengine.core.events.api.Event;
+import speiger.src.coreengine.core.events.api.ICancelableEvent;
-public abstract class KeyEvent extends Event {
+public abstract class KeyEvent extends Event implements ICancelableEvent {
final long window;
public KeyEvent(long window) {
this.window = window;
}
- @Override
- public boolean isCancelable() { return true; }
public long window() { return window; }
public static class Key extends KeyEvent {
diff --git a/src/graphics/java/speiger/src/coreengine/platform/input/events/MouseEvent.java b/src/graphics/java/speiger/src/coreengine/platform/input/events/MouseEvent.java
index 5845a70..8d96d0b 100644
--- a/src/graphics/java/speiger/src/coreengine/platform/input/events/MouseEvent.java
+++ b/src/graphics/java/speiger/src/coreengine/platform/input/events/MouseEvent.java
@@ -1,8 +1,9 @@
package speiger.src.coreengine.platform.input.events;
-import speiger.src.coreengine.utils.eventbus.Event;
+import speiger.src.coreengine.core.events.api.Event;
+import speiger.src.coreengine.core.events.api.ICancelableEvent;
-public abstract class MouseEvent extends Event {
+public abstract class MouseEvent extends Event implements ICancelableEvent {
final long window;
int x;
int y;
@@ -30,8 +31,6 @@ public abstract class MouseEvent extends Event {
//TODO implement support for Scaling
- @Override
- public boolean isCancelable() { return true; }
public boolean isForced() { return false; }
public static class Click extends MouseEvent {
diff --git a/src/gui/java/speiger/src/coreengine/ui/gui/animation/GuiAnimation.java b/src/gui/java/speiger/src/coreengine/ui/gui/animation/GuiAnimation.java
new file mode 100644
index 0000000..2246d76
--- /dev/null
+++ b/src/gui/java/speiger/src/coreengine/ui/gui/animation/GuiAnimation.java
@@ -0,0 +1,88 @@
+package speiger.src.coreengine.ui.gui.animation;
+
+import java.util.Objects;
+import java.util.function.BiConsumer;
+
+import speiger.src.collections.objects.functions.consumer.ObjectFloatConsumer;
+import speiger.src.collections.objects.functions.function.ToFloatFunction;
+import speiger.src.collections.objects.maps.impl.misc.LinkedEnum2ObjectMap;
+import speiger.src.collections.objects.maps.interfaces.Object2ObjectMap;
+import speiger.src.collections.objects.maps.interfaces.Object2ObjectMap.Entry;
+import speiger.src.collections.objects.utils.maps.Object2ObjectMaps;
+import speiger.src.coreengine.ui.gui.component.base.GuiComponent;
+import speiger.src.coreengine.ui.gui.layout.box.IGuiBox;
+
+
+public class GuiAnimation {
+ Object2ObjectMap actions;
+ BiConsumer listener;
+ float duration;
+
+ public GuiAnimation(Object2ObjectMap actions, BiConsumer listener, float duration) {
+ this.actions = actions;
+ this.listener = listener;
+ this.duration = duration;
+ }
+
+ public Builder copy() { return new Builder(this); }
+ public static Builder of() { return new Builder(); }
+
+ @Override
+ public boolean equals(Object obj) { return obj instanceof GuiAnimation animation && animation.actions.equals(actions); }
+ @Override
+ public int hashCode() { return actions.hashCode(); }
+ public BiConsumer listener() { return listener; }
+ public float duration() { return duration; }
+
+ public void apply(ToFloatFunction getter, ObjectFloatConsumer setter, float progress) {
+ for(Entry entry : Object2ObjectMaps.fastIterable(actions)) {
+ IAction action = entry.getValue();
+ action.apply(entry.getKey(), getter, setter, Math.min(progress, action.duration()));
+ }
+ }
+
+ public static class Builder {
+ Object2ObjectMap actions = new LinkedEnum2ObjectMap<>(Target.class);
+ BiConsumer listener;
+
+ private Builder() {}
+ private Builder(GuiAnimation owner) {
+ this.actions.putAll(owner.actions);
+ }
+
+ public Builder add(Target target, IAction action) {
+ this.actions.put(target, Objects.requireNonNull(action));
+ return this;
+ }
+
+ public Builder withListener(BiConsumer listener) {
+ this.listener = listener;
+ return this;
+ }
+
+ public GuiAnimation build() {
+ float duration = 0F;
+ for(IAction action : actions.values()) {
+ duration = Math.max(duration, action.duration());
+ }
+ return new GuiAnimation(actions, listener, duration);
+ }
+ }
+
+ public static enum Target {
+ X(IGuiBox::getBaseX),
+ Y(IGuiBox::getBaseY),
+ WIDTH(IGuiBox::getBaseWidth),
+ HEIGHT(IGuiBox::getBaseHeight),
+ SCALE(IGuiBox::getBaseScale);
+
+ ToFloatFunction provider;
+
+ private Target(ToFloatFunction provider) {
+ this.provider = provider;
+ }
+
+ public int changeState() { return this == X || this == Y ? 1 : 2; }
+ public float get(GuiComponent component) { return provider.applyAsFloat(component.getBox()); }
+ }
+}
diff --git a/src/gui/java/speiger/src/coreengine/ui/gui/animation/GuiAnimationSnapshot.java b/src/gui/java/speiger/src/coreengine/ui/gui/animation/GuiAnimationSnapshot.java
new file mode 100644
index 0000000..3609775
--- /dev/null
+++ b/src/gui/java/speiger/src/coreengine/ui/gui/animation/GuiAnimationSnapshot.java
@@ -0,0 +1,78 @@
+package speiger.src.coreengine.ui.gui.animation;
+
+import speiger.src.collections.objects.functions.consumer.ObjectFloatConsumer;
+import speiger.src.collections.objects.functions.function.ToFloatFunction;
+import speiger.src.coreengine.ui.gui.animation.GuiAnimation.Target;
+import speiger.src.coreengine.ui.gui.component.base.GuiComponent;
+import speiger.src.coreengine.ui.gui.layout.box.IGuiBox;
+
+public class GuiAnimationSnapshot implements ToFloatFunction, ObjectFloatConsumer {
+ final GuiAnimator animator;
+ final float x;
+ final float y;
+ final float width;
+ final float height;
+ final float scale;
+ final boolean looping;
+ float progress;
+ int loops;
+
+ public GuiAnimationSnapshot(GuiAnimator animator, IGuiBox box, boolean looping) {
+ this(animator, box.getBaseX(), box.getBaseY(), box.getBaseWidth(), box.getBaseHeight(), box.getBaseScale(), looping);
+ }
+
+ public GuiAnimationSnapshot(GuiAnimator animator, float x, float y, float width, float height, float scale, boolean looping) {
+ this.animator = animator;
+ this.x = x;
+ this.y = y;
+ this.width = width;
+ this.height = height;
+ this.scale = scale;
+ this.looping = looping;
+ }
+
+ @Override
+ public float applyAsFloat(Target k) {
+ return switch(k) {
+ case X -> x;
+ case Y -> y;
+ case WIDTH -> width;
+ case HEIGHT -> height;
+ case SCALE -> scale;
+ default -> 0F;
+ };
+ }
+
+ @Override
+ public void accept(Target k, float v) {
+ switch(k) {
+ case X -> animator.accept(k, v - x);
+ case Y -> animator.accept(k, v - y);
+ case WIDTH -> animator.accept(k, v - width);
+ case HEIGHT -> animator.accept(k, v - height);
+ case SCALE -> animator.accept(k, v / scale);
+ }
+ }
+
+ public int loops() {
+ return loops;
+ }
+
+ public GuiAnimationSnapshot applyDifference(GuiComponent component) {
+ for(Target target : Target.values()) {
+ animator.accept(target, applyAsFloat(target) - target.get(component));
+ }
+ return this;
+ }
+
+ public boolean update(GuiAnimation animation, float partialTime) {
+ progress += partialTime;
+ animation.apply(this, this, Math.min(progress, animation.duration()));
+ if(progress >= animation.duration()) {
+ if(!looping) return true;
+ progress = 0F;
+ loops++;
+ }
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/src/gui/java/speiger/src/coreengine/ui/gui/animation/GuiAnimator.java b/src/gui/java/speiger/src/coreengine/ui/gui/animation/GuiAnimator.java
new file mode 100644
index 0000000..24c6e1f
--- /dev/null
+++ b/src/gui/java/speiger/src/coreengine/ui/gui/animation/GuiAnimator.java
@@ -0,0 +1,93 @@
+package speiger.src.coreengine.ui.gui.animation;
+
+import java.util.List;
+import java.util.Map.Entry;
+
+import speiger.src.collections.objects.functions.consumer.ObjectFloatConsumer;
+import speiger.src.collections.objects.lists.ObjectArrayList;
+import speiger.src.collections.objects.maps.interfaces.Object2ObjectMap;
+import speiger.src.collections.objects.utils.maps.Object2ObjectMaps;
+import speiger.src.coreengine.ui.gui.animation.GuiAnimation.Target;
+import speiger.src.coreengine.ui.gui.component.base.GuiComponent;
+
+public class GuiAnimator implements ObjectFloatConsumer {
+ protected GuiComponent owner;
+ float xDiff = 0F;
+ float yDiff = 0F;
+ float widthDiff = 0F;
+ float heightDiff = 0F;
+ float scaleDiff = 1F;
+ int changeState = 0;
+ Object2ObjectMap activeAnimations = Object2ObjectMap.builder().linkedMap();
+ List toDelete = new ObjectArrayList<>();
+
+ public GuiAnimator(GuiComponent owner) {
+ this.owner = owner;
+ }
+
+ public void addAnimation(GuiAnimation animation, boolean looping) {
+ activeAnimations.put(animation, new GuiAnimationSnapshot(this, owner.getBox(), looping));
+ }
+
+ public boolean isAnimationPlaying(GuiAnimation animation) {
+ return activeAnimations.containsKey(animation);
+ }
+
+ public int getLoopAmount(GuiAnimation animation) {
+ GuiAnimationSnapshot snapshot = activeAnimations.get(animation);
+ return snapshot == null ? -1 : snapshot.loops();
+ }
+
+ public void removeAnimation(GuiAnimation animation) {
+ activeAnimations.remove(animation);
+ }
+
+ private void reset() {
+ if(changeState == 0) return;
+ owner.getBox().move(-xDiff, -yDiff).resize(-widthDiff, -heightDiff).scale(1F / scaleDiff);
+ xDiff = 0F;
+ yDiff = 0F;
+ widthDiff = 0F;
+ heightDiff = 0F;
+ scaleDiff = 1F;
+ changeState = 0;
+ }
+
+ public void apply() {
+ if(changeState == 0) return;
+ owner.getBox().move(xDiff, yDiff).resize(widthDiff, heightDiff).scale(scaleDiff);
+ }
+
+ public boolean update(float partialTime) {
+ reset();
+ for(Entry entry : Object2ObjectMaps.fastIterable(activeAnimations)) {
+ GuiAnimation animation = entry.getKey();
+ if(entry.getValue().applyDifference(owner).update(animation, partialTime)) {
+ toDelete.add(animation);
+ }
+ }
+ if(changeState > 0) {
+ owner.onChanged(changeState > 1);
+ }
+ for(int i = 0,m=toDelete.size();i 0;
+ }
+
+ @Override
+ public void accept(Target k, float v) {
+ switch(k) {
+ case X -> xDiff += v;
+ case Y -> yDiff += v;
+ case WIDTH -> widthDiff += v;
+ case HEIGHT -> heightDiff += v;
+ case SCALE -> scaleDiff *= v;
+ }
+ changeState |= k.changeState();
+ }
+}
diff --git a/src/gui/java/speiger/src/coreengine/ui/gui/animation/IAction.java b/src/gui/java/speiger/src/coreengine/ui/gui/animation/IAction.java
new file mode 100644
index 0000000..3c94768
--- /dev/null
+++ b/src/gui/java/speiger/src/coreengine/ui/gui/animation/IAction.java
@@ -0,0 +1,51 @@
+package speiger.src.coreengine.ui.gui.animation;
+
+import speiger.src.collections.objects.functions.consumer.ObjectFloatConsumer;
+import speiger.src.collections.objects.functions.function.ToFloatFunction;
+import speiger.src.coreengine.ui.gui.animation.GuiAnimation.Target;
+
+public interface IAction {
+ public float duration();
+ public void apply(Target target, ToFloatFunction getter, ObjectFloatConsumer setter, float progress);
+ public default IAction withPreDelay(float delay) { return this instanceof PreDelayedAction delayed ? new PreDelayedAction(delayed.action(), delay + delayed.delay()) : new PreDelayedAction(this, delay); }
+ public default IAction withPostDelay(float delay) { return this instanceof PostDelayedAction delayed ? new PostDelayedAction(delayed.action(), delay + delayed.delay()) : new PostDelayedAction(this, delay); }
+ public default IAction reverse() { return this instanceof ReversedAction reversed ? reversed.action() : new ReversedAction(this); }
+ public default IAction asLoop() { return this instanceof LoopingAction loop ? loop : new LoopingAction(this); }
+
+ public record PreDelayedAction(IAction action, float delay) implements IAction {
+ @Override
+ public float duration() { return delay + action.duration(); }
+ @Override
+ public void apply(Target target, ToFloatFunction getter, ObjectFloatConsumer setter, float progress) {
+ action.apply(target, getter, setter, Math.max(progress - delay, 0F));
+ }
+ }
+
+ public record PostDelayedAction(IAction action, float delay) implements IAction {
+ @Override
+ public float duration() { return action.duration() + delay; }
+ @Override
+ public void apply(Target target, ToFloatFunction getter, ObjectFloatConsumer setter, float progress) {
+ action.apply(target, getter, setter, Math.min(progress, action.duration()));
+ }
+ }
+
+ public record ReversedAction(IAction action) implements IAction {
+ @Override
+ public float duration() { return action.duration(); }
+ @Override
+ public void apply(Target target, ToFloatFunction getter, ObjectFloatConsumer setter, float progress) {
+ action.apply(target, getter, setter, action.duration() - progress);
+ }
+ }
+
+ public record LoopingAction(IAction action) implements IAction {
+ @Override
+ public float duration() { return action.duration() * 2F; }
+ @Override
+ public void apply(Target target, ToFloatFunction getter, ObjectFloatConsumer setter, float progress) {
+ float duration = action.duration();
+ action.apply(target, getter, setter, progress >= duration ? duration - (progress - duration) : progress);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/gui/java/speiger/src/coreengine/ui/gui/animation/actions/EasedAction.java b/src/gui/java/speiger/src/coreengine/ui/gui/animation/actions/EasedAction.java
new file mode 100644
index 0000000..0179a55
--- /dev/null
+++ b/src/gui/java/speiger/src/coreengine/ui/gui/animation/actions/EasedAction.java
@@ -0,0 +1,27 @@
+package speiger.src.coreengine.ui.gui.animation.actions;
+
+import speiger.src.collections.objects.functions.consumer.ObjectFloatConsumer;
+import speiger.src.collections.objects.functions.function.ToFloatFunction;
+import speiger.src.coreengine.math.MathUtils;
+import speiger.src.coreengine.math.easing.IEasing;
+import speiger.src.coreengine.ui.gui.animation.GuiAnimation.Target;
+import speiger.src.coreengine.ui.gui.animation.IAction;
+
+public class EasedAction implements IAction {
+ float duration;
+ float targetValue;
+ IEasing function;
+
+ public EasedAction(float duration, float targetValue, IEasing function) {
+ this.duration = duration;
+ this.targetValue = targetValue;
+ this.function = function;
+ }
+
+ @Override
+ public float duration() { return duration; }
+ @Override
+ public void apply(Target target, ToFloatFunction getter, ObjectFloatConsumer setter, float progress) {
+ setter.accept(target, MathUtils.lerp(getter.applyAsFloat(target), targetValue, (float)function.ease(progress, duration)));
+ }
+}
diff --git a/src/gui/java/speiger/src/coreengine/ui/gui/component/base/GuiComponent.java b/src/gui/java/speiger/src/coreengine/ui/gui/component/base/GuiComponent.java
new file mode 100644
index 0000000..7932e0d
--- /dev/null
+++ b/src/gui/java/speiger/src/coreengine/ui/gui/component/base/GuiComponent.java
@@ -0,0 +1,273 @@
+package speiger.src.coreengine.ui.gui.component.base;
+
+import java.util.List;
+import java.util.function.Consumer;
+
+import speiger.src.collections.objects.lists.ObjectArrayList;
+import speiger.src.coreengine.math.bits.FlagObject;
+import speiger.src.coreengine.ui.gui.animation.GuiAnimator;
+import speiger.src.coreengine.ui.gui.interaction.IInteractable;
+import speiger.src.coreengine.ui.gui.interaction.InteractionContainer;
+import speiger.src.coreengine.ui.gui.layout.box.IGuiBox;
+import speiger.src.coreengine.ui.gui.layout.constraint.ConstrainedContext;
+import speiger.src.coreengine.ui.gui.layout.constraint.ConstraintContainer;
+import speiger.src.coreengine.ui.gui.renderer.IUIRenderer;
+
+public abstract non-sealed class GuiComponent extends FlagObject implements ICastable, ILayoutComponent, IListableComponent {
+ private static final int FLAG_FOCUSED = 1 << 0;
+ private static final int FLAG_ENABLED = 1 << 1;
+ private static final int FLAG_VISIBLE = 1 << 2;
+ private static final int FLAG_MANUAL_MANAGED = 1 << 3;
+ private static final int FLAG_SCISSORED = 1 << 4;
+ final IGuiBox box;
+ IComponentScreen screen;
+ ConstraintContainer constraints;
+ ConstrainedContext constraintedContext = new ConstrainedContext();
+ GuiAnimator animator;
+ GuiComponent parent;
+ List children = new ObjectArrayList<>();
+ InteractionContainer interactions = new InteractionContainer(this::isInteractable);
+ List>[] listeners = IListableComponent.createList(IListableComponent.MAX_LISTENER_TYPES);
+ IComponentRenderer renderer;
+
+ public GuiComponent(float x, float y, float width, float height) {
+ this(IGuiBox.of(x, y, width, height));
+ }
+
+ public GuiComponent(IGuiBox box) {
+ this.box = box;
+ if(this instanceof IInteractable actor) {
+ interactions.add(actor);
+ }
+ }
+
+ @Override
+ public void calculateBounds(ILayoutScanner output) {
+ output.accept(getBox());
+ for(GuiComponent child : children) {
+ if(child.isVisible() || output.acceptsInvisble()) {
+ child.calculateBounds(output);
+ }
+ }
+ }
+
+ public void setScreen(IComponentScreen screen) {
+ this.screen = screen;
+ for(GuiComponent child : children) {
+ child.setScreen(screen);
+ }
+ }
+
+ public abstract void init();
+ public void close() {
+ notifyListeners(LISTENER_CLOSED);
+ }
+
+ public void updateAnimations(float partialTicks) {
+ if(animator == null || animator.update(partialTicks)) return;
+ animator = null;
+ }
+
+ protected void updateComponent() {
+ }
+
+ protected void renderComponent(IUIRenderer renderer, int mouseX, int mouseY, float partialTicks) {
+ renderChildren(renderer, mouseX, mouseY, partialTicks);
+ }
+
+ protected void renderChildren(IUIRenderer renderer, int mouseX, int mouseY, float partialTicks) {
+ for(GuiComponent child : children) {
+ if(child.isManualManaged()) continue;
+ renderComponent(child, renderer, mouseX, mouseY, partialTicks);
+ }
+ }
+
+ public static void tickComponent(GuiComponent comp) {
+ if(comp.renderer != null && comp.renderer.overrideTick()) {
+ comp.renderer.updateComponent(comp);
+ }
+ else comp.updateComponent();
+ comp.children.forEach(GuiComponent::tickComponent);
+ }
+
+ public static boolean renderComponent(GuiComponent comp, IUIRenderer renderer, int mouseX, int mouseY, float partialTicks) {
+ if(!comp.isVisible()) return false;
+ comp.updateAnimations(partialTicks);
+ if(comp.isScissored()) {
+ if(!renderer.isInScissors(comp.getBox())) return false;
+ renderer.pushScissors(comp.getBox());
+ if(comp.renderer != null) {
+ comp.renderer.renderComponent(comp, renderer, mouseX, mouseY, partialTicks);
+ comp.renderChildren(renderer, mouseX, mouseY, partialTicks);
+ }
+ else comp.renderComponent(renderer, mouseX, mouseY, partialTicks);
+ renderer.popScissors();
+ }
+ else if(comp.renderer != null) {
+ comp.renderer.renderComponent(comp, renderer, mouseX, mouseY, partialTicks);
+ comp.renderChildren(renderer, mouseX, mouseY, partialTicks);
+ }
+ else comp.renderComponent(renderer, mouseX, mouseY, partialTicks);
+ return true;
+ }
+
+ private ConstrainedContext parentContext() {
+ return parent != null ? parent.constraintedContext : null;
+ }
+
+ public GuiComponent withConstraints(ConstraintContainer container) {
+ this.constraints = container;
+ if((screen != null || parent != null) && constraints != null) constraints.apply(this, parent, parentContext());
+ return this;
+ }
+
+ public ConstraintContainer constraint() {
+ return constraints;
+ }
+
+ public GuiComponent addChild(GuiComponent child) {
+ if(child.parent != null) throw new IllegalArgumentException("A Child can not have multiple parents");
+ child.parent = this;
+ children.add(child);
+ box.addChild(child.getBox());
+ interactions.add(child.interactions);
+ child.setScreen(screen);
+ child.init();
+ child.onChanged(true);
+ return child;
+ }
+
+ public boolean containsChild(GuiComponent child) {
+ return child.parent == this && children.contains(child);
+ }
+
+ public void removeChild(GuiComponent child) {
+ if(child.parent != this) throw new IllegalArgumentException("Child isn't owned by this Component");
+ child.close();
+ child.parent = null;
+ child.screen = null;
+ children.remove(child);
+ box.removeChild(child.getBox());
+ interactions.remove(child.interactions);
+ }
+
+ @Override
+ public GuiComponent addListener(Consumer listener, int index) {
+ listeners[index].add(listener);
+ return this;
+ }
+
+ @Override
+ public GuiComponent removeListener(Consumer listener, int index) {
+ listeners[index].remove(listener);
+ return this;
+ }
+
+ @Override
+ public void notifyListeners(int index) {
+ if(listeners[index].isEmpty()) return;
+ for(Consumer comp : listeners[index]) {
+ comp.accept(this);
+ }
+ }
+
+ public void onChanged(boolean repaint) {
+ if(constraints != null) constraints.apply(this, parent, parentContext());
+ if(animator != null) animator.apply();
+ box.onChanged();
+ constraintedContext.update(this, children);
+ notifyListeners(LISTENER_ON_CHANGE);
+ if(repaint) repaint();
+ if(children.isEmpty()) return;
+ int index = 0;
+ for(GuiComponent comp : children) {
+ constraintedContext.setCurrent(index++);
+ comp.onChanged(repaint);
+ }
+ }
+
+ protected void repaint() {}
+
+ public IComponentScreen screen() { return screen; }
+ @Override
+ public IGuiBox getBox() { return box; }
+ public IInteractable interactContainer() { return interactions; }
+
+
+ @Override
+ public GuiComponent set(float x, float y) {
+ if(box.getBaseX() != x || box.getBaseY() != y) {
+ box.setXY(x, y);
+ onChanged(false);
+ }
+ return this;
+ }
+
+ @Override
+ public GuiComponent bounds(float width, float height) {
+ if(box.getBaseWidth() != width || box.getBaseHeight() != height) {
+ box.setBounds(width, height);
+ onChanged(true);
+ }
+ return this;
+ }
+
+ @Override
+ public GuiComponent resize(float width, float height) {
+ if(width != 0F || height != 0F) {
+ box.resize(width, height);
+ onChanged(true);
+ }
+ return this;
+ }
+
+ @Override
+ public GuiComponent move(float moveX, float moveY) {
+ if(moveX != 0F || moveY != 0F) {
+ box.move(moveX, moveY);
+ onChanged(false);
+ }
+ return this;
+ }
+
+ public final GuiComponent scale(float newScale) {
+ if(newScale != box.getBaseScale()) {
+ box.setScale(newScale);
+ onChanged(true);
+ }
+ return this;
+ }
+
+ //@formatter:off
+ private final boolean isInteractable() { return isFlagSet(FLAG_ENABLED | FLAG_VISIBLE); }
+ public final boolean isFocused() { return isFlagSet(FLAG_FOCUSED); }
+ public final boolean isEnabled() { return isFlagSet(FLAG_ENABLED); }
+ public final boolean isVisible() { return isFlagSet(FLAG_VISIBLE); }
+ public final boolean isManualManaged() { return isFlagSet(FLAG_MANUAL_MANAGED); }
+ public final boolean isScissored() { return isFlagSet(FLAG_SCISSORED); }
+ //@formatter:on
+
+ public final void setFocused(boolean value) {
+ setFlag(FLAG_FOCUSED, value);
+ }
+
+ public final GuiComponent setEnabled(boolean value) {
+ setFlag(FLAG_ENABLED, value);
+ return this;
+ }
+
+ public final GuiComponent setVisible(boolean value) {
+ setFlag(FLAG_VISIBLE, value);
+ return this;
+ }
+
+ public final GuiComponent setManualManaged(boolean value) {
+ setFlag(FLAG_MANUAL_MANAGED, value);
+ return this;
+ }
+
+ public final GuiComponent setScissored(boolean value) {
+ setFlag(FLAG_SCISSORED, value);
+ return this;
+ }
+}
\ No newline at end of file
diff --git a/src/gui/java/speiger/src/coreengine/ui/gui/component/base/ICastable.java b/src/gui/java/speiger/src/coreengine/ui/gui/component/base/ICastable.java
new file mode 100644
index 0000000..9f6056a
--- /dev/null
+++ b/src/gui/java/speiger/src/coreengine/ui/gui/component/base/ICastable.java
@@ -0,0 +1,33 @@
+package speiger.src.coreengine.ui.gui.component.base;
+
+public interface ICastable
+{
+ @SuppressWarnings("unchecked")
+ public default T cast() {
+ return (T)this; }
+
+ @SuppressWarnings("unchecked")
+ public default T cast(Class clz) {
+ return (T)this;
+ }
+
+ @SuppressWarnings("unchecked")
+ public default T tryCast(Class clz) {
+ return clz.isInstance(this) ? (T)this : null;
+ }
+
+ @SuppressWarnings("unchecked")
+ public static T cast(Object obj) {
+ return (T)obj;
+ }
+
+ @SuppressWarnings("unchecked")
+ public static T cast(Object obj, Class clz) {
+ return (T)obj;
+ }
+
+ @SuppressWarnings("unchecked")
+ public static T tryCast(Object obj, Class clz) {
+ return clz.isInstance(obj) ? (T)obj : null;
+ }
+}
\ No newline at end of file
diff --git a/src/gui/java/speiger/src/coreengine/ui/gui/component/base/IComponentRenderer.java b/src/gui/java/speiger/src/coreengine/ui/gui/component/base/IComponentRenderer.java
new file mode 100644
index 0000000..5952ec6
--- /dev/null
+++ b/src/gui/java/speiger/src/coreengine/ui/gui/component/base/IComponentRenderer.java
@@ -0,0 +1,9 @@
+package speiger.src.coreengine.ui.gui.component.base;
+
+import speiger.src.coreengine.ui.gui.renderer.IUIRenderer;
+
+public interface IComponentRenderer {
+ public void renderComponent(T component, IUIRenderer renderer, int mouseX, int mouseY, float particalTicks);
+ public default boolean overrideTick() { return false; }
+ public default void updateComponent(T component) {}
+}
diff --git a/src/gui/java/speiger/src/coreengine/ui/gui/component/base/IComponentScreen.java b/src/gui/java/speiger/src/coreengine/ui/gui/component/base/IComponentScreen.java
new file mode 100644
index 0000000..f1c12ef
--- /dev/null
+++ b/src/gui/java/speiger/src/coreengine/ui/gui/component/base/IComponentScreen.java
@@ -0,0 +1,15 @@
+package speiger.src.coreengine.ui.gui.component.base;
+
+import speiger.src.coreengine.ui.gui.layout.box.IGuiBox;
+import speiger.src.coreengine.ui.gui.layout.constraint.ConstraintContainer;
+
+public interface IComponentScreen {
+ public IGuiBox getBox();
+ public long clock();
+
+ public void pushLayer();
+ public void popLayer();
+ public GuiComponent addComponent(GuiComponent component, ConstraintContainer constraints);
+ public boolean hasComponent(GuiComponent component);
+ public boolean removeComponent(GuiComponent component);
+}
diff --git a/src/gui/java/speiger/src/coreengine/ui/gui/component/base/ILayoutComponent.java b/src/gui/java/speiger/src/coreengine/ui/gui/component/base/ILayoutComponent.java
new file mode 100644
index 0000000..382a381
--- /dev/null
+++ b/src/gui/java/speiger/src/coreengine/ui/gui/component/base/ILayoutComponent.java
@@ -0,0 +1,87 @@
+package speiger.src.coreengine.ui.gui.component.base;
+
+import speiger.src.coreengine.ui.gui.layout.box.IGuiBox;
+
+public interface ILayoutComponent {
+ public void calculateBounds(ILayoutScanner output);
+ public IGuiBox getBox();
+
+ public default float getX() {
+ return getBox().getMinX();
+ }
+
+ public default float getY() {
+ return getBox().getMinY();
+ }
+
+ public default float getWidth() {
+ return getBox().getWidth();
+ }
+
+ public default float getHeight() {
+ return getBox().getHeight();
+ }
+
+ public ILayoutComponent set(float x, float y);
+ public ILayoutComponent bounds(float width, float height);
+ public ILayoutComponent resize(float width, float height);
+ public ILayoutComponent move(float moveX, float moveY);
+
+ public static interface ILayoutScanner {
+ public default void accept(IGuiBox box) { accept(box.getMinX(), box.getMinY(), box.getMaxX(), box.getMaxY()); }
+ public void accept(float minX, float minY, float maxX, float maxY);
+ public boolean acceptsInvisble();
+
+ public static ArrayFetcher of() { return new ArrayFetcher(false); }
+ public static ArrayFetcher includeInvisible() { return new ArrayFetcher(true); }
+ }
+
+ public static class ArrayFetcher implements ILayoutScanner {
+ private static final int INDEX_MIN_X = 0;
+ private static final int INDEX_MIN_Y = 1;
+ private static final int INDEX_MAX_X = 2;
+ private static final int INDEX_MAX_Y = 3;
+ float[] result = new float[] {Float.MAX_VALUE, Float.MAX_VALUE, -Float.MAX_VALUE, -Float.MAX_VALUE};
+ boolean includeInvisible = false;
+
+ public ArrayFetcher(boolean includeInvisible) {
+ this.includeInvisible = includeInvisible;
+ }
+
+ @Override
+ public boolean acceptsInvisble() {
+ return includeInvisible;
+ }
+
+ @Override
+ public void accept(float minX, float minY, float maxX, float maxY) {
+ result[INDEX_MIN_X] = Math.min(minX, result[INDEX_MIN_X]);
+ result[INDEX_MIN_Y] = Math.min(minY, result[INDEX_MIN_Y]);
+ result[INDEX_MAX_X] = Math.max(maxX, result[INDEX_MAX_X]);
+ result[INDEX_MAX_Y] = Math.max(maxY, result[INDEX_MAX_Y]);
+ }
+
+ public void reset() {
+ result[0] = Float.MAX_VALUE;
+ result[1] = Float.MAX_VALUE;
+ result[2] = -Float.MAX_VALUE;
+ result[3] = -Float.MAX_VALUE;
+ }
+
+ public void hardReset() {
+ result = new float[] {Float.MAX_VALUE, Float.MAX_VALUE, -Float.MAX_VALUE, -Float.MAX_VALUE};
+ }
+
+ public float[] result() {
+ return result;
+ }
+
+ public float[] unscaledResult(IGuiBox owner) {
+ float[] unscaled = new float[4];
+ for(int i = 0;i<4;i++) {
+ unscaled[i] = result[i] / owner.getScale();
+ }
+ return unscaled;
+ }
+ }
+}
diff --git a/src/gui/java/speiger/src/coreengine/ui/gui/component/base/IListableComponent.java b/src/gui/java/speiger/src/coreengine/ui/gui/component/base/IListableComponent.java
new file mode 100644
index 0000000..5ba02c2
--- /dev/null
+++ b/src/gui/java/speiger/src/coreengine/ui/gui/component/base/IListableComponent.java
@@ -0,0 +1,67 @@
+package speiger.src.coreengine.ui.gui.component.base;
+
+import java.util.function.Consumer;
+
+import speiger.src.collections.objects.lists.ObjectArrayList;
+import speiger.src.collections.objects.lists.ObjectList;
+
+public sealed interface IListableComponent permits GuiComponent {
+ public static final int LISTENER_USER_ACTION = 0;
+ public static final int LISTENER_ON_CHANGE = 1;
+ public static final int LISTENER_CLOSED = 2;
+
+ public static final int MAX_LISTENER_TYPES = 3;
+
+ public GuiComponent addListener(Consumer listener, int index);
+ public GuiComponent removeListener(Consumer listener, int index);
+ public void notifyListeners(int index);
+
+ public default GuiComponent onAction(Consumer listener) {
+ return addListener(listener, IListableComponent.LISTENER_USER_ACTION);
+ }
+
+ public default GuiComponent onAction(Runnable listener) {
+ return addListener(listener, IListableComponent.LISTENER_USER_ACTION);
+ }
+
+ public default GuiComponent onChange(Consumer listener) {
+ return addListener(listener, IListableComponent.LISTENER_ON_CHANGE);
+ }
+
+ public default GuiComponent onChange(Runnable listener) {
+ return addListener(listener, IListableComponent.LISTENER_ON_CHANGE);
+ }
+
+ public default GuiComponent onClose(Consumer listener) {
+ return addListener(listener, IListableComponent.LISTENER_CLOSED);
+ }
+
+ public default GuiComponent onClose(Runnable listener) {
+ return addListener(listener, IListableComponent.LISTENER_CLOSED);
+ }
+
+ public default GuiComponent addListener(Runnable runnable, int index) {
+ return addListener(_ -> runnable.run(), index);
+ }
+
+ public default GuiComponent removeUserActionListener(Consumer listener) {
+ return removeListener(listener, GuiComponent.LISTENER_USER_ACTION);
+ }
+
+ public default GuiComponent removeChangeListener(Consumer listener) {
+ return removeListener(listener, GuiComponent.LISTENER_ON_CHANGE);
+ }
+
+ public default GuiComponent removeCloseListener(Consumer listener) {
+ return removeListener(listener, GuiComponent.LISTENER_CLOSED);
+ }
+
+ @SuppressWarnings("unchecked")
+ public static ObjectList[] createList(int size) {
+ ObjectList[] list = new ObjectList[size];
+ for(int i = 0;i < size;i++) {
+ list[i] = new ObjectArrayList<>();
+ }
+ return list;
+ }
+}
\ No newline at end of file
diff --git a/src/gui/java/speiger/src/coreengine/ui/gui/interaction/IInteractable.java b/src/gui/java/speiger/src/coreengine/ui/gui/interaction/IInteractable.java
new file mode 100644
index 0000000..0d79b0f
--- /dev/null
+++ b/src/gui/java/speiger/src/coreengine/ui/gui/interaction/IInteractable.java
@@ -0,0 +1,19 @@
+package speiger.src.coreengine.ui.gui.interaction;
+
+public interface IInteractable
+{
+ public default boolean isMouseColliding(int mouseX, int mouseY) { return false; };
+
+ public default boolean onMouseClick(int button, int mouseX, int mouseY) { return false; }
+ public default boolean onMouseDragged(int mouseX, int mouseY, int diffX, int diffY) { return false; }
+ public default boolean onMouseReleased(int button, int mouseX, int mouseY) { return false; }
+ public default boolean onMouseScroll(int scroll, int mouseX, int mouseY) { return false; }
+
+ public default boolean isPriorityKeyTarget() { return false; }
+ public default boolean onKeyPressed(int key, int mouseX, int mouseY) { return false; }
+ public default boolean onKeyReleased(int key, int mouseX, int mouseY) { return false; }
+ public default boolean onKeyTyped(char letter, int codepoint) { return false; }
+
+ public boolean isFocused();
+ public void setFocused(boolean value);
+}
diff --git a/src/gui/java/speiger/src/coreengine/ui/gui/interaction/IInteractableContainer.java b/src/gui/java/speiger/src/coreengine/ui/gui/interaction/IInteractableContainer.java
new file mode 100644
index 0000000..138257c
--- /dev/null
+++ b/src/gui/java/speiger/src/coreengine/ui/gui/interaction/IInteractableContainer.java
@@ -0,0 +1,103 @@
+package speiger.src.coreengine.ui.gui.interaction;
+
+import java.util.List;
+
+import speiger.src.collections.ints.sets.IntSet;
+
+public interface IInteractableContainer extends IInteractable
+{
+ public List extends IInteractable> getChildren();
+
+ public void setFocusedChild(IInteractable child);
+ public IInteractable getFocusedChild();
+ public IntSet getActiveButtons();
+
+ public default IInteractable getChildAt(int mouseX, int mouseY) {
+ List extends IInteractable> children = getChildren();
+ for(int i = 0,m=children.size();i 0 && getFocusedChild() != null) {
+ IInteractable focus = getFocusedChild();
+ if(focus.isMouseColliding(mouseX, mouseY) && focus.onMouseClick(button, mouseX, mouseY)) {
+ active.add(button);
+ return true;
+ }
+ return false;
+ }
+ List extends IInteractable> children = getChildren();
+ for(int i = 0,m=children.size();i children = getChildren();
+ for(int i = 0,m=children.size();i children = getChildren();
+ for(int i = 0,m=children.size();i children = new ObjectArrayList<>();
+ IInteractable focused;
+ IntSet activeButtons = new IntOpenHashSet();
+ BooleanSupplier active;
+
+ public InteractionContainer() {
+ this(() -> true);
+ }
+
+ public InteractionContainer(BooleanSupplier active) {
+ this.active = active;
+ }
+
+ public void add(IInteractable child) {
+ children.add(child);
+ }
+
+ public void remove(IInteractable child) {
+ children.remove(child);
+ }
+
+ public boolean contains(IInteractable child) {
+ return children.contains(child);
+ }
+
+ @Override
+ public boolean isFocused() {
+ return getFocusedChild() != null;
+ }
+
+ @Override
+ public void setFocused(boolean value) {
+ }
+
+ @Override
+ public List extends IInteractable> getChildren() {
+ return active.getAsBoolean() ? children : ObjectLists.empty();
+ }
+
+ @Override
+ public boolean isMouseColliding(int mouseX, int mouseY) {
+ return active.getAsBoolean();
+ }
+
+ @Override
+ public void setFocusedChild(IInteractable child) {
+ if(focused != null) focused.setFocused(false);
+ this.focused = child;
+ if(focused != null) focused.setFocused(true);
+ }
+
+ @Override
+ public IInteractable getFocusedChild() {
+ return focused;
+ }
+
+ @Override
+ public IntSet getActiveButtons() {
+ return activeButtons;
+ }
+}
diff --git a/src/gui/java/speiger/src/coreengine/ui/gui/layout/box/GuiBox.java b/src/gui/java/speiger/src/coreengine/ui/gui/layout/box/GuiBox.java
new file mode 100644
index 0000000..2a305c5
--- /dev/null
+++ b/src/gui/java/speiger/src/coreengine/ui/gui/layout/box/GuiBox.java
@@ -0,0 +1,182 @@
+package speiger.src.coreengine.ui.gui.layout.box;
+
+import java.util.List;
+
+import speiger.src.collections.objects.lists.ObjectArrayList;
+import speiger.src.collections.objects.lists.ObjectList;
+
+public class GuiBox implements IGuiBox
+{
+ ObjectList children = new ObjectArrayList<>();
+ IGuiBox parent;
+
+ float minX;
+ float minY;
+ float maxX;
+ float maxY;
+
+ float width;
+ float height;
+ float scale = 1F;
+
+ float baseX;
+ float baseY;
+ float baseWidth;
+ float baseHeight;
+ float baseScale = 1F;
+
+
+ protected GuiBox() {}
+
+ public GuiBox(float x, float y, float width, float height) {
+ set(x, y, width, height);
+ onChanged();
+ }
+
+ public static IGuiBox clone(IGuiBox box) {
+ return new GuiBox(box.getMinX(), box.getMinY(), box.getWidth(), box.getHeight());
+ }
+
+ public static IGuiBox clonePadded(IGuiBox box, float padding) {
+ return new GuiBox(box.getMinX() + padding, box.getMinY() + padding, box.getWidth() - padding * 2F, box.getHeight() - padding * 2F);
+ }
+
+ @Override
+ public IGuiBox addChild(IGuiBox box) {
+ children.add(box);
+ box.setParent(this);
+ return this;
+ }
+
+ @Override
+ public IGuiBox removeChild(IGuiBox box) {
+ if(children.remove(box)) box.setParent(null);
+ return this;
+ }
+
+ @Override
+ public IGuiBox clearChildren() {
+ for(int i = 0,m=children.size();i children() {
+ return children.unmodifiable();
+ }
+
+ @Override
+ public IGuiBox onChanged() {
+ minX = parent == null ? baseX : parent.getMinX(baseX);
+ minY = parent == null ? baseY : parent.getMinY(baseY);
+ maxX = parent == null ? baseX + width : parent.getMinX(baseX + width);
+ maxY = parent == null ? baseY + height : parent.getMinY(baseY + height);
+ scale = parent == null ? baseScale : parent.getScale() * baseScale;
+ for(int i = 0,m=children.size();i children();
+ public IGuiBox setParent(IGuiBox box);
+ public IGuiBox getParent();
+ public IGuiBox onChanged();
+
+ public static IGuiBox of(float x, float y, float width, float height) { return new GuiBox(x, y, width, height); }
+
+ public default IGuiBox copy() { return GuiBox.clone(this); }
+ public default IGuiBox copy(float padding) { return GuiBox.clonePadded(this, padding); }
+
+ public float getScale();
+ public float getBaseX();
+ public float getBaseY();
+ public float getRelativeX();
+ public float getRelativeY();
+ public float getWidth();
+ public float getWidth(float extra);
+ public float getHeight();
+ public float getHeight(float extra);
+ public default float getSquaredWidth() {
+ float value = getWidth();
+ return value * value;
+ }
+ public default float getSquaredHeight() {
+ float value = getHeight();
+ return value * value;
+ }
+
+ public float getMinX();
+ public float getMinX(float extra);
+ public float getMinY();
+ public float getMinY(float extra);
+ public float getMaxX();
+ public float getMaxX(float extra);
+ public float getMaxY();
+ public float getMaxY(float extra);
+ public float getCenterX();
+ public float getCenterX(float extra);
+ public float getCenterY();
+ public float getCenterY(float extra);
+
+ public IGuiBox setX(float x);
+ public IGuiBox setY(float y);
+ public IGuiBox setWidth(float width);
+ public IGuiBox setHeight(float height);
+ public IGuiBox setScale(float scale);
+
+ public IGuiBox move(float xOffset, float yOffset);
+ public IGuiBox resize(float xGrowth, float yGrowth);
+ public IGuiBox scale(float scale);
+
+ public default IGuiBox setXY(float x, float y) { return setX(x).setY(y); }
+ public default IGuiBox setBounds(float width, float height) { return setWidth(width).setHeight(height); }
+ public default IGuiBox set(float x, float y, float width, float height) { return setX(x).setY(y).setWidth(width).setHeight(height); }
+
+ public default boolean isColiding(float x, float y) { return getMinX() <= x && getMaxX() >= x && getMinY() <= y && getMaxY() >= y; }
+
+ public default DirectionList getColidingBorder(float x, float y, float margin) {
+ margin *= getScale();
+ float minX = getMinX();
+ float maxX = getMaxX();
+ float minY = getMinY();
+ float maxY = getMaxY();
+ DirectionList list = DirectionList.EMPTY;
+ if(y <= minY + margin && y >= minY) list = list.add(Direction.NORTH);
+ if(x >= maxX - margin && x <= maxX) list = list.add(Direction.EAST);
+ if(y >= maxY - margin && y <= maxY) list = list.add(Direction.SOUTH);
+ if(x <= minX + margin && x >= minX) list = list.add(Direction.WEST);
+ return list;
+ }
+
+ public default boolean isIntersecting(IGuiBox box) { return isIntersecting(box.getMinX(), box.getMaxX(), box.getMinY(), box.getMaxY()); }
+ public default boolean isIntersecting(float minX, float maxX, float minY, float maxY) {
+ float xMin = getMinX();
+ float yMin = getMinY();
+ float xMax = getMaxX();
+ float yMax = getMaxY();
+ return ((minX >= xMin && minX <= xMax) || (maxX >= xMin && maxX <= xMax)) && ((minY >= yMin && minY <= yMax) || (maxY >= yMin && maxY <= yMax));
+ }
+}
\ No newline at end of file
diff --git a/src/gui/java/speiger/src/coreengine/ui/gui/layout/box/IScreenBox.java b/src/gui/java/speiger/src/coreengine/ui/gui/layout/box/IScreenBox.java
new file mode 100644
index 0000000..f2ac94a
--- /dev/null
+++ b/src/gui/java/speiger/src/coreengine/ui/gui/layout/box/IScreenBox.java
@@ -0,0 +1,18 @@
+package speiger.src.coreengine.ui.gui.layout.box;
+
+public interface IScreenBox
+{
+ public float getBaseScale();
+ public float getBaseWidth();
+ public float getBaseHeight();
+
+ public default float getSquaredBaseWidth() {
+ float value = getBaseWidth();
+ return value * value;
+ }
+
+ public default float getSquaredBaseHeight() {
+ float value = getBaseWidth();
+ return value * value;
+ }
+}
diff --git a/src/gui/java/speiger/src/coreengine/ui/gui/layout/box/ParentBox.java b/src/gui/java/speiger/src/coreengine/ui/gui/layout/box/ParentBox.java
new file mode 100644
index 0000000..29a3c31
--- /dev/null
+++ b/src/gui/java/speiger/src/coreengine/ui/gui/layout/box/ParentBox.java
@@ -0,0 +1,139 @@
+package speiger.src.coreengine.ui.gui.layout.box;
+
+import java.util.List;
+
+import speiger.src.collections.objects.lists.ObjectArrayList;
+import speiger.src.collections.objects.lists.ObjectList;
+
+public class ParentBox implements IGuiBox
+{
+ ObjectList children = new ObjectArrayList<>();
+ IGuiBox parent;
+
+ float minX;
+ float minY;
+ float maxX;
+ float maxY;
+
+ public ParentBox(float value) {
+ minX = value;
+ minY = value;
+ maxX = -value;
+ maxY = -value;
+ }
+
+ public ParentBox(float paddingTop, float paddingLeft, float paddingBottom, float paddingRight) {
+ minX = paddingLeft;
+ minY = paddingTop;
+ maxX = paddingRight;
+ maxY = paddingBottom;
+ }
+
+ @Override
+ public IGuiBox addChild(IGuiBox box) {
+ children.add(box);
+ box.setParent(this);
+ return this;
+ }
+
+ @Override
+ public IGuiBox removeChild(IGuiBox box) {
+ if(children.remove(box)) box.setParent(null);
+ return this;
+ }
+
+ @Override
+ public IGuiBox clearChildren() {
+ for(int i = 0,m=children.size();i children() {
+ return children.unmodifiable();
+ }
+
+ @Override
+ public IGuiBox onChanged() {
+ for(int i = 0,m=children.size();i children) {
+ locked = false;
+ bounds = new float[children.size()*2];
+ weights = new float[children.size()*2];
+ for(int i = 0,m=children.size();i 0F) {
+ float scale = 1F / totals[2];
+ for(int i = 0,m=children.size();i 0F) {
+ float scale = 1F / totals[3];
+ for(int i = 0,m=children.size();i children = owner.children();
+ for(int i = 0,m=children.size();i children = owner.children();
+ for(int i = 0,m=children.size();i WIDTH;
+ case Y -> HEIGHT;
+ default -> this;
+ };
+ }
+
+ public float get(GuiComponent comp) { return get(comp.getBox()); }
+ public void set(GuiComponent comp, float value) { set(comp.getBox(), value); }
+ public float get(ConstrainedContext context) { return (isPosition() ? context.getOffset(isXAxis()) : context.get(isXAxis())); }
+
+ public float get(IGuiBox box) {
+ return switch(this) {
+ case X -> box.getBaseX();
+ case Y -> box.getBaseY();
+ case WIDTH -> box.getBaseWidth();
+ case HEIGHT -> box.getBaseHeight();
+ };
+ }
+
+ public void set(IGuiBox box, float value) {
+ switch(this) {
+ case X -> box.setX(value);
+ case Y -> box.setY(value);
+ case WIDTH -> box.setWidth(value);
+ case HEIGHT -> box.setHeight(value);
+ }
+ }
+ }
+}
diff --git a/src/gui/java/speiger/src/coreengine/ui/gui/layout/layouts/ILayout.java b/src/gui/java/speiger/src/coreengine/ui/gui/layout/layouts/ILayout.java
new file mode 100644
index 0000000..3cf0316
--- /dev/null
+++ b/src/gui/java/speiger/src/coreengine/ui/gui/layout/layouts/ILayout.java
@@ -0,0 +1,16 @@
+package speiger.src.coreengine.ui.gui.layout.layouts;
+
+import java.util.function.Consumer;
+
+import speiger.src.coreengine.ui.gui.component.base.GuiComponent;
+
+
+public interface ILayout extends Consumer {
+ public default ILayout add(GuiComponent comp) { return add(comp, null); }
+ public ILayout add(GuiComponent comp, T value);
+ public ILayout remove(GuiComponent comp);
+
+ public void apply(GuiComponent owner);
+ @Override
+ default void accept(GuiComponent t) { apply(t); }
+}
diff --git a/src/gui/java/speiger/src/coreengine/ui/gui/renderer/IUIRenderer.java b/src/gui/java/speiger/src/coreengine/ui/gui/renderer/IUIRenderer.java
new file mode 100644
index 0000000..c13ca8b
--- /dev/null
+++ b/src/gui/java/speiger/src/coreengine/ui/gui/renderer/IUIRenderer.java
@@ -0,0 +1,24 @@
+package speiger.src.coreengine.ui.gui.renderer;
+
+import speiger.src.coreengine.math.vector.quaternion.Quaternion;
+import speiger.src.coreengine.ui.gui.layout.box.IGuiBox;
+
+public interface IUIRenderer {
+ public boolean isInScissors(IGuiBox box);
+ public void pushScissors(IGuiBox box);
+ public void popScissors();
+
+ public void flush();
+
+ public void pushTransform();
+ public void popTransform();
+
+ public void translate(float z);
+ public void translate(float x, float y);
+ public void translate(float x, float y, float z);
+
+ public void scale(float scale);
+ public void scale(float x, float y);
+
+ public void rotate(Quaternion rotation);
+}
diff --git a/src/main/java/speiger/src/coreengine/NewRenderEngineTest.java b/src/main/java/speiger/src/coreengine/NewRenderEngineTest.java
index ee23722..d4bfc10 100644
--- a/src/main/java/speiger/src/coreengine/NewRenderEngineTest.java
+++ b/src/main/java/speiger/src/coreengine/NewRenderEngineTest.java
@@ -10,6 +10,7 @@ import org.lwjgl.util.freetype.FreeType;
import speiger.src.coreengine.assets.api.IAssetPackage;
import speiger.src.coreengine.assets.api.ID;
import speiger.src.coreengine.assets.manager.AssetManager;
+import speiger.src.coreengine.core.events.bus.EventBus;
import speiger.src.coreengine.math.vector.matrix.Matrix4f;
import speiger.src.coreengine.platform.graphics.api.buffer.VertexBuffer;
import speiger.src.coreengine.platform.graphics.api.buffer.states.BufferState;
@@ -41,11 +42,10 @@ import speiger.src.coreengine.platform.input.device.Keyboard;
import speiger.src.coreengine.platform.input.device.Mouse;
import speiger.src.coreengine.platform.input.window.Window;
import speiger.src.coreengine.platform.input.window.WindowManager;
-import speiger.src.coreengine.utils.eventbus.EventBus;
import speiger.src.coreengine.utils.helpers.IOUtils;
public class NewRenderEngineTest {
- EventBus bus = new EventBus();
+ EventBus bus = EventBus.builder().build();
WindowManager manager = new WindowManager();
AssetManager assets = AssetManager.single(IAssetPackage.asset(IOUtils.getBaseLocation()));
diff --git a/src/main/java/speiger/src/coreengine/rendering/gui/font/FontCache.java b/src/main/java/speiger/src/coreengine/rendering/gui/font/FontCache.java
deleted file mode 100644
index 4d16051..0000000
--- a/src/main/java/speiger/src/coreengine/rendering/gui/font/FontCache.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package speiger.src.coreengine.rendering.gui.font;
-
-public class FontCache {
- float size;
-
-
-}
diff --git a/src/main/java/speiger/src/coreengine/rendering/gui/layout/constraints/ConstraintContainer.java b/src/main/java/speiger/src/coreengine/rendering/gui/layout/constraints/ConstraintContainer.java
index 6808599..70e5a54 100644
--- a/src/main/java/speiger/src/coreengine/rendering/gui/layout/constraints/ConstraintContainer.java
+++ b/src/main/java/speiger/src/coreengine/rendering/gui/layout/constraints/ConstraintContainer.java
@@ -1,67 +1,67 @@
-package speiger.src.coreengine.rendering.gui.layout.constraints;
-
-import speiger.src.coreengine.rendering.gui.components.base.GuiComponent;
-import speiger.src.coreengine.rendering.gui.layout.constraints.IConstraint.Target;
-
-public class ConstraintContainer {
- private static final int CONSTRAINT_LENGTH = 4;
- IConstraint[] constraints = new IConstraint[CONSTRAINT_LENGTH];
-
- private ConstraintContainer() {}
-
- public void apply(GuiComponent owner, GuiComponent parent, ConstrainedContext context) {
- for(int i = 0;i<4;i++) {
- if(constraints[i] == null) continue;
- constraints[i].apply(owner, parent, Target.by(i), context);
- }
- }
-
- public void fetch(GuiComponent owner, GuiComponent parent, ConstrainedContext context) {
- for(int i = 2;i WIDTH;
- case Y -> HEIGHT;
- default -> this;
- };
- }
-
- public float get(GuiComponent comp) { return get(comp.getBox()); }
- public void set(GuiComponent comp, float value) { set(comp.getBox(), value); }
- public float get(ConstrainedContext context) { return (isPosition() ? context.getOffset(isXAxis()) : context.get(isXAxis())); }
-
- public float get(IGuiBox box) {
- return switch(this) {
- case X -> box.getBaseX();
- case Y -> box.getBaseY();
- case WIDTH -> box.getBaseWidth();
- case HEIGHT -> box.getBaseHeight();
- };
- }
-
- public void set(IGuiBox box, float value) {
- switch(this) {
- case X -> box.setX(value);
- case Y -> box.setY(value);
- case WIDTH -> box.setWidth(value);
- case HEIGHT -> box.setHeight(value);
- }
- }
- }
-}
+package speiger.src.coreengine.rendering.gui.layout.constraints;
+
+import java.util.function.BooleanSupplier;
+
+import speiger.src.coreengine.rendering.gui.components.base.GuiComponent;
+import speiger.src.coreengine.rendering.gui.layout.constraints.Constraints.Center;
+import speiger.src.coreengine.rendering.gui.layout.constraints.Constraints.Children;
+import speiger.src.coreengine.rendering.gui.layout.constraints.Constraints.Conditional;
+import speiger.src.coreengine.rendering.gui.layout.constraints.Constraints.Parent;
+import speiger.src.coreengine.rendering.gui.layout.constraints.Constraints.Pixels;
+import speiger.src.coreengine.rendering.gui.layout.constraints.Constraints.Relative;
+import speiger.src.coreengine.rendering.gui.layout.constraints.Constraints.Weighted;
+import speiger.src.coreengine.rendering.guiOld.helper.box.IGuiBox;
+
+public interface IConstraint {
+ public void apply(GuiComponent owner, GuiComponent parent, Target target, ConstrainedContext context);
+ public void fetch(GuiComponent owner, GuiComponent parent, Target target, ConstrainedContext context);
+
+ public static IConstraint pixels(float value) { return Pixels.of(value); }
+ public static IConstraint pixelsInv(float value) { return Pixels.inverted(value); }
+
+ public static IConstraint parent() { return Parent.of(); }
+ public static IConstraint parent(float padding) { return Parent.of(padding); }
+ public static IConstraint parentInv() { return Parent.inverted(); }
+ public static IConstraint parentInv(float padding) { return Parent.inverted(padding); }
+
+ public static IConstraint children() { return Children.onlyBounds(); }
+ public static IConstraint children(float padding) { return Children.onlyBounds(padding); }
+ public static IConstraint childrenPos() { return Children.withPos(); }
+ public static IConstraint childrenPos(float padding) { return Children.withPos(padding); }
+
+ public static IConstraint relative(float value) { return Relative.of(value); }
+ public static IConstraint relative(float value, float padding) { return Relative.of(value, padding); }
+
+ public static IConstraint weightedPos() { return Weighted.of(0F); }
+ public static IConstraint weightedInvPos() { return Weighted.inverted(0F); }
+ public static IConstraint weighted(float weight) { return Weighted.of(weight); }
+ public static IConstraint weightedPad(float weight, float padding) { return Weighted.padded(weight, padding); }
+ public static IConstraint weightedMin(float weight, float minimum) { return Weighted.minimum(weight, minimum); }
+ public static IConstraint weighted(float weight, float minimum, float padding) { return Weighted.of(weight, minimum, padding); }
+ public static IConstraint weightedInv(float weight) { return Weighted.inverted(weight);}
+ public static IConstraint weightedInvPad(float weight, float padding) { return Weighted.invertedPadded(weight, padding); }
+ public static IConstraint weightedInvMin(float weight, float minimum) { return Weighted.invertedMinimum(weight, minimum); }
+ public static IConstraint weightedInv(float weight, float minimum, float padding) { return Weighted.inverted(weight, minimum, padding); }
+
+ public static IConstraint center() { return Center.of(); }
+ public static IConstraint conditional(BooleanSupplier supplier, IConstraint onTrue, IConstraint onFalse) { return Conditional.of(supplier, onTrue, onFalse); }
+
+ public static interface ISimpleConstraint extends IConstraint {
+ @Override
+ default void apply(GuiComponent owner, GuiComponent parent, Target target, ConstrainedContext context) { apply(owner.getBox(), parent == null ? owner.screen().getBox() : parent.getBox(), target, context); }
+ public void apply(IGuiBox owner, IGuiBox parent, Target target, ConstrainedContext context);
+
+ @Override
+ default void fetch(GuiComponent owner, GuiComponent parent, Target target, ConstrainedContext context) { fetch(owner.getBox(), parent == null ? owner.screen().getBox() : parent.getBox(), target, context); }
+ public void fetch(IGuiBox owner, IGuiBox parent, Target target, ConstrainedContext context);
+ }
+
+ public static enum Target {
+ X,
+ Y,
+ WIDTH,
+ HEIGHT;
+
+ static final Target[] BY_INDEX = values();
+
+ public static Target by(int index) { return BY_INDEX[index & 3]; }
+ public static Target pos(boolean x) { return x ? X : Y; }
+ public static Target bounds(boolean x) { return x ? WIDTH : HEIGHT; }
+ public boolean isPosition() { return this == X || this == Y; }
+ public boolean isXAxis() { return this == X || this == WIDTH; }
+ public Target asArea() {
+ return switch(this) {
+ case X -> WIDTH;
+ case Y -> HEIGHT;
+ default -> this;
+ };
+ }
+
+ public float get(GuiComponent comp) { return get(comp.getBox()); }
+ public void set(GuiComponent comp, float value) { set(comp.getBox(), value); }
+ public float get(ConstrainedContext context) { return (isPosition() ? context.getOffset(isXAxis()) : context.get(isXAxis())); }
+
+ public float get(IGuiBox box) {
+ return switch(this) {
+ case X -> box.getBaseX();
+ case Y -> box.getBaseY();
+ case WIDTH -> box.getBaseWidth();
+ case HEIGHT -> box.getBaseHeight();
+ };
+ }
+
+ public void set(IGuiBox box, float value) {
+ switch(this) {
+ case X -> box.setX(value);
+ case Y -> box.setY(value);
+ case WIDTH -> box.setWidth(value);
+ case HEIGHT -> box.setHeight(value);
+ }
+ }
+ }
+}
diff --git a/src/math/java/speiger/src/coreengine/math/direction/Direction.java b/src/math/java/speiger/src/coreengine/math/direction/Direction.java
new file mode 100644
index 0000000..bd12543
--- /dev/null
+++ b/src/math/java/speiger/src/coreengine/math/direction/Direction.java
@@ -0,0 +1,108 @@
+package speiger.src.coreengine.math.direction;
+
+import java.util.function.Predicate;
+
+import speiger.src.coreengine.math.vector.ints.Vec2i;
+
+public enum Direction {
+ NORTH(0, 2, "North", Axis.VERTICAL, Vec2i.of(0, 1)),
+ EAST(1, 3, "East", Axis.HORIZONTAL, Vec2i.of(1, 0)),
+ SOUTH(2, 0, "South", Axis.VERTICAL, Vec2i.of(0, -1)),
+ WEST(3, 1, "West", Axis.HORIZONTAL, Vec2i.of(-1, 0));
+
+ private static final Direction[] VALUES;
+ private static final Direction[] ROTATIONS;
+ final int index;
+ final int rotationIndex;
+ final String name;
+ final Axis axis;
+ final Vec2i offset;
+ final boolean positive;
+ final Rotation rotation;
+
+ private Direction(int direction, int rotation, String display, Axis axis, Vec2i offset) {
+ index = direction;
+ rotationIndex = rotation;
+ name = display;
+ this.axis = axis;
+ this.offset = offset;
+ positive = index < 2;
+ this.rotation = Rotation.fromFacing(this);
+ }
+
+ public int getIndex() { return index; }
+ public boolean isPositive() { return positive; }
+
+ public boolean isXAxis() { return axis == Axis.HORIZONTAL; }
+ public boolean isZAxis() { return axis == Axis.VERTICAL; }
+ public Axis getAxis() { return axis; }
+
+
+ public Vec2i getOffset() { return offset; }
+ public float getMultiplier() { return positive ? 1F : -1F; }
+
+ public String getName() { return name; }
+
+ public int getRotationIndex() { return rotationIndex; }
+ public int getRotation() { return rotationIndex * 90; }
+ public int getRotation(Direction other) {
+ if(other == backwards()) return getRotation() - 45;
+ else if(other == forward()) return getRotation() + 45;
+ return getRotation();
+ }
+
+ public Rotation toRotation() { return rotation; }
+ public Direction rotate(int amount) { return byIndex(index + amount); }
+ public Direction forward() { return byIndex(index + 1); }
+ public Direction backwards() { return byIndex(index - 1); }
+ public Direction opposite() { return byIndex(index + 2); }
+ @Override
+ public String toString() { return getName()+": "+offset; }
+
+ public static Direction byIndex(int index) { return VALUES[index & 3]; }
+ public static Direction byRotationIndex(int index) { return ROTATIONS[index & 3]; }
+ public static Direction byYaw(float value) { return byRotationIndex((int)(value / 90) & 3); }
+
+ static {
+ Direction[] values = values();
+ VALUES = new Direction[values.length];
+ ROTATIONS = new Direction[values.length];
+ for(Direction entry : values) {
+ VALUES[entry.getIndex()] = entry;
+ ROTATIONS[entry.getRotationIndex()] = entry;
+ }
+ }
+
+ public static enum Rotation {
+ NONE(Direction.NORTH),
+ CLOCKWISE(Direction.EAST),
+ OPPOSITE(Direction.SOUTH),
+ COUNTER_CLOCKWISE(Direction.WEST);
+
+ static final Rotation[] ROTATION = Rotation.values();
+ Direction facing;
+
+ private Rotation(Direction facing) {
+ this.facing = facing;
+ }
+
+ public static Rotation fromFacing(Direction facing) { return ROTATION[facing.getIndex()]; }
+ public Direction toFacing() { return facing; }
+ }
+
+ public static enum Axis implements Predicate {
+ HORIZONTAL(5),
+ VERTICAL(10);
+
+ int code;
+
+ private Axis(int code) {
+ this.code = code;
+ }
+ public int getCode() { return code; }
+
+ @Override
+ public boolean test(Direction t) { return t.getAxis() == this; }
+ }
+
+}
diff --git a/src/math/java/speiger/src/coreengine/math/direction/DirectionList.java b/src/math/java/speiger/src/coreengine/math/direction/DirectionList.java
new file mode 100644
index 0000000..f70fa8e
--- /dev/null
+++ b/src/math/java/speiger/src/coreengine/math/direction/DirectionList.java
@@ -0,0 +1,160 @@
+package speiger.src.coreengine.math.direction;
+
+import java.util.EnumSet;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.StringJoiner;
+import java.util.function.Predicate;
+
+import speiger.src.collections.objects.lists.ObjectArrayList;
+import speiger.src.collections.objects.lists.ObjectList;
+import speiger.src.coreengine.math.MathUtils;
+import speiger.src.coreengine.math.direction.Direction.Axis;
+import speiger.src.coreengine.math.vector.ints.Vec2i;
+
+public final class DirectionList implements Iterable, Predicate {
+ static final DirectionList[] FACINGS = createArray();
+ public static final DirectionList EMPTY = ofNumber(0);
+ public static final DirectionList NORTH = ofFacings(Direction.NORTH);
+ public static final DirectionList EAST = ofFacings(Direction.EAST);
+ public static final DirectionList SOUTH = ofFacings(Direction.SOUTH);
+ public static final DirectionList WEST = ofFacings(Direction.WEST);
+ public static final DirectionList NORTH_EAST = ofFacings(Direction.NORTH, Direction.EAST);
+ public static final DirectionList SOUTH_EAST = ofFacings(Direction.EAST, Direction.SOUTH);
+ public static final DirectionList SOUTH_WEST = ofFacings(Direction.SOUTH, Direction.WEST);
+ public static final DirectionList NORTH_WEST = ofFacings(Direction.WEST, Direction.NORTH);
+ public static final DirectionList VERTICAL = ofFacings(Direction.NORTH, Direction.SOUTH);
+ public static final DirectionList HORIZONTAL = ofFacings(Direction.EAST, Direction.WEST);
+ public static final DirectionList ALL = ofFacings(Direction.NORTH, Direction.SOUTH, Direction.EAST, Direction.WEST);
+ final byte code;
+ final byte next;
+ final byte opposite;
+ final byte prev;
+ final byte count;
+ final Vec2i offset;
+ final Direction[] array;
+
+ private DirectionList(int initCode) {
+ code = (byte)MathUtils.clamp(0, 15, initCode);
+ Vec2i pos = Vec2i.mutable();
+ ObjectList facings = new ObjectArrayList<>();
+ for(int i = 0;i < 4;i++) {
+ if((code & 1 << i) != 0) {
+ pos.add(Direction.byIndex(i).getOffset());
+ facings.add(Direction.byIndex(i));
+ }
+ }
+ int[] data = new int[3];
+ for(int i = 0,m = facings.size();i < m;i++) {
+ Direction face = facings.get(i);
+ data[0] |= 1 << face.forward().getIndex();
+ data[1] |= 1 << face.opposite().getIndex();
+ data[2] |= 1 << face.backwards().getIndex();
+ }
+ next = (byte)MathUtils.clamp(0, 15, data[0]);
+ opposite = (byte)MathUtils.clamp(0, 15, data[1]);
+ prev = (byte)MathUtils.clamp(0, 15, data[2]);
+ offset = pos.asImmutable();
+ count = (byte)facings.size();
+ array = facings.toArray(new Direction[facings.size()]);
+ }
+
+ public static DirectionList ofFacing(Direction facing) { return FACINGS[1 << facing.getIndex()]; }
+ public static DirectionList ofFacings(Direction... facings) { return FACINGS[toNumber(facings)]; }
+ public static DirectionList ofFlags(boolean[] facings) { return FACINGS[toNumber(facings)]; }
+ public static DirectionList ofNumber(int value) { return FACINGS[value & 15]; }
+ public static DirectionList ofAxis(Axis axis) { return FACINGS[axis.getCode()]; }
+
+ public static DirectionList fromVec(Vec2i value) {
+ value = value.clamp(-1, 1);
+ for(int i = 0;i < 16;i++) { if(FACINGS[i].getOffset().equals(value)) { return FACINGS[i]; } }
+ return FACINGS[0];
+ }
+
+ public Set toFacings() { return isEmpty() ? EnumSet.noneOf(Direction.class) : EnumSet.copyOf(ObjectArrayList.wrap(array)); }
+ public boolean[] toFlags() { return toFlags(array); }
+
+ public int getRotation() {
+ switch(count) {
+ case 1: return array[0].getRotation();
+ case 2: return array[0].getRotation(array[1]);
+ default: return 0;
+ }
+ }
+
+ public DirectionList rotate(int amount) {
+ switch(amount & 3) {
+ case 1: return FACINGS[next];
+ case 2: return FACINGS[opposite];
+ case 3: return FACINGS[prev];
+ default: return this;
+ }
+ }
+
+ public DirectionList invert() { return FACINGS[15 - code]; }
+ public DirectionList opposite() { return FACINGS[opposite]; }
+ public DirectionList add(Direction facing) { return FACINGS[code | (1 << facing.getIndex())]; }
+ public DirectionList add(DirectionList facings) { return FACINGS[code | facings.code]; }
+ public DirectionList remove(Direction facing) { return FACINGS[code & ~(1 << facing.getIndex())]; }
+ public DirectionList remove(DirectionList facings) { return FACINGS[code & ~facings.code]; }
+ public boolean contains(Direction direction) { return (code & 1 << direction.getIndex()) != 0; }
+ public boolean contains(DirectionList facings) { return (code & facings.code) == facings.code; }
+ public boolean containsAny(DirectionList facings) { return (code & facings.code) != 0; }
+ public boolean notContains(Direction direction) { return (code & 1 << direction.getIndex()) == 0; }
+ public boolean notContains(DirectionList facings) { return (code & facings.code) != facings.code; }
+ public DirectionList flipFacing(Direction facing) { return contains(facing) ? remove(facing).add(facing.opposite()) : this; }
+ public DirectionList flipAxis(Axis axis) {
+ DirectionList result = this;
+ for(int i = 0,m = array.length;i < m;i++) { if(array[i].getAxis() == axis) { result = result.remove(array[i]).add(array[i].opposite()); } }
+ return result;
+ }
+
+ public Vec2i getOffset() { return offset; }
+ public Direction getFacing(int index) { return array[index]; }
+ public int getCode() { return code; }
+ public int size() { return count; }
+ public boolean isEmpty() { return code == 0; }
+ public boolean isFull() { return code == 15; }
+
+ @Override
+ public String toString() {
+ StringJoiner joiner = new StringJoiner(",", "[", "]");
+ for(int i = 0,m = array.length;i < m;i++) { joiner.add(array[i].getName()); }
+ return joiner.toString();
+ }
+
+ @Override
+ public boolean test(Direction t) { return (code & 1 << t.getIndex()) != 0; }
+
+ @Override
+ public Iterator iterator() {
+ return new Iterator() {
+ int index = 0;
+
+ @Override
+ public boolean hasNext() { return index < size(); }
+ @Override
+ public Direction next() { return array[index++]; }
+ };
+ }
+
+ public static int toNumber(Direction... facings) {
+ int value = 0;
+ for(int i = 0,m = facings.length;i < m;i++) { value |= (1 << facings[i].getIndex()); }
+ return value & 15;
+ }
+
+ public static int toNumber(boolean[] facings) { return (facings[0] ? 1 : 0) << 0 | (facings[1] ? 1 : 0) << 1 | (facings[2] ? 1 : 0) << 2 | (facings[3] ? 1 : 0) << 3; }
+
+ public static boolean[] toFlags(Direction... facings) {
+ boolean[] array = new boolean[4];
+ for(int i = 0,m = facings.length;i < m;i++) { array[facings[i].getIndex()] = true; }
+ return array;
+ }
+
+ private static DirectionList[] createArray() {
+ DirectionList[] facings = new DirectionList[16];
+ for(int i = 0;i < 16;i++) { facings[i] = new DirectionList(i); }
+ return facings;
+ }
+}