More work on the rewrite

This commit is contained in:
2026-08-05 17:15:58 +02:00
parent 060f5e534c
commit 9362707ccc
45 changed files with 2647 additions and 277 deletions
+20 -8
View File
@@ -12,22 +12,34 @@
<attribute name="gradle_used_by_scope" value="main,test"/> <attribute name="gradle_used_by_scope" value="main,test"/>
</attributes> </attributes>
</classpathentry> </classpathentry>
<classpathentry kind="src" output="bin/assets" path="src/assets/java">
<attributes>
<attribute name="gradle_scope" value="assets"/>
<attribute name="gradle_used_by_scope" value="main,test,assets,graphics,gui"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="bin/events" path="src/events/java">
<attributes>
<attribute name="gradle_scope" value="events"/>
<attribute name="gradle_used_by_scope" value="main,test,assets,events,graphics,gui"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="bin/graphics" path="src/graphics/java"> <classpathentry kind="src" output="bin/graphics" path="src/graphics/java">
<attributes> <attributes>
<attribute name="gradle_scope" value="graphics"/> <attribute name="gradle_scope" value="graphics"/>
<attribute name="gradle_used_by_scope" value="graphics"/> <attribute name="gradle_used_by_scope" value="main,test,graphics,gui"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="bin/gui" path="src/gui/java">
<attributes>
<attribute name="gradle_scope" value="gui"/>
<attribute name="gradle_used_by_scope" value="main,test,gui"/>
</attributes> </attributes>
</classpathentry> </classpathentry>
<classpathentry kind="src" output="bin/math" path="src/math/java"> <classpathentry kind="src" output="bin/math" path="src/math/java">
<attributes> <attributes>
<attribute name="gradle_scope" value="math"/> <attribute name="gradle_scope" value="math"/>
<attribute name="gradle_used_by_scope" value="main,graphics,math"/> <attribute name="gradle_used_by_scope" value="main,test,graphics,gui,math"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="bin/assets" path="src/assets/java">
<attributes>
<attribute name="gradle_scope" value="assets"/>
<attribute name="gradle_used_by_scope" value="assets"/>
</attributes> </attributes>
</classpathentry> </classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-25/"/> <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-25/"/>
+13 -14
View File
@@ -2,22 +2,21 @@
<projectDescription> <projectDescription>
<name>SimpleJavaEngine</name> <name>SimpleJavaEngine</name>
<comment></comment> <comment></comment>
<projects> <projects/>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures> <natures>
<nature>org.eclipse.jdt.core.javanature</nature> <nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.buildship.core.gradleprojectnature</nature> <nature>org.eclipse.buildship.core.gradleprojectnature</nature>
</natures> </natures>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments/>
</buildCommand>
<buildCommand>
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
<arguments/>
</buildCommand>
</buildSpec>
<linkedResources/>
<filteredResources/>
</projectDescription> </projectDescription>
+3 -11
View File
@@ -49,7 +49,7 @@ repositories {
name = "Speiger Maven" name = "Speiger Maven"
url = "https://maven.speiger.com/repository/main" 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 { jar {
manifest {
attributes "Main-Class": 'speiger.src.coreengine.NewRenderEngineTest'
}
sourceSets.each { ss -> sourceSets.each { ss ->
from ss.output from ss.output
} }
from { from {
configurations.runtimeClasspath.collect { configurations.runtimeClasspath.collect {
it.isDirectory() ? it : zipTree(it) !it.exists() ? null : (it.isDirectory() ? it : zipTree(it))
} }
} }
duplicatesStrategy = DuplicatesStrategy.INCLUDE duplicatesStrategy = DuplicatesStrategy.INCLUDE
@@ -113,11 +110,6 @@ task srcJar(type: Jar) {
sourceSets.each { ss -> sourceSets.each { ss ->
from ss.allSource from ss.allSource
} }
from {
configurations.runtimeClasspath.collect {
it.isDirectory() ? it : zipTree(it)
}
}
duplicatesStrategy = DuplicatesStrategy.INCLUDE duplicatesStrategy = DuplicatesStrategy.INCLUDE
} }
@@ -1,5 +1,6 @@
package speiger.src.coreengine.core.events.api; package speiger.src.coreengine.core.events.api;
public class Event { public class Event {
boolean canceled = false;
public final Event self() { return this; }
} }
@@ -2,23 +2,7 @@ package speiger.src.coreengine.core.events.api;
public enum EventPriority public enum EventPriority
{ {
HIGH(0), HIGH,
MEDIUM(1), MEDIUM,
LOW(2); LOW;
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;
}
} }
@@ -1,6 +1,8 @@
package speiger.src.coreengine.core.events.api; package speiger.src.coreengine.core.events.api;
public interface ICancelableEvent { public interface ICancelableEvent {
void setCanceled(boolean value); Event self();
boolean isCanceled(); default boolean isCancelable() { return true; }
default void setCanceled(boolean value) { self().canceled = value; }
default boolean isCanceled() { return self().canceled; }
} }
@@ -5,14 +5,15 @@ import java.util.Optional;
import java.util.function.Predicate; import java.util.function.Predicate;
import speiger.src.coreengine.core.events.api.Event; 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.IEventDispatcher;
import speiger.src.coreengine.core.events.utility.IEventExceptionHandler; import speiger.src.coreengine.core.events.utility.IEventExceptionHandler;
public class BusBuilder { public class BusBuilder {
IEventDispatcher dispatcher = IEventDispatcher.DIRECT; IEventDispatcher dispatcher = IEventDispatcher.DIRECT;
Optional<IEventExceptionHandler> exceptions = Optional.empty(); Optional<IEventExceptionHandler> exceptions = Optional.empty();
Predicate<Class<? extends Event>> filter = _ -> true; ClassFilter typeCheck = new ClassFilter(false, _ -> true, "");
boolean checkType = false; boolean running = true;
public BusBuilder dispatcher(IEventDispatcher dispatcher) { public BusBuilder dispatcher(IEventDispatcher dispatcher) {
this.dispatcher = Objects.requireNonNull(dispatcher, "A Dispatcher is required"); this.dispatcher = Objects.requireNonNull(dispatcher, "A Dispatcher is required");
@@ -24,9 +25,13 @@ public class BusBuilder {
return this; return this;
} }
public BusBuilder filter(Predicate<Class<? extends Event>> filter) { public BusBuilder filter(Predicate<Class<? extends Event>> filter, String filterMessage) {
this.filter = Objects.requireNonNull(filter, "A Filter is required"); this.typeCheck = new ClassFilter(true, Objects.requireNonNull(filter, "A Filter is required"), Objects.requireNonNull(filterMessage, "A Filter Message is required"));
checkType = true; return this;
}
public BusBuilder beginShutdown() {
running = false;
return this; return this;
} }
@@ -28,8 +28,7 @@ public class EventBus implements IEventExceptionHandler {
private static final Lookup LOOKUP = MethodHandles.lookup(); private static final Lookup LOOKUP = MethodHandles.lookup();
IEventDispatcher dispatcher; IEventDispatcher dispatcher;
IEventExceptionHandler exceptions; IEventExceptionHandler exceptions;
Predicate<Class<? extends Event>> filter; ClassFilter typeCheck;
boolean checkType;
boolean shutdown = false; boolean shutdown = false;
Map<Class<? extends Event>, Listeners> listeners = new Object2ObjectConcurrentOpenHashMap<>(); Map<Class<? extends Event>, Listeners> listeners = new Object2ObjectConcurrentOpenHashMap<>();
@@ -37,8 +36,8 @@ public class EventBus implements IEventExceptionHandler {
EventBus(BusBuilder builder) { EventBus(BusBuilder builder) {
dispatcher = builder.dispatcher; dispatcher = builder.dispatcher;
exceptions = builder.exceptions.orElse(this); exceptions = builder.exceptions.orElse(this);
checkType = builder.checkType; typeCheck = builder.typeCheck;
filter = builder.filter; shutdown = !builder.running;
} }
public static BusBuilder builder() { public static BusBuilder builder() {
@@ -49,8 +48,16 @@ public class EventBus implements IEventExceptionHandler {
return new Subscriptions(this); return new Subscriptions(this);
} }
public void start() {
shutdown = false;
}
public void shutdown() {
shutdown = true;
}
public <T extends Event> T post(T event) { public <T extends Event> T post(T event) {
if(shutdown) throw new IllegalStateException("Bus is shutdown"); if(shutdown) return event;
validateType(event.getClass()); validateType(event.getClass());
Listeners listener = listeners.get(event.getClass()); Listeners listener = listeners.get(event.getClass());
if(listener == null) return event; if(listener == null) return event;
@@ -59,12 +66,12 @@ public class EventBus implements IEventExceptionHandler {
} }
public <T extends Event> CompletableFuture<T> postAsync(T event) { public <T extends Event> CompletableFuture<T> postAsync(T event) {
if(shutdown) throw new IllegalStateException("Bus is shutdown"); if(shutdown) return CompletableFuture.completedFuture(event);
return CompletableFuture.supplyAsync(() -> post(event)); return CompletableFuture.supplyAsync(() -> post(event));
} }
public <T extends Event> CompletableFuture<T> postAsync(T event, Executor executor) { public <T extends Event> CompletableFuture<T> 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); return CompletableFuture.supplyAsync(() -> post(event), executor);
} }
@@ -157,6 +164,12 @@ public class EventBus implements IEventExceptionHandler {
} }
} }
public static record ClassFilter(boolean enabled, Predicate<Class<? extends Event>> event, String message) {
public boolean isInvalid(Class<? extends Event> filter) {
return enabled() && !event().test(filter);
}
}
private void register(SubscribeEvent data, Object obj, List<Subscription> listeners) { private void register(SubscribeEvent data, Object obj, List<Subscription> listeners) {
if(data == null || !(obj instanceof Consumer)) return; if(data == null || !(obj instanceof Consumer)) return;
validateType(data.value()); validateType(data.value());
@@ -173,7 +186,7 @@ public class EventBus implements IEventExceptionHandler {
} }
private void validateType(Class<? extends Event> type) { 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") @SuppressWarnings("unchecked")
@@ -1,27 +1,27 @@
package speiger.src.coreengine.core.events.utility; package speiger.src.coreengine.core.events.utility;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.function.Consumer; import java.util.function.Consumer;
import speiger.src.collections.objects.lists.ObjectArrayList; 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.Event;
import speiger.src.coreengine.core.events.api.EventPriority; import speiger.src.coreengine.core.events.api.EventPriority;
public class Listeners public class Listeners
{ {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Set<Consumer<Event>>[] unsortedListeners = new Set[EventPriority.getPriorities().length]; Queue<Consumer<Event>>[] unsortedListeners = new Queue[EventPriority.values().length];
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Consumer<Event>[] listeners = new Consumer[0]; Consumer<Event>[] listeners = new Consumer[0];
boolean rebuild = true; volatile boolean rebuild = true;
Listeners parent; Listeners parent;
List<Listeners> childs = null; List<Listeners> childs = null;
public Listeners() { public Listeners() {
for(int i = 0;i<unsortedListeners.length;i++) { for(int i = 0;i<unsortedListeners.length;i++) {
unsortedListeners[i] = new ObjectLinkedOpenHashSet<Consumer<Event>>(); unsortedListeners[i] = new ConcurrentLinkedQueue<>();
} }
} }
@@ -37,23 +37,17 @@ public class Listeners
} }
public void addListener(EventPriority priority, Consumer<Event> listener) { public void addListener(EventPriority priority, Consumer<Event> listener) {
synchronized(unsortedListeners) { if(unsortedListeners[priority.ordinal()].add(listener)) markDirty();
if(unsortedListeners[priority.getPriority()].add(listener)) markDirty();
}
} }
public void removeListeners(Consumer<Event> listener) { public void removeListeners(Consumer<Event> listener) {
synchronized(unsortedListeners) { for(int i = 0,m=unsortedListeners.length;i<m;i++) {
for(int i = 0,m=unsortedListeners.length;i<m;i++) { if(unsortedListeners[i].remove(listener)) markDirty();
if(unsortedListeners[i].remove(listener)) markDirty();
}
} }
} }
public void getListeners(EventPriority entry, List<Consumer<Event>> events) { public void getListeners(EventPriority entry, List<Consumer<Event>> events) {
synchronized(unsortedListeners) { events.addAll(unsortedListeners[entry.ordinal()]);
events.addAll(unsortedListeners[entry.getPriority()]);
}
if(parent != null) parent.getListeners(entry, events); if(parent != null) parent.getListeners(entry, events);
} }
@@ -78,11 +72,11 @@ public class Listeners
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void rebuildListeners() { public void rebuildListeners() {
rebuild = false;
List<Consumer<Event>> result = new ObjectArrayList<>(); List<Consumer<Event>> result = new ObjectArrayList<>();
for(EventPriority entry : EventPriority.getPriorities()) { for(EventPriority entry : EventPriority.values()) {
getListeners(entry, result); getListeners(entry, result);
} }
listeners = result.toArray(new Consumer[result.size()]); listeners = result.toArray(new Consumer[result.size()]);
rebuild = false;
} }
} }
@@ -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: <a href=https://github.com/lukaszdk/texture-atlas-generator/blob/master/AtlasGenerator.java>AtlasGenerator</a>
*/
public class AtlasStitcher<T extends Entry> {
final int maxWidth;
final int maxHeight;
int width;
int height;
int pixelsUsed;
boolean valid = true;
List<Record<T>> toStitch = new ObjectArrayList<>();
Slot<T> 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<T>::new).forEach(this::add); }
@SuppressWarnings("unchecked")
public void addAll(T...entries) {
for(T entry : entries) {
add(new Record<>(entry));
}
}
private void add(Record<T> 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<T> entry : toStitch) {
if(!addToSlot(entry)) {
valid = false;
return;
}
}
}
public void process(IAtlasScanner<T> scanner) {
if(slot == null) return;
slot.scan(scanner);
}
private boolean addToSlot(Record<T> 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<T> 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 extends Entry>(T entry, int width, int height) implements Comparable<Record<T>> {
public Record(T entry) {
this(entry, entry.width(), entry.height());
}
public int pixels() { return width() * height(); }
@Override
public int compareTo(Record<T> 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<T extends Entry> {
int x;
int y;
int width;
int height;
Record<T> record;
Slot<T>[] 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<T> 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<T> 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<T> 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<T> {
public void accept(T entry, int x, int y);
}
public static interface Entry {
public ID id();
public int width();
public int height();
}
}
@@ -7,10 +7,11 @@ import java.util.concurrent.ConcurrentLinkedDeque;
import speiger.src.collections.longs.collections.LongIterable; import speiger.src.collections.longs.collections.LongIterable;
import speiger.src.collections.longs.maps.impl.concurrent.Long2ObjectConcurrentOpenHashMap; import speiger.src.collections.longs.maps.impl.concurrent.Long2ObjectConcurrentOpenHashMap;
import speiger.src.collections.longs.maps.interfaces.Long2ObjectMap; 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.platform.input.window.IWindowListener.Reason;
import speiger.src.coreengine.utils.eventbus.Event; import speiger.src.coreengine.platform.input.window.Window;
import speiger.src.coreengine.utils.eventbus.EventBus;
public abstract class AbstractDevice<T, E> implements InputDevice { public abstract class AbstractDevice<T, E> implements InputDevice {
protected Long2ObjectMap<Deque<E>> queues = new Long2ObjectConcurrentOpenHashMap<>(); protected Long2ObjectMap<Deque<E>> queues = new Long2ObjectConcurrentOpenHashMap<>();
@@ -51,7 +52,7 @@ public abstract class AbstractDevice<T, E> implements InputDevice {
protected boolean pushEvent(Event event) { protected boolean pushEvent(Event event) {
if(bus == null) return true; if(bus == null) return true;
bus.post(event); bus.post(event);
return event.isCancelable() && event.isCanceled(); return event instanceof ICancelableEvent cancel && cancel.isCanceled();
} }
protected void push(long windowId, E task) { protected void push(long windowId, E task) {
@@ -16,11 +16,11 @@ import speiger.src.collections.ints.sets.IntSet;
import speiger.src.collections.longs.collections.LongIterator; import speiger.src.collections.longs.collections.LongIterator;
import speiger.src.collections.longs.sets.LongOpenHashSet; import speiger.src.collections.longs.sets.LongOpenHashSet;
import speiger.src.collections.longs.sets.LongSet; 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.JoyStickData;
import speiger.src.coreengine.platform.input.device.Joystick.JoyStickTask; import speiger.src.coreengine.platform.input.device.Joystick.JoyStickTask;
import speiger.src.coreengine.platform.input.events.JoystickEvent; import speiger.src.coreengine.platform.input.events.JoystickEvent;
import speiger.src.coreengine.platform.input.window.WindowManager; import speiger.src.coreengine.platform.input.window.WindowManager;
import speiger.src.coreengine.utils.eventbus.EventBus;
public class Joystick extends AbstractDevice<JoyStickData, JoyStickTask> { public class Joystick extends AbstractDevice<JoyStickData, JoyStickTask> {
public static final Joystick INSTANCE = new Joystick(); public static final Joystick INSTANCE = new Joystick();
@@ -1,6 +1,6 @@
package speiger.src.coreengine.platform.input.events; 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 { public class JoystickEvent extends Event {
final long window; final long window;
@@ -1,16 +1,15 @@
package speiger.src.coreengine.platform.input.events; 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; final long window;
public KeyEvent(long window) { public KeyEvent(long window) {
this.window = window; this.window = window;
} }
@Override
public boolean isCancelable() { return true; }
public long window() { return window; } public long window() { return window; }
public static class Key extends KeyEvent { public static class Key extends KeyEvent {
@@ -1,8 +1,9 @@
package speiger.src.coreengine.platform.input.events; 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; final long window;
int x; int x;
int y; int y;
@@ -30,8 +31,6 @@ public abstract class MouseEvent extends Event {
//TODO implement support for Scaling //TODO implement support for Scaling
@Override
public boolean isCancelable() { return true; }
public boolean isForced() { return false; } public boolean isForced() { return false; }
public static class Click extends MouseEvent { public static class Click extends MouseEvent {
@@ -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<Target, IAction> actions;
BiConsumer<GuiAnimation, GuiComponent> listener;
float duration;
public GuiAnimation(Object2ObjectMap<Target, IAction> actions, BiConsumer<GuiAnimation, GuiComponent> 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<GuiAnimation, GuiComponent> listener() { return listener; }
public float duration() { return duration; }
public void apply(ToFloatFunction<Target> getter, ObjectFloatConsumer<Target> setter, float progress) {
for(Entry<Target, IAction> entry : Object2ObjectMaps.fastIterable(actions)) {
IAction action = entry.getValue();
action.apply(entry.getKey(), getter, setter, Math.min(progress, action.duration()));
}
}
public static class Builder {
Object2ObjectMap<Target, IAction> actions = new LinkedEnum2ObjectMap<>(Target.class);
BiConsumer<GuiAnimation, GuiComponent> 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<GuiAnimation, GuiComponent> 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<IGuiBox> provider;
private Target(ToFloatFunction<IGuiBox> provider) {
this.provider = provider;
}
public int changeState() { return this == X || this == Y ? 1 : 2; }
public float get(GuiComponent component) { return provider.applyAsFloat(component.getBox()); }
}
}
@@ -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<Target>, ObjectFloatConsumer<Target> {
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;
}
}
@@ -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<Target> {
protected GuiComponent owner;
float xDiff = 0F;
float yDiff = 0F;
float widthDiff = 0F;
float heightDiff = 0F;
float scaleDiff = 1F;
int changeState = 0;
Object2ObjectMap<GuiAnimation, GuiAnimationSnapshot> activeAnimations = Object2ObjectMap.builder().linkedMap();
List<GuiAnimation> 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<GuiAnimation, GuiAnimationSnapshot> 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<m;i++) {
GuiAnimation animation = toDelete.get(i);
activeAnimations.remove(animation);
if(animation.listener() != null) {
animation.listener().accept(animation, owner);
}
}
return activeAnimations.size() > 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();
}
}
@@ -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<Target> getter, ObjectFloatConsumer<Target> 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<Target> getter, ObjectFloatConsumer<Target> 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<Target> getter, ObjectFloatConsumer<Target> 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<Target> getter, ObjectFloatConsumer<Target> 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<Target> getter, ObjectFloatConsumer<Target> setter, float progress) {
float duration = action.duration();
action.apply(target, getter, setter, progress >= duration ? duration - (progress - duration) : progress);
}
}
}
@@ -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<Target> getter, ObjectFloatConsumer<Target> setter, float progress) {
setter.accept(target, MathUtils.lerp(getter.applyAsFloat(target), targetValue, (float)function.ease(progress, duration)));
}
}
@@ -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<GuiComponent> children = new ObjectArrayList<>();
InteractionContainer interactions = new InteractionContainer(this::isInteractable);
List<Consumer<GuiComponent>>[] listeners = IListableComponent.createList(IListableComponent.MAX_LISTENER_TYPES);
IComponentRenderer<GuiComponent> 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<GuiComponent> listener, int index) {
listeners[index].add(listener);
return this;
}
@Override
public GuiComponent removeListener(Consumer<GuiComponent> listener, int index) {
listeners[index].remove(listener);
return this;
}
@Override
public void notifyListeners(int index) {
if(listeners[index].isEmpty()) return;
for(Consumer<GuiComponent> 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;
}
}
@@ -0,0 +1,33 @@
package speiger.src.coreengine.ui.gui.component.base;
public interface ICastable
{
@SuppressWarnings("unchecked")
public default <T> T cast() {
return (T)this; }
@SuppressWarnings("unchecked")
public default <T> T cast(Class<T> clz) {
return (T)this;
}
@SuppressWarnings("unchecked")
public default <T> T tryCast(Class<T> clz) {
return clz.isInstance(this) ? (T)this : null;
}
@SuppressWarnings("unchecked")
public static <T> T cast(Object obj) {
return (T)obj;
}
@SuppressWarnings("unchecked")
public static <T> T cast(Object obj, Class<T> clz) {
return (T)obj;
}
@SuppressWarnings("unchecked")
public static <T> T tryCast(Object obj, Class<T> clz) {
return clz.isInstance(obj) ? (T)obj : null;
}
}
@@ -0,0 +1,9 @@
package speiger.src.coreengine.ui.gui.component.base;
import speiger.src.coreengine.ui.gui.renderer.IUIRenderer;
public interface IComponentRenderer<T extends GuiComponent> {
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) {}
}
@@ -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);
}
@@ -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;
}
}
}
@@ -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<GuiComponent> listener, int index);
public GuiComponent removeListener(Consumer<GuiComponent> listener, int index);
public void notifyListeners(int index);
public default GuiComponent onAction(Consumer<GuiComponent> 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<GuiComponent> 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<GuiComponent> 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<GuiComponent> listener) {
return removeListener(listener, GuiComponent.LISTENER_USER_ACTION);
}
public default GuiComponent removeChangeListener(Consumer<GuiComponent> listener) {
return removeListener(listener, GuiComponent.LISTENER_ON_CHANGE);
}
public default GuiComponent removeCloseListener(Consumer<GuiComponent> listener) {
return removeListener(listener, GuiComponent.LISTENER_CLOSED);
}
@SuppressWarnings("unchecked")
public static <T> ObjectList<T>[] createList(int size) {
ObjectList<T>[] list = new ObjectList[size];
for(int i = 0;i < size;i++) {
list[i] = new ObjectArrayList<>();
}
return list;
}
}
@@ -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);
}
@@ -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<m;i++) {
IInteractable interact = children.get(i);
if(interact.isMouseColliding(mouseX, mouseY) && IRecursiveInteractionContainer.validateContainer(interact, mouseX, mouseY)) return interact;
}
return null;
}
@Override
default boolean onMouseClick(int button, int mouseX, int mouseY) {
IntSet active = getActiveButtons();
if(active.size() > 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<m;i++) {
IInteractable interact = children.get(i);
if(interact.isMouseColliding(mouseX, mouseY) && interact.onMouseClick(button, mouseX, mouseY)) {
setFocusedChild(interact);
active.add(button);
return true;
}
}
if(active.isEmpty() && getFocusedChild() != null) setFocusedChild(null);
return false;
}
@Override
default boolean onMouseDragged(int mouseX, int mouseY, int diffX, int diffY) {
return getFocusedChild() != null && getActiveButtons().contains(0) && getFocusedChild().onMouseDragged(mouseX, mouseY, diffX, diffY);
}
@Override
default boolean onMouseReleased(int button, int mouseX, int mouseY) {
getActiveButtons().remove(button);
IInteractable interact = getChildAt(mouseX, mouseY);
return interact != null && interact == getFocusedChild() && interact.onMouseReleased(button, mouseX, mouseY);
}
@Override
default boolean onMouseScroll(int scroll, int mouseX, int mouseY) {
IInteractable interact = getChildAt(mouseX, mouseY);
return interact != null && interact.onMouseScroll(scroll, mouseX, mouseY);
}
@Override
default boolean onKeyPressed(int key, int mouseX, int mouseY) {
IInteractable interact = getFocusedChild();
if(interact != null && (interact.onKeyPressed(key, mouseX, mouseY) || interact.isPriorityKeyTarget())) return true;
List<? extends IInteractable> children = getChildren();
for(int i = 0,m=children.size();i<m;i++) {
if(children.get(i).onKeyPressed(key, mouseX, mouseY)) return true;
}
return false;
}
@Override
default boolean onKeyReleased(int key, int mouseX, int mouseY) {
IInteractable interact = getFocusedChild();
if(interact != null && (interact.onKeyReleased(key, mouseX, mouseY) || interact.isPriorityKeyTarget())) return true;
List<? extends IInteractable> children = getChildren();
for(int i = 0,m=children.size();i<m;i++) {
if(children.get(i).onKeyReleased(key, mouseX, mouseY)) return true;
}
return false;
}
@Override
default boolean onKeyTyped(char letter, int codepoint) {
return getFocusedChild() != null && getFocusedChild().onKeyTyped(letter, codepoint);
}
public static interface IRecursiveInteractionContainer extends IInteractableContainer {
private static boolean validateContainer(IInteractable interact, int mouseX, int mouseY) {
return !(interact instanceof IRecursiveInteractionContainer container) || container.getChildAt(mouseX, mouseY) != null;
}
@Override
default boolean isMouseColliding(int mouseX, int mouseY) {
return true;
}
}
}
@@ -0,0 +1,73 @@
package speiger.src.coreengine.ui.gui.interaction;
import java.util.List;
import java.util.function.BooleanSupplier;
import speiger.src.collections.ints.sets.IntOpenHashSet;
import speiger.src.collections.ints.sets.IntSet;
import speiger.src.collections.objects.lists.ObjectArrayList;
import speiger.src.collections.objects.utils.ObjectLists;
import speiger.src.coreengine.ui.gui.interaction.IInteractableContainer.IRecursiveInteractionContainer;
public class InteractionContainer implements IRecursiveInteractionContainer {
List<IInteractable> 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;
}
}
@@ -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<IGuiBox> 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<m;i++) {
children.get(i).setParent(null);
}
children.clear();
return this;
}
@Override
public List<IGuiBox> 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<m;i++) {
children.get(i).onChanged();
}
return this;
}
@Override
public IGuiBox setParent(IGuiBox box) {
parent = box;
return this;
}
@Override
public IGuiBox getParent() { return parent; }
@Override
public float getScale() { return scale; }
@Override
public float getBaseX() { return baseX; }
@Override
public float getBaseY() { return baseY; }
@Override
public float getRelativeX() { return parent != null ? baseX : 0F; }
@Override
public float getRelativeY() { return parent != null ? baseY : 0F; }
@Override
public float getWidth() { return baseWidth * scale; }
@Override
public float getWidth(float extra) { return baseWidth * scale + extra * baseScale; }
@Override
public float getHeight() { return baseHeight * scale; }
@Override
public float getHeight(float extra) { return baseHeight * scale + extra * baseScale; }
@Override
public float getMinX() { return minX; }
@Override
public float getMinX(float extra) { return minX + extra * scale; }
@Override
public float getMinY() { return minY; }
@Override
public float getMinY(float extra) { return minY + extra * scale; }
@Override
public float getMaxX() { return maxX; }
@Override
public float getMaxX(float extra) { return maxX + extra * scale; }
@Override
public float getMaxY() { return maxY; }
@Override
public float getMaxY(float extra) { return maxY + extra * scale; }
@Override
public float getCenterX() { return minX + (maxX - minX) * 0.5F; }
@Override
public float getCenterX(float extra) { return minX + (maxX - minX) * 0.5F + extra * scale; }
@Override
public float getCenterY() { return minY + (maxY - minY) * 0.5F; }
@Override
public float getCenterY(float extra) { return minY + (maxY - minY) * 0.5F + extra * scale; }
@Override
public IGuiBox setX(float x) {
baseX = x;
return this;
}
@Override
public IGuiBox setY(float y) {
baseY = y;
return this;
}
@Override
public IGuiBox setWidth(float width) {
baseWidth = width;
this.width = baseWidth * baseScale;
return this;
}
@Override
public IGuiBox setHeight(float height) {
baseHeight = height;
this.height = baseHeight * baseScale;
return this;
}
@Override
public IGuiBox setScale(float scale) {
baseScale = scale;
width = baseWidth * baseScale;
height = baseHeight * baseScale;
return this;
}
@Override
public IGuiBox move(float xOffset, float yOffset) { return setXY(baseX + xOffset, baseY + yOffset); }
@Override
public IGuiBox resize(float xGrowth, float yGrowth) { return setBounds(baseWidth + xGrowth, baseHeight + yGrowth); }
@Override
public IGuiBox scale(float scale) { return setScale(scale * baseScale); }
@Override
public float getBaseScale() { return baseScale; }
@Override
public float getBaseWidth() { return baseWidth; }
@Override
public float getBaseHeight() { return baseHeight; }
@Override
public String toString() { return "Box[sx="+minX+", sy="+minY+", ex="+maxX+", ey="+maxY+", w="+width+", h="+height+"]"; }
}
@@ -0,0 +1,92 @@
package speiger.src.coreengine.ui.gui.layout.box;
import java.util.List;
import speiger.src.coreengine.math.direction.Direction;
import speiger.src.coreengine.math.direction.DirectionList;
public interface IGuiBox extends IScreenBox
{
public IGuiBox addChild(IGuiBox box);
public IGuiBox removeChild(IGuiBox box);
public IGuiBox clearChildren();
public List<IGuiBox> 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));
}
}
@@ -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;
}
}
@@ -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<IGuiBox> 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<m;i++) {
children.get(i).setParent(null);
}
children.clear();
return this;
}
@Override
public List<IGuiBox> children() {
return children.unmodifiable();
}
@Override
public IGuiBox onChanged() {
for(int i = 0,m=children.size();i<m;i++) {
children.get(i).onChanged();
}
return this;
}
@Override
public IGuiBox setParent(IGuiBox box) {
parent = box;
return this;
}
@Override
public IGuiBox getParent() { return parent; }
@Override
public float getScale() { return parent.getScale(); }
@Override
public float getBaseX() { return minX + parent.getBaseX(); }
@Override
public float getBaseY() { return minY + parent.getBaseY(); }
@Override
public float getRelativeX() { return minX + parent.getRelativeX(); }
@Override
public float getRelativeY() { return minY + parent.getRelativeY(); }
@Override
public float getWidth() { return parent.getWidth(maxX - minX); }
@Override
public float getWidth(float extra) { return parent.getWidth((maxX - minX) + extra); }
@Override
public float getHeight() { return parent.getHeight(maxY - minY); }
@Override
public float getHeight(float extra) { return parent.getHeight((maxY - minY) + extra); }
@Override
public float getMinX() { return parent.getMinX(minX); }
@Override
public float getMinX(float extra) { return parent.getMinX(minX + extra); }
@Override
public float getMinY() { return parent.getMinY(minY); }
@Override
public float getMinY(float extra) { return parent.getMinY(minY + extra); }
@Override
public float getMaxX() { return parent.getMaxX(maxX); }
@Override
public float getMaxX(float extra) { return parent.getMaxX(maxX + extra); }
@Override
public float getMaxY() { return parent.getMaxY(maxY); }
@Override
public float getMaxY(float extra) { return parent.getMaxY(maxY + extra); }
@Override
public float getCenterX() { return parent.getCenterX((maxX - minX) * 0.5F); }
@Override
public float getCenterX(float extra) { return parent.getCenterX((maxX - minX) * 0.5F + extra); }
@Override
public float getCenterY() { return parent.getCenterY((maxY - minY) * 0.5F); }
@Override
public float getCenterY(float extra) { return parent.getCenterY((maxY - minY) * 0.5F + extra); }
@Override
public IGuiBox setX(float x) { throw new UnsupportedOperationException(); }
@Override
public IGuiBox setY(float y) { throw new UnsupportedOperationException(); }
@Override
public IGuiBox setWidth(float width) { throw new UnsupportedOperationException(); }
@Override
public IGuiBox setHeight(float height) { throw new UnsupportedOperationException(); }
@Override
public IGuiBox setScale(float scale) { throw new UnsupportedOperationException(); }
@Override
public IGuiBox move(float xOffset, float yOffset) { throw new UnsupportedOperationException(); }
@Override
public IGuiBox resize(float xGrowth, float yGrowth) { throw new UnsupportedOperationException(); }
@Override
public IGuiBox scale(float scale) { throw new UnsupportedOperationException(); }
@Override
public float getBaseScale() { return 1F; }
@Override
public float getBaseWidth() { return parent.getBaseWidth() + (maxX - minX); }
@Override
public float getBaseHeight() { return parent.getBaseHeight() + (maxY - minY); }
}
@@ -0,0 +1,90 @@
package speiger.src.coreengine.ui.gui.layout.constraint;
import java.util.List;
import speiger.src.coreengine.ui.gui.component.base.GuiComponent;
import speiger.src.coreengine.ui.gui.layout.box.IGuiBox;
public class ConstrainedContext {
float[] bounds;
float[] weights;
boolean locked = false;
int current;
public void update(GuiComponent owner, List<GuiComponent> children) {
locked = false;
bounds = new float[children.size()*2];
weights = new float[children.size()*2];
for(int i = 0,m=children.size();i<m;i++) {
current = i;
GuiComponent component = children.get(i);
if(component.constraint() == null) {
applyDefault(component);
continue;
}
component.constraint().fetch(component, owner, this);
}
locked = true;
float[] totals = new float[4];
for(int i = 0,m=children.size();i<m;i++) {
totals[0] += bounds[i*2];
totals[1] += bounds[i*2+1];
totals[2] += weights[i*2];
totals[3] += weights[i*2+1];
}
IGuiBox box = owner.getBox();
totals[0] = box.getBaseWidth() - totals[0];
totals[1] = box.getBaseHeight() - totals[1];
if(totals[2] > 0F) {
float scale = 1F / totals[2];
for(int i = 0,m=children.size();i<m;i++) {
float value = weights[i*2];
if(value <= 0F) continue;
bounds[i*2] += value * scale;
}
}
if(totals[3] > 0F) {
float scale = 1F / totals[3];
for(int i = 0,m=children.size();i<m;i++) {
float value = weights[i*2+1];
if(value <= 0) continue;
bounds[i*2+1] += value * scale;
}
}
}
public void setCurrent(int index) {
if(!locked) throw new IllegalStateException("Cursor can only be moved when its in read mode!");
current = index;
}
public float getOffset(boolean width) {
if(!locked) throw new IllegalStateException("You can't read during write!");
float total = 0F;
for(int i = 0;i<current;i++) {
total += bounds[i*2+(width ? 0 : 1)];
}
return total;
}
public float get(boolean width) {
if(!locked) throw new IllegalStateException("You can't read during write!");
return bounds[current*2+(width ? 0 : 1)];
}
private void applyDefault(GuiComponent component) {
addBound(component.getWidth(), true);
addBound(component.getHeight(), true);
}
public void addWeight(float weight, boolean width) {
if(locked) return;
weights[current*2+(width ? 0 : 1)] = weight;
}
public void addBound(float bound, boolean width) {
if(locked) return;
bounds[current*2+(width ? 0 : 1)] = bound;
}
}
@@ -0,0 +1,67 @@
package speiger.src.coreengine.ui.gui.layout.constraint;
import speiger.src.coreengine.ui.gui.component.base.GuiComponent;
import speiger.src.coreengine.ui.gui.layout.constraint.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<CONSTRAINT_LENGTH;i++) {
Target target = Target.by(i);
if(constraints[i] == null) {
context.addBound(target.get(owner), target.isXAxis());
continue;
}
constraints[i].fetch(owner, parent, target, context);
}
}
public static Builder builder() { return new Builder(); }
public Builder copy() { return new Builder(this); }
public static class Builder {
ConstraintContainer container = new ConstraintContainer();
private Builder() {}
private Builder(ConstraintContainer container) {
System.arraycopy(container.constraints, 0, this.container.constraints, 0, CONSTRAINT_LENGTH);
}
public Builder x(IConstraint value) {
container.constraints[Target.X.ordinal()] = value;
return this;
}
public Builder y(IConstraint value) {
container.constraints[Target.Y.ordinal()] = value;
return this;
}
public Builder width(IConstraint value) {
container.constraints[Target.WIDTH.ordinal()] = value;
return this;
}
public Builder height(IConstraint value) {
container.constraints[Target.HEIGHT.ordinal()] = value;
return this;
}
public ConstraintContainer build() {
var result = container;
container = null;
return result;
}
}
}
@@ -0,0 +1,151 @@
package speiger.src.coreengine.ui.gui.layout.constraint;
import java.util.List;
import java.util.function.BooleanSupplier;
import speiger.src.coreengine.ui.gui.component.base.GuiComponent;
import speiger.src.coreengine.ui.gui.layout.box.IGuiBox;
import speiger.src.coreengine.ui.gui.layout.constraint.IConstraint.ISimpleConstraint;
public class Constraints {
public static record Pixels(float value, boolean inverted) implements ISimpleConstraint {
public static Pixels of(float value) { return new Pixels(value, false); }
public static Pixels inverted(float value) { return new Pixels(value, true); }
@Override
public void apply(IGuiBox owner, IGuiBox parent, Target target, ConstrainedContext context) {
target.set(owner, inverted ? target.asArea().get(parent) - value : value);
}
@Override
public void fetch(IGuiBox owner, IGuiBox parent, Target target, ConstrainedContext context) {
context.addBound(inverted ? target.asArea().get(parent) - value : value, target.isXAxis());
}
}
public static record Parent(float padding, boolean inv) implements ISimpleConstraint {
public static Parent of() { return new Parent(0F, false); }
public static Parent of(float padding) { return new Parent(padding, false); }
public static Parent inverted() { return new Parent(0F, true); }
public static Parent inverted(float padding) { return new Parent(padding, true); }
@Override
public void apply(IGuiBox owner, IGuiBox parent, Target target, ConstrainedContext context) {
if(inv) target.set(owner, target.isPosition() ? target.asArea().get(parent) - padding : padding * 2);
else target.set(owner, target.isPosition() ? padding : target.get(parent) - padding * 2);
}
@Override
public void fetch(IGuiBox owner, IGuiBox parent, Target target, ConstrainedContext context) {
if(inv) context.addBound(target.isPosition() ? target.asArea().get(parent) - padding : padding * 2, target.isXAxis());
else context.addBound(target.isPosition() ? padding : target.get(parent) - padding * 2, target.isXAxis());
}
}
public static record Children(float padding, boolean includeChildOffsets) implements ISimpleConstraint {
public static Children onlyBounds() { return new Children(0F, false); }
public static Children onlyBounds(float padding) { return new Children(padding, false); }
public static Children withPos() { return new Children(0F, true); }
public static Children withPos(float padding) { return new Children(padding, true); }
@Override
public void apply(IGuiBox owner, IGuiBox parent, Target target, ConstrainedContext context) {
if(target.isPosition()) return;
float value = 0F;
List<IGuiBox> children = owner.children();
for(int i = 0,m=children.size();i<m;i++) {
IGuiBox child = children.get(i);
value = Math.max(value, child.getBaseWidth() + (includeChildOffsets() ? child.getBaseX() : 0));
}
target.set(owner, value);
}
@Override
public void fetch(IGuiBox owner, IGuiBox parent, Target target, ConstrainedContext context) {
float value = 0F;
List<IGuiBox> children = owner.children();
for(int i = 0,m=children.size();i<m;i++) {
IGuiBox child = children.get(i);
value = Math.max(value, child.getBaseWidth() + (includeChildOffsets() ? child.getBaseX() : 0));
}
context.addBound(value, target.isXAxis());
}
}
public static record Relative(float value, float padding) implements ISimpleConstraint {
public static Relative of(float value) { return new Relative(value, 0F); }
public static Relative of(float value, float padding) { return new Relative(value, padding); }
@Override
public void apply(IGuiBox owner, IGuiBox parent, Target target, ConstrainedContext context) {
float result = value * target.asArea().get(parent);
target.set(owner, target.isPosition() ? result + padding : result - padding * 2F);
}
@Override
public void fetch(IGuiBox owner, IGuiBox parent, Target target, ConstrainedContext context) {
context.addBound(value * target.asArea().get(parent), target.isXAxis());
}
}
public static record Weighted(float weight, float minimum, float padding, boolean inverted) implements ISimpleConstraint {
public static Weighted of(float weight) { return new Weighted(weight, 0F, 0F, false); }
public static Weighted padded(float weight, float padding) { return new Weighted(weight, 0F, padding, false); }
public static Weighted minimum(float weight, float minimized) { return new Weighted(weight, minimized, 0F, false); }
public static Weighted of(float weight, float minimized, float padding) { return new Weighted(weight, minimized, padding, false); }
public static Weighted inverted(float weight) { return new Weighted(weight, 0F, 0F, true);}
public static Weighted invertedPadded(float weight, float padding) { return new Weighted(weight, 0F, padding, true); }
public static Weighted invertedMinimum(float weight, float minimized) { return new Weighted(weight, minimized, 0F, true); }
public static Weighted inverted(float weight, float minimized, float padding) { return new Weighted(weight, minimized, padding, true); }
@Override
public void apply(IGuiBox owner, IGuiBox parent, Target target, ConstrainedContext context) {
float value = target.get(context);
if(inverted) target.set(owner, target.isPosition() ? target.asArea().get(parent) - value - padding : value - padding * 2);
else target.set(owner, target.isPosition() ? value + padding : value - padding * 2);
}
@Override
public void fetch(IGuiBox owner, IGuiBox parent, Target target, ConstrainedContext context) {
if(target.isPosition()) return;
context.addWeight(weight, target.isXAxis());
context.addBound(minimum, target.isXAxis());
}
}
public static record Center() implements ISimpleConstraint {
private static final Center INSTANCE = new Center();
public static Center of() { return INSTANCE; }
@Override
public void apply(IGuiBox owner, IGuiBox parent, Target target, ConstrainedContext context) {
Target bounds = target.asArea();
target.set(owner, (bounds.get(parent) * 0.5F) - (bounds.get(owner) * 0.5F));
}
@Override
public void fetch(IGuiBox owner, IGuiBox parent, Target target, ConstrainedContext context) {
context.addBound(target.get(owner), target.isXAxis());
}
}
public static record Conditional(BooleanSupplier supplier, IConstraint onTrue, IConstraint onFalse) implements IConstraint {
public static Conditional of(BooleanSupplier supplier, IConstraint onTrue, IConstraint onFalse) { return new Conditional(supplier, onTrue, onFalse); }
@Override
public void apply(GuiComponent owner, GuiComponent parent, Target target, ConstrainedContext context) {
(supplier.getAsBoolean() ? onTrue : onFalse).apply(owner, parent, target, context);
}
@Override
public void fetch(GuiComponent owner, GuiComponent parent, Target target, ConstrainedContext context) {
(supplier.getAsBoolean() ? onTrue : onFalse).fetch(owner, parent, target, context);
}
}
}
@@ -0,0 +1,103 @@
package speiger.src.coreengine.ui.gui.layout.constraint;
import java.util.function.BooleanSupplier;
import speiger.src.coreengine.ui.gui.component.base.GuiComponent;
import speiger.src.coreengine.ui.gui.layout.box.IGuiBox;
import speiger.src.coreengine.ui.gui.layout.constraint.Constraints.Center;
import speiger.src.coreengine.ui.gui.layout.constraint.Constraints.Children;
import speiger.src.coreengine.ui.gui.layout.constraint.Constraints.Conditional;
import speiger.src.coreengine.ui.gui.layout.constraint.Constraints.Parent;
import speiger.src.coreengine.ui.gui.layout.constraint.Constraints.Pixels;
import speiger.src.coreengine.ui.gui.layout.constraint.Constraints.Relative;
import speiger.src.coreengine.ui.gui.layout.constraint.Constraints.Weighted;
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);
}
}
}
}
@@ -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<T> extends Consumer<GuiComponent> {
public default ILayout<T> add(GuiComponent comp) { return add(comp, null); }
public ILayout<T> add(GuiComponent comp, T value);
public ILayout<T> remove(GuiComponent comp);
public void apply(GuiComponent owner);
@Override
default void accept(GuiComponent t) { apply(t); }
}
@@ -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);
}
@@ -10,6 +10,7 @@ import org.lwjgl.util.freetype.FreeType;
import speiger.src.coreengine.assets.api.IAssetPackage; import speiger.src.coreengine.assets.api.IAssetPackage;
import speiger.src.coreengine.assets.api.ID; import speiger.src.coreengine.assets.api.ID;
import speiger.src.coreengine.assets.manager.AssetManager; 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.math.vector.matrix.Matrix4f;
import speiger.src.coreengine.platform.graphics.api.buffer.VertexBuffer; import speiger.src.coreengine.platform.graphics.api.buffer.VertexBuffer;
import speiger.src.coreengine.platform.graphics.api.buffer.states.BufferState; 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.device.Mouse;
import speiger.src.coreengine.platform.input.window.Window; import speiger.src.coreengine.platform.input.window.Window;
import speiger.src.coreengine.platform.input.window.WindowManager; import speiger.src.coreengine.platform.input.window.WindowManager;
import speiger.src.coreengine.utils.eventbus.EventBus;
import speiger.src.coreengine.utils.helpers.IOUtils; import speiger.src.coreengine.utils.helpers.IOUtils;
public class NewRenderEngineTest { public class NewRenderEngineTest {
EventBus bus = new EventBus(); EventBus bus = EventBus.builder().build();
WindowManager manager = new WindowManager(); WindowManager manager = new WindowManager();
AssetManager assets = AssetManager.single(IAssetPackage.asset(IOUtils.getBaseLocation())); AssetManager assets = AssetManager.single(IAssetPackage.asset(IOUtils.getBaseLocation()));
@@ -1,7 +0,0 @@
package speiger.src.coreengine.rendering.gui.font;
public class FontCache {
float size;
}
@@ -1,67 +1,67 @@
package speiger.src.coreengine.rendering.gui.layout.constraints; package speiger.src.coreengine.rendering.gui.layout.constraints;
import speiger.src.coreengine.rendering.gui.components.base.GuiComponent; import speiger.src.coreengine.rendering.gui.components.base.GuiComponent;
import speiger.src.coreengine.rendering.gui.layout.constraints.IConstraint.Target; import speiger.src.coreengine.rendering.gui.layout.constraints.IConstraint.Target;
public class ConstraintContainer { public class ConstraintContainer {
private static final int CONSTRAINT_LENGTH = 4; private static final int CONSTRAINT_LENGTH = 4;
IConstraint[] constraints = new IConstraint[CONSTRAINT_LENGTH]; IConstraint[] constraints = new IConstraint[CONSTRAINT_LENGTH];
private ConstraintContainer() {} private ConstraintContainer() {}
public void apply(GuiComponent owner, GuiComponent parent, ConstrainedContext context) { public void apply(GuiComponent owner, GuiComponent parent, ConstrainedContext context) {
for(int i = 0;i<4;i++) { for(int i = 0;i<4;i++) {
if(constraints[i] == null) continue; if(constraints[i] == null) continue;
constraints[i].apply(owner, parent, Target.by(i), context); constraints[i].apply(owner, parent, Target.by(i), context);
} }
} }
public void fetch(GuiComponent owner, GuiComponent parent, ConstrainedContext context) { public void fetch(GuiComponent owner, GuiComponent parent, ConstrainedContext context) {
for(int i = 2;i<CONSTRAINT_LENGTH;i++) { for(int i = 2;i<CONSTRAINT_LENGTH;i++) {
Target target = Target.by(i); Target target = Target.by(i);
if(constraints[i] == null) { if(constraints[i] == null) {
context.addBound(target.get(owner), target.isXAxis()); context.addBound(target.get(owner), target.isXAxis());
continue; continue;
} }
constraints[i].fetch(owner, parent, target, context); constraints[i].fetch(owner, parent, target, context);
} }
} }
public static Builder builder() { return new Builder(); } public static Builder builder() { return new Builder(); }
public Builder copy() { return new Builder(this); } public Builder copy() { return new Builder(this); }
public static class Builder { public static class Builder {
ConstraintContainer container = new ConstraintContainer(); ConstraintContainer container = new ConstraintContainer();
private Builder() {} private Builder() {}
private Builder(ConstraintContainer container) { private Builder(ConstraintContainer container) {
System.arraycopy(container.constraints, 0, this.container.constraints, 0, CONSTRAINT_LENGTH); System.arraycopy(container.constraints, 0, this.container.constraints, 0, CONSTRAINT_LENGTH);
} }
public Builder x(IConstraint value) { public Builder x(IConstraint value) {
container.constraints[Target.X.ordinal()] = value; container.constraints[Target.X.ordinal()] = value;
return this; return this;
} }
public Builder y(IConstraint value) { public Builder y(IConstraint value) {
container.constraints[Target.Y.ordinal()] = value; container.constraints[Target.Y.ordinal()] = value;
return this; return this;
} }
public Builder width(IConstraint value) { public Builder width(IConstraint value) {
container.constraints[Target.WIDTH.ordinal()] = value; container.constraints[Target.WIDTH.ordinal()] = value;
return this; return this;
} }
public Builder height(IConstraint value) { public Builder height(IConstraint value) {
container.constraints[Target.HEIGHT.ordinal()] = value; container.constraints[Target.HEIGHT.ordinal()] = value;
return this; return this;
} }
public ConstraintContainer build() { public ConstraintContainer build() {
var result = container; var result = container;
container = null; container = null;
return result; return result;
} }
} }
} }
@@ -1,102 +1,102 @@
package speiger.src.coreengine.rendering.gui.layout.constraints; package speiger.src.coreengine.rendering.gui.layout.constraints;
import java.util.function.BooleanSupplier; import java.util.function.BooleanSupplier;
import speiger.src.coreengine.rendering.gui.components.base.GuiComponent; 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.Center;
import speiger.src.coreengine.rendering.gui.layout.constraints.Constraints.Children; 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.Conditional;
import speiger.src.coreengine.rendering.gui.layout.constraints.Constraints.Parent; 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.Pixels;
import speiger.src.coreengine.rendering.gui.layout.constraints.Constraints.Relative; 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.gui.layout.constraints.Constraints.Weighted;
import speiger.src.coreengine.rendering.guiOld.helper.box.IGuiBox; import speiger.src.coreengine.rendering.guiOld.helper.box.IGuiBox;
public interface IConstraint { public interface IConstraint {
public void apply(GuiComponent owner, GuiComponent parent, Target target, ConstrainedContext context); public void apply(GuiComponent owner, GuiComponent parent, Target target, ConstrainedContext context);
public void fetch(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 pixels(float value) { return Pixels.of(value); }
public static IConstraint pixelsInv(float value) { return Pixels.inverted(value); } public static IConstraint pixelsInv(float value) { return Pixels.inverted(value); }
public static IConstraint parent() { return Parent.of(); } public static IConstraint parent() { return Parent.of(); }
public static IConstraint parent(float padding) { return Parent.of(padding); } public static IConstraint parent(float padding) { return Parent.of(padding); }
public static IConstraint parentInv() { return Parent.inverted(); } public static IConstraint parentInv() { return Parent.inverted(); }
public static IConstraint parentInv(float padding) { return Parent.inverted(padding); } public static IConstraint parentInv(float padding) { return Parent.inverted(padding); }
public static IConstraint children() { return Children.onlyBounds(); } public static IConstraint children() { return Children.onlyBounds(); }
public static IConstraint children(float padding) { return Children.onlyBounds(padding); } public static IConstraint children(float padding) { return Children.onlyBounds(padding); }
public static IConstraint childrenPos() { return Children.withPos(); } public static IConstraint childrenPos() { return Children.withPos(); }
public static IConstraint childrenPos(float padding) { return Children.withPos(padding); } 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) { return Relative.of(value); }
public static IConstraint relative(float value, float padding) { return Relative.of(value, padding); } public static IConstraint relative(float value, float padding) { return Relative.of(value, padding); }
public static IConstraint weightedPos() { return Weighted.of(0F); } public static IConstraint weightedPos() { return Weighted.of(0F); }
public static IConstraint weightedInvPos() { return Weighted.inverted(0F); } public static IConstraint weightedInvPos() { return Weighted.inverted(0F); }
public static IConstraint weighted(float weight) { return Weighted.of(weight); } 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 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 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 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 weightedInv(float weight) { return Weighted.inverted(weight);}
public static IConstraint weightedInvPad(float weight, float padding) { return Weighted.invertedPadded(weight, padding); } 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 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 weightedInv(float weight, float minimum, float padding) { return Weighted.inverted(weight, minimum, padding); }
public static IConstraint center() { return Center.of(); } 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 IConstraint conditional(BooleanSupplier supplier, IConstraint onTrue, IConstraint onFalse) { return Conditional.of(supplier, onTrue, onFalse); }
public static interface ISimpleConstraint extends IConstraint { public static interface ISimpleConstraint extends IConstraint {
@Override @Override
default void apply(GuiComponent owner, GuiComponent parent, Target target, ConstrainedContext context) { apply(owner.getBox(), parent == null ? owner.screen().getBox() : parent.getBox(), target, context); } 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); public void apply(IGuiBox owner, IGuiBox parent, Target target, ConstrainedContext context);
@Override @Override
default void fetch(GuiComponent owner, GuiComponent parent, Target target, ConstrainedContext context) { fetch(owner.getBox(), parent == null ? owner.screen().getBox() : parent.getBox(), target, context); } 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 void fetch(IGuiBox owner, IGuiBox parent, Target target, ConstrainedContext context);
} }
public static enum Target { public static enum Target {
X, X,
Y, Y,
WIDTH, WIDTH,
HEIGHT; HEIGHT;
static final Target[] BY_INDEX = values(); static final Target[] BY_INDEX = values();
public static Target by(int index) { return BY_INDEX[index & 3]; } public static Target by(int index) { return BY_INDEX[index & 3]; }
public static Target pos(boolean x) { return x ? X : Y; } public static Target pos(boolean x) { return x ? X : Y; }
public static Target bounds(boolean x) { return x ? WIDTH : HEIGHT; } public static Target bounds(boolean x) { return x ? WIDTH : HEIGHT; }
public boolean isPosition() { return this == X || this == Y; } public boolean isPosition() { return this == X || this == Y; }
public boolean isXAxis() { return this == X || this == WIDTH; } public boolean isXAxis() { return this == X || this == WIDTH; }
public Target asArea() { public Target asArea() {
return switch(this) { return switch(this) {
case X -> WIDTH; case X -> WIDTH;
case Y -> HEIGHT; case Y -> HEIGHT;
default -> this; default -> this;
}; };
} }
public float get(GuiComponent comp) { return get(comp.getBox()); } public float get(GuiComponent comp) { return get(comp.getBox()); }
public void set(GuiComponent comp, float value) { set(comp.getBox(), value); } 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(ConstrainedContext context) { return (isPosition() ? context.getOffset(isXAxis()) : context.get(isXAxis())); }
public float get(IGuiBox box) { public float get(IGuiBox box) {
return switch(this) { return switch(this) {
case X -> box.getBaseX(); case X -> box.getBaseX();
case Y -> box.getBaseY(); case Y -> box.getBaseY();
case WIDTH -> box.getBaseWidth(); case WIDTH -> box.getBaseWidth();
case HEIGHT -> box.getBaseHeight(); case HEIGHT -> box.getBaseHeight();
}; };
} }
public void set(IGuiBox box, float value) { public void set(IGuiBox box, float value) {
switch(this) { switch(this) {
case X -> box.setX(value); case X -> box.setX(value);
case Y -> box.setY(value); case Y -> box.setY(value);
case WIDTH -> box.setWidth(value); case WIDTH -> box.setWidth(value);
case HEIGHT -> box.setHeight(value); case HEIGHT -> box.setHeight(value);
} }
} }
} }
} }
@@ -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<Direction> {
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; }
}
}
@@ -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<Direction>, Predicate<Direction> {
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<Direction> 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<Direction> 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<Direction> iterator() {
return new Iterator<Direction>() {
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;
}
}