More work on the rewrite
This commit is contained in:
+20
-8
@@ -12,22 +12,34 @@
|
||||
<attribute name="gradle_used_by_scope" value="main,test"/>
|
||||
</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="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">
|
||||
<attributes>
|
||||
<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>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" output="bin/math" path="src/math/java">
|
||||
<attributes>
|
||||
<attribute name="gradle_scope" value="math"/>
|
||||
<attribute name="gradle_used_by_scope" value="main,graphics,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"/>
|
||||
<attribute name="gradle_used_by_scope" value="main,test,graphics,gui,math"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-25/"/>
|
||||
|
||||
@@ -2,22 +2,21 @@
|
||||
<projectDescription>
|
||||
<name>SimpleJavaEngine</name>
|
||||
<comment></comment>
|
||||
<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>
|
||||
<projects/>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
|
||||
</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>
|
||||
|
||||
+3
-11
@@ -49,7 +49,7 @@ repositories {
|
||||
name = "Speiger Maven"
|
||||
url = "https://maven.speiger.com/repository/main"
|
||||
}
|
||||
maven { url "https://central.sonatype.com/repository/maven-snapshots" }
|
||||
maven { url = "https://central.sonatype.com/repository/maven-snapshots" }
|
||||
}
|
||||
|
||||
|
||||
@@ -93,15 +93,12 @@ dependencies {
|
||||
}
|
||||
|
||||
jar {
|
||||
manifest {
|
||||
attributes "Main-Class": 'speiger.src.coreengine.NewRenderEngineTest'
|
||||
}
|
||||
sourceSets.each { ss ->
|
||||
from ss.output
|
||||
}
|
||||
from {
|
||||
from {
|
||||
configurations.runtimeClasspath.collect {
|
||||
it.isDirectory() ? it : zipTree(it)
|
||||
!it.exists() ? null : (it.isDirectory() ? it : zipTree(it))
|
||||
}
|
||||
}
|
||||
duplicatesStrategy = DuplicatesStrategy.INCLUDE
|
||||
@@ -113,11 +110,6 @@ task srcJar(type: Jar) {
|
||||
sourceSets.each { ss ->
|
||||
from ss.allSource
|
||||
}
|
||||
from {
|
||||
configurations.runtimeClasspath.collect {
|
||||
it.isDirectory() ? it : zipTree(it)
|
||||
}
|
||||
}
|
||||
duplicatesStrategy = DuplicatesStrategy.INCLUDE
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package speiger.src.coreengine.core.events.api;
|
||||
|
||||
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
|
||||
{
|
||||
HIGH(0),
|
||||
MEDIUM(1),
|
||||
LOW(2);
|
||||
|
||||
int priority;
|
||||
|
||||
static final EventPriority[] PRIORITIES = new EventPriority[] {HIGH, MEDIUM, LOW};
|
||||
|
||||
private EventPriority(int id) {
|
||||
priority = id;
|
||||
}
|
||||
|
||||
public int getPriority() {
|
||||
return priority;
|
||||
}
|
||||
|
||||
public static EventPriority[] getPriorities() {
|
||||
return PRIORITIES;
|
||||
}
|
||||
HIGH,
|
||||
MEDIUM,
|
||||
LOW;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package speiger.src.coreengine.core.events.api;
|
||||
|
||||
public interface ICancelableEvent {
|
||||
void setCanceled(boolean value);
|
||||
boolean isCanceled();
|
||||
Event self();
|
||||
default boolean isCancelable() { return true; }
|
||||
default void setCanceled(boolean value) { self().canceled = value; }
|
||||
default boolean isCanceled() { return self().canceled; }
|
||||
}
|
||||
|
||||
@@ -5,14 +5,15 @@ import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import speiger.src.coreengine.core.events.api.Event;
|
||||
import speiger.src.coreengine.core.events.bus.EventBus.ClassFilter;
|
||||
import speiger.src.coreengine.core.events.utility.IEventDispatcher;
|
||||
import speiger.src.coreengine.core.events.utility.IEventExceptionHandler;
|
||||
|
||||
public class BusBuilder {
|
||||
IEventDispatcher dispatcher = IEventDispatcher.DIRECT;
|
||||
Optional<IEventExceptionHandler> exceptions = Optional.empty();
|
||||
Predicate<Class<? extends Event>> filter = _ -> true;
|
||||
boolean checkType = false;
|
||||
ClassFilter typeCheck = new ClassFilter(false, _ -> true, "");
|
||||
boolean running = true;
|
||||
|
||||
public BusBuilder dispatcher(IEventDispatcher dispatcher) {
|
||||
this.dispatcher = Objects.requireNonNull(dispatcher, "A Dispatcher is required");
|
||||
@@ -24,9 +25,13 @@ public class BusBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
public BusBuilder filter(Predicate<Class<? extends Event>> filter) {
|
||||
this.filter = Objects.requireNonNull(filter, "A Filter is required");
|
||||
checkType = true;
|
||||
public BusBuilder filter(Predicate<Class<? extends Event>> filter, String filterMessage) {
|
||||
this.typeCheck = new ClassFilter(true, Objects.requireNonNull(filter, "A Filter is required"), Objects.requireNonNull(filterMessage, "A Filter Message is required"));
|
||||
return this;
|
||||
}
|
||||
|
||||
public BusBuilder beginShutdown() {
|
||||
running = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,8 +28,7 @@ public class EventBus implements IEventExceptionHandler {
|
||||
private static final Lookup LOOKUP = MethodHandles.lookup();
|
||||
IEventDispatcher dispatcher;
|
||||
IEventExceptionHandler exceptions;
|
||||
Predicate<Class<? extends Event>> filter;
|
||||
boolean checkType;
|
||||
ClassFilter typeCheck;
|
||||
boolean shutdown = false;
|
||||
|
||||
Map<Class<? extends Event>, Listeners> listeners = new Object2ObjectConcurrentOpenHashMap<>();
|
||||
@@ -37,8 +36,8 @@ public class EventBus implements IEventExceptionHandler {
|
||||
EventBus(BusBuilder builder) {
|
||||
dispatcher = builder.dispatcher;
|
||||
exceptions = builder.exceptions.orElse(this);
|
||||
checkType = builder.checkType;
|
||||
filter = builder.filter;
|
||||
typeCheck = builder.typeCheck;
|
||||
shutdown = !builder.running;
|
||||
}
|
||||
|
||||
public static BusBuilder builder() {
|
||||
@@ -49,8 +48,16 @@ public class EventBus implements IEventExceptionHandler {
|
||||
return new Subscriptions(this);
|
||||
}
|
||||
|
||||
public void start() {
|
||||
shutdown = false;
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
shutdown = true;
|
||||
}
|
||||
|
||||
public <T extends Event> T post(T event) {
|
||||
if(shutdown) throw new IllegalStateException("Bus is shutdown");
|
||||
if(shutdown) return event;
|
||||
validateType(event.getClass());
|
||||
Listeners listener = listeners.get(event.getClass());
|
||||
if(listener == null) return event;
|
||||
@@ -59,12 +66,12 @@ public class EventBus implements IEventExceptionHandler {
|
||||
}
|
||||
|
||||
public <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));
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
if(data == null || !(obj instanceof Consumer)) return;
|
||||
validateType(data.value());
|
||||
@@ -173,7 +186,7 @@ public class EventBus implements IEventExceptionHandler {
|
||||
}
|
||||
|
||||
private void validateType(Class<? extends Event> type) {
|
||||
if(checkType && !filter.test(type)) throw new IllegalStateException("Event ["+type.getSimpleName()+"] is not allowed with this EventBus");
|
||||
if(typeCheck.isInvalid(type)) throw new IllegalStateException(String.format(typeCheck.message(), type.getSimpleName()));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
package speiger.src.coreengine.core.events.utility;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import speiger.src.collections.objects.lists.ObjectArrayList;
|
||||
import speiger.src.collections.objects.sets.ObjectLinkedOpenHashSet;
|
||||
import speiger.src.coreengine.core.events.api.Event;
|
||||
import speiger.src.coreengine.core.events.api.EventPriority;
|
||||
|
||||
public class Listeners
|
||||
{
|
||||
@SuppressWarnings("unchecked")
|
||||
Set<Consumer<Event>>[] unsortedListeners = new Set[EventPriority.getPriorities().length];
|
||||
Queue<Consumer<Event>>[] unsortedListeners = new Queue[EventPriority.values().length];
|
||||
@SuppressWarnings("unchecked")
|
||||
Consumer<Event>[] listeners = new Consumer[0];
|
||||
boolean rebuild = true;
|
||||
volatile boolean rebuild = true;
|
||||
Listeners parent;
|
||||
List<Listeners> childs = null;
|
||||
|
||||
public Listeners() {
|
||||
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) {
|
||||
synchronized(unsortedListeners) {
|
||||
if(unsortedListeners[priority.getPriority()].add(listener)) markDirty();
|
||||
}
|
||||
if(unsortedListeners[priority.ordinal()].add(listener)) markDirty();
|
||||
}
|
||||
|
||||
public void removeListeners(Consumer<Event> listener) {
|
||||
synchronized(unsortedListeners) {
|
||||
for(int i = 0,m=unsortedListeners.length;i<m;i++) {
|
||||
if(unsortedListeners[i].remove(listener)) markDirty();
|
||||
}
|
||||
for(int i = 0,m=unsortedListeners.length;i<m;i++) {
|
||||
if(unsortedListeners[i].remove(listener)) markDirty();
|
||||
}
|
||||
}
|
||||
|
||||
public void getListeners(EventPriority entry, List<Consumer<Event>> events) {
|
||||
synchronized(unsortedListeners) {
|
||||
events.addAll(unsortedListeners[entry.getPriority()]);
|
||||
}
|
||||
events.addAll(unsortedListeners[entry.ordinal()]);
|
||||
if(parent != null) parent.getListeners(entry, events);
|
||||
}
|
||||
|
||||
@@ -78,11 +72,11 @@ public class Listeners
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void rebuildListeners() {
|
||||
rebuild = false;
|
||||
List<Consumer<Event>> result = new ObjectArrayList<>();
|
||||
for(EventPriority entry : EventPriority.getPriorities()) {
|
||||
for(EventPriority entry : EventPriority.values()) {
|
||||
getListeners(entry, result);
|
||||
}
|
||||
listeners = result.toArray(new Consumer[result.size()]);
|
||||
rebuild = false;
|
||||
}
|
||||
}
|
||||
|
||||
+210
@@ -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.maps.impl.concurrent.Long2ObjectConcurrentOpenHashMap;
|
||||
import speiger.src.collections.longs.maps.interfaces.Long2ObjectMap;
|
||||
import speiger.src.coreengine.platform.input.window.Window;
|
||||
import speiger.src.coreengine.core.events.api.Event;
|
||||
import speiger.src.coreengine.core.events.api.ICancelableEvent;
|
||||
import speiger.src.coreengine.core.events.bus.EventBus;
|
||||
import speiger.src.coreengine.platform.input.window.IWindowListener.Reason;
|
||||
import speiger.src.coreengine.utils.eventbus.Event;
|
||||
import speiger.src.coreengine.utils.eventbus.EventBus;
|
||||
import speiger.src.coreengine.platform.input.window.Window;
|
||||
|
||||
public abstract class AbstractDevice<T, E> implements InputDevice {
|
||||
protected Long2ObjectMap<Deque<E>> queues = new Long2ObjectConcurrentOpenHashMap<>();
|
||||
@@ -51,7 +52,7 @@ public abstract class AbstractDevice<T, E> implements InputDevice {
|
||||
protected boolean pushEvent(Event event) {
|
||||
if(bus == null) return true;
|
||||
bus.post(event);
|
||||
return event.isCancelable() && event.isCanceled();
|
||||
return event instanceof ICancelableEvent cancel && cancel.isCanceled();
|
||||
}
|
||||
|
||||
protected void push(long windowId, E task) {
|
||||
|
||||
@@ -16,11 +16,11 @@ import speiger.src.collections.ints.sets.IntSet;
|
||||
import speiger.src.collections.longs.collections.LongIterator;
|
||||
import speiger.src.collections.longs.sets.LongOpenHashSet;
|
||||
import speiger.src.collections.longs.sets.LongSet;
|
||||
import speiger.src.coreengine.core.events.bus.EventBus;
|
||||
import speiger.src.coreengine.platform.input.device.Joystick.JoyStickData;
|
||||
import speiger.src.coreengine.platform.input.device.Joystick.JoyStickTask;
|
||||
import speiger.src.coreengine.platform.input.events.JoystickEvent;
|
||||
import speiger.src.coreengine.platform.input.window.WindowManager;
|
||||
import speiger.src.coreengine.utils.eventbus.EventBus;
|
||||
|
||||
public class Joystick extends AbstractDevice<JoyStickData, JoyStickTask> {
|
||||
public static final Joystick INSTANCE = new Joystick();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package speiger.src.coreengine.platform.input.events;
|
||||
|
||||
import speiger.src.coreengine.utils.eventbus.Event;
|
||||
import speiger.src.coreengine.core.events.api.Event;
|
||||
|
||||
public class JoystickEvent extends Event {
|
||||
final long window;
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
package speiger.src.coreengine.platform.input.events;
|
||||
|
||||
import speiger.src.coreengine.utils.eventbus.Event;
|
||||
import speiger.src.coreengine.core.events.api.Event;
|
||||
import speiger.src.coreengine.core.events.api.ICancelableEvent;
|
||||
|
||||
public abstract class KeyEvent extends Event {
|
||||
public abstract class KeyEvent extends Event implements ICancelableEvent {
|
||||
final long window;
|
||||
|
||||
public KeyEvent(long window) {
|
||||
this.window = window;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelable() { return true; }
|
||||
public long window() { return window; }
|
||||
|
||||
public static class Key extends KeyEvent {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package speiger.src.coreengine.platform.input.events;
|
||||
|
||||
import speiger.src.coreengine.utils.eventbus.Event;
|
||||
import speiger.src.coreengine.core.events.api.Event;
|
||||
import speiger.src.coreengine.core.events.api.ICancelableEvent;
|
||||
|
||||
public abstract class MouseEvent extends Event {
|
||||
public abstract class MouseEvent extends Event implements ICancelableEvent {
|
||||
final long window;
|
||||
int x;
|
||||
int y;
|
||||
@@ -30,8 +31,6 @@ public abstract class MouseEvent extends Event {
|
||||
|
||||
//TODO implement support for Scaling
|
||||
|
||||
@Override
|
||||
public boolean isCancelable() { return true; }
|
||||
public boolean isForced() { return false; }
|
||||
|
||||
public static class Click extends MouseEvent {
|
||||
|
||||
@@ -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.ID;
|
||||
import speiger.src.coreengine.assets.manager.AssetManager;
|
||||
import speiger.src.coreengine.core.events.bus.EventBus;
|
||||
import speiger.src.coreengine.math.vector.matrix.Matrix4f;
|
||||
import speiger.src.coreengine.platform.graphics.api.buffer.VertexBuffer;
|
||||
import speiger.src.coreengine.platform.graphics.api.buffer.states.BufferState;
|
||||
@@ -41,11 +42,10 @@ import speiger.src.coreengine.platform.input.device.Keyboard;
|
||||
import speiger.src.coreengine.platform.input.device.Mouse;
|
||||
import speiger.src.coreengine.platform.input.window.Window;
|
||||
import speiger.src.coreengine.platform.input.window.WindowManager;
|
||||
import speiger.src.coreengine.utils.eventbus.EventBus;
|
||||
import speiger.src.coreengine.utils.helpers.IOUtils;
|
||||
|
||||
public class NewRenderEngineTest {
|
||||
EventBus bus = new EventBus();
|
||||
EventBus bus = EventBus.builder().build();
|
||||
WindowManager manager = new WindowManager();
|
||||
AssetManager assets = AssetManager.single(IAssetPackage.asset(IOUtils.getBaseLocation()));
|
||||
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
package speiger.src.coreengine.rendering.gui.font;
|
||||
|
||||
public class FontCache {
|
||||
float size;
|
||||
|
||||
|
||||
}
|
||||
+67
-67
@@ -1,67 +1,67 @@
|
||||
package speiger.src.coreengine.rendering.gui.layout.constraints;
|
||||
|
||||
import speiger.src.coreengine.rendering.gui.components.base.GuiComponent;
|
||||
import speiger.src.coreengine.rendering.gui.layout.constraints.IConstraint.Target;
|
||||
|
||||
public class ConstraintContainer {
|
||||
private static final int CONSTRAINT_LENGTH = 4;
|
||||
IConstraint[] constraints = new IConstraint[CONSTRAINT_LENGTH];
|
||||
|
||||
private ConstraintContainer() {}
|
||||
|
||||
public void apply(GuiComponent owner, GuiComponent parent, ConstrainedContext context) {
|
||||
for(int i = 0;i<4;i++) {
|
||||
if(constraints[i] == null) continue;
|
||||
constraints[i].apply(owner, parent, Target.by(i), context);
|
||||
}
|
||||
}
|
||||
|
||||
public void fetch(GuiComponent owner, GuiComponent parent, ConstrainedContext context) {
|
||||
for(int i = 2;i<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;
|
||||
}
|
||||
}
|
||||
}
|
||||
package speiger.src.coreengine.rendering.gui.layout.constraints;
|
||||
|
||||
import speiger.src.coreengine.rendering.gui.components.base.GuiComponent;
|
||||
import speiger.src.coreengine.rendering.gui.layout.constraints.IConstraint.Target;
|
||||
|
||||
public class ConstraintContainer {
|
||||
private static final int CONSTRAINT_LENGTH = 4;
|
||||
IConstraint[] constraints = new IConstraint[CONSTRAINT_LENGTH];
|
||||
|
||||
private ConstraintContainer() {}
|
||||
|
||||
public void apply(GuiComponent owner, GuiComponent parent, ConstrainedContext context) {
|
||||
for(int i = 0;i<4;i++) {
|
||||
if(constraints[i] == null) continue;
|
||||
constraints[i].apply(owner, parent, Target.by(i), context);
|
||||
}
|
||||
}
|
||||
|
||||
public void fetch(GuiComponent owner, GuiComponent parent, ConstrainedContext context) {
|
||||
for(int i = 2;i<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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+102
-102
@@ -1,102 +1,102 @@
|
||||
package speiger.src.coreengine.rendering.gui.layout.constraints;
|
||||
|
||||
import java.util.function.BooleanSupplier;
|
||||
|
||||
import speiger.src.coreengine.rendering.gui.components.base.GuiComponent;
|
||||
import speiger.src.coreengine.rendering.gui.layout.constraints.Constraints.Center;
|
||||
import speiger.src.coreengine.rendering.gui.layout.constraints.Constraints.Children;
|
||||
import speiger.src.coreengine.rendering.gui.layout.constraints.Constraints.Conditional;
|
||||
import speiger.src.coreengine.rendering.gui.layout.constraints.Constraints.Parent;
|
||||
import speiger.src.coreengine.rendering.gui.layout.constraints.Constraints.Pixels;
|
||||
import speiger.src.coreengine.rendering.gui.layout.constraints.Constraints.Relative;
|
||||
import speiger.src.coreengine.rendering.gui.layout.constraints.Constraints.Weighted;
|
||||
import speiger.src.coreengine.rendering.guiOld.helper.box.IGuiBox;
|
||||
|
||||
public interface IConstraint {
|
||||
public void apply(GuiComponent owner, GuiComponent parent, Target target, ConstrainedContext context);
|
||||
public void fetch(GuiComponent owner, GuiComponent parent, Target target, ConstrainedContext context);
|
||||
|
||||
public static IConstraint pixels(float value) { return Pixels.of(value); }
|
||||
public static IConstraint pixelsInv(float value) { return Pixels.inverted(value); }
|
||||
|
||||
public static IConstraint parent() { return Parent.of(); }
|
||||
public static IConstraint parent(float padding) { return Parent.of(padding); }
|
||||
public static IConstraint parentInv() { return Parent.inverted(); }
|
||||
public static IConstraint parentInv(float padding) { return Parent.inverted(padding); }
|
||||
|
||||
public static IConstraint children() { return Children.onlyBounds(); }
|
||||
public static IConstraint children(float padding) { return Children.onlyBounds(padding); }
|
||||
public static IConstraint childrenPos() { return Children.withPos(); }
|
||||
public static IConstraint childrenPos(float padding) { return Children.withPos(padding); }
|
||||
|
||||
public static IConstraint relative(float value) { return Relative.of(value); }
|
||||
public static IConstraint relative(float value, float padding) { return Relative.of(value, padding); }
|
||||
|
||||
public static IConstraint weightedPos() { return Weighted.of(0F); }
|
||||
public static IConstraint weightedInvPos() { return Weighted.inverted(0F); }
|
||||
public static IConstraint weighted(float weight) { return Weighted.of(weight); }
|
||||
public static IConstraint weightedPad(float weight, float padding) { return Weighted.padded(weight, padding); }
|
||||
public static IConstraint weightedMin(float weight, float minimum) { return Weighted.minimum(weight, minimum); }
|
||||
public static IConstraint weighted(float weight, float minimum, float padding) { return Weighted.of(weight, minimum, padding); }
|
||||
public static IConstraint weightedInv(float weight) { return Weighted.inverted(weight);}
|
||||
public static IConstraint weightedInvPad(float weight, float padding) { return Weighted.invertedPadded(weight, padding); }
|
||||
public static IConstraint weightedInvMin(float weight, float minimum) { return Weighted.invertedMinimum(weight, minimum); }
|
||||
public static IConstraint weightedInv(float weight, float minimum, float padding) { return Weighted.inverted(weight, minimum, padding); }
|
||||
|
||||
public static IConstraint center() { return Center.of(); }
|
||||
public static IConstraint conditional(BooleanSupplier supplier, IConstraint onTrue, IConstraint onFalse) { return Conditional.of(supplier, onTrue, onFalse); }
|
||||
|
||||
public static interface ISimpleConstraint extends IConstraint {
|
||||
@Override
|
||||
default void apply(GuiComponent owner, GuiComponent parent, Target target, ConstrainedContext context) { apply(owner.getBox(), parent == null ? owner.screen().getBox() : parent.getBox(), target, context); }
|
||||
public void apply(IGuiBox owner, IGuiBox parent, Target target, ConstrainedContext context);
|
||||
|
||||
@Override
|
||||
default void fetch(GuiComponent owner, GuiComponent parent, Target target, ConstrainedContext context) { fetch(owner.getBox(), parent == null ? owner.screen().getBox() : parent.getBox(), target, context); }
|
||||
public void fetch(IGuiBox owner, IGuiBox parent, Target target, ConstrainedContext context);
|
||||
}
|
||||
|
||||
public static enum Target {
|
||||
X,
|
||||
Y,
|
||||
WIDTH,
|
||||
HEIGHT;
|
||||
|
||||
static final Target[] BY_INDEX = values();
|
||||
|
||||
public static Target by(int index) { return BY_INDEX[index & 3]; }
|
||||
public static Target pos(boolean x) { return x ? X : Y; }
|
||||
public static Target bounds(boolean x) { return x ? WIDTH : HEIGHT; }
|
||||
public boolean isPosition() { return this == X || this == Y; }
|
||||
public boolean isXAxis() { return this == X || this == WIDTH; }
|
||||
public Target asArea() {
|
||||
return switch(this) {
|
||||
case X -> WIDTH;
|
||||
case Y -> HEIGHT;
|
||||
default -> this;
|
||||
};
|
||||
}
|
||||
|
||||
public float get(GuiComponent comp) { return get(comp.getBox()); }
|
||||
public void set(GuiComponent comp, float value) { set(comp.getBox(), value); }
|
||||
public float get(ConstrainedContext context) { return (isPosition() ? context.getOffset(isXAxis()) : context.get(isXAxis())); }
|
||||
|
||||
public float get(IGuiBox box) {
|
||||
return switch(this) {
|
||||
case X -> box.getBaseX();
|
||||
case Y -> box.getBaseY();
|
||||
case WIDTH -> box.getBaseWidth();
|
||||
case HEIGHT -> box.getBaseHeight();
|
||||
};
|
||||
}
|
||||
|
||||
public void set(IGuiBox box, float value) {
|
||||
switch(this) {
|
||||
case X -> box.setX(value);
|
||||
case Y -> box.setY(value);
|
||||
case WIDTH -> box.setWidth(value);
|
||||
case HEIGHT -> box.setHeight(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
package speiger.src.coreengine.rendering.gui.layout.constraints;
|
||||
|
||||
import java.util.function.BooleanSupplier;
|
||||
|
||||
import speiger.src.coreengine.rendering.gui.components.base.GuiComponent;
|
||||
import speiger.src.coreengine.rendering.gui.layout.constraints.Constraints.Center;
|
||||
import speiger.src.coreengine.rendering.gui.layout.constraints.Constraints.Children;
|
||||
import speiger.src.coreengine.rendering.gui.layout.constraints.Constraints.Conditional;
|
||||
import speiger.src.coreengine.rendering.gui.layout.constraints.Constraints.Parent;
|
||||
import speiger.src.coreengine.rendering.gui.layout.constraints.Constraints.Pixels;
|
||||
import speiger.src.coreengine.rendering.gui.layout.constraints.Constraints.Relative;
|
||||
import speiger.src.coreengine.rendering.gui.layout.constraints.Constraints.Weighted;
|
||||
import speiger.src.coreengine.rendering.guiOld.helper.box.IGuiBox;
|
||||
|
||||
public interface IConstraint {
|
||||
public void apply(GuiComponent owner, GuiComponent parent, Target target, ConstrainedContext context);
|
||||
public void fetch(GuiComponent owner, GuiComponent parent, Target target, ConstrainedContext context);
|
||||
|
||||
public static IConstraint pixels(float value) { return Pixels.of(value); }
|
||||
public static IConstraint pixelsInv(float value) { return Pixels.inverted(value); }
|
||||
|
||||
public static IConstraint parent() { return Parent.of(); }
|
||||
public static IConstraint parent(float padding) { return Parent.of(padding); }
|
||||
public static IConstraint parentInv() { return Parent.inverted(); }
|
||||
public static IConstraint parentInv(float padding) { return Parent.inverted(padding); }
|
||||
|
||||
public static IConstraint children() { return Children.onlyBounds(); }
|
||||
public static IConstraint children(float padding) { return Children.onlyBounds(padding); }
|
||||
public static IConstraint childrenPos() { return Children.withPos(); }
|
||||
public static IConstraint childrenPos(float padding) { return Children.withPos(padding); }
|
||||
|
||||
public static IConstraint relative(float value) { return Relative.of(value); }
|
||||
public static IConstraint relative(float value, float padding) { return Relative.of(value, padding); }
|
||||
|
||||
public static IConstraint weightedPos() { return Weighted.of(0F); }
|
||||
public static IConstraint weightedInvPos() { return Weighted.inverted(0F); }
|
||||
public static IConstraint weighted(float weight) { return Weighted.of(weight); }
|
||||
public static IConstraint weightedPad(float weight, float padding) { return Weighted.padded(weight, padding); }
|
||||
public static IConstraint weightedMin(float weight, float minimum) { return Weighted.minimum(weight, minimum); }
|
||||
public static IConstraint weighted(float weight, float minimum, float padding) { return Weighted.of(weight, minimum, padding); }
|
||||
public static IConstraint weightedInv(float weight) { return Weighted.inverted(weight);}
|
||||
public static IConstraint weightedInvPad(float weight, float padding) { return Weighted.invertedPadded(weight, padding); }
|
||||
public static IConstraint weightedInvMin(float weight, float minimum) { return Weighted.invertedMinimum(weight, minimum); }
|
||||
public static IConstraint weightedInv(float weight, float minimum, float padding) { return Weighted.inverted(weight, minimum, padding); }
|
||||
|
||||
public static IConstraint center() { return Center.of(); }
|
||||
public static IConstraint conditional(BooleanSupplier supplier, IConstraint onTrue, IConstraint onFalse) { return Conditional.of(supplier, onTrue, onFalse); }
|
||||
|
||||
public static interface ISimpleConstraint extends IConstraint {
|
||||
@Override
|
||||
default void apply(GuiComponent owner, GuiComponent parent, Target target, ConstrainedContext context) { apply(owner.getBox(), parent == null ? owner.screen().getBox() : parent.getBox(), target, context); }
|
||||
public void apply(IGuiBox owner, IGuiBox parent, Target target, ConstrainedContext context);
|
||||
|
||||
@Override
|
||||
default void fetch(GuiComponent owner, GuiComponent parent, Target target, ConstrainedContext context) { fetch(owner.getBox(), parent == null ? owner.screen().getBox() : parent.getBox(), target, context); }
|
||||
public void fetch(IGuiBox owner, IGuiBox parent, Target target, ConstrainedContext context);
|
||||
}
|
||||
|
||||
public static enum Target {
|
||||
X,
|
||||
Y,
|
||||
WIDTH,
|
||||
HEIGHT;
|
||||
|
||||
static final Target[] BY_INDEX = values();
|
||||
|
||||
public static Target by(int index) { return BY_INDEX[index & 3]; }
|
||||
public static Target pos(boolean x) { return x ? X : Y; }
|
||||
public static Target bounds(boolean x) { return x ? WIDTH : HEIGHT; }
|
||||
public boolean isPosition() { return this == X || this == Y; }
|
||||
public boolean isXAxis() { return this == X || this == WIDTH; }
|
||||
public Target asArea() {
|
||||
return switch(this) {
|
||||
case X -> WIDTH;
|
||||
case Y -> HEIGHT;
|
||||
default -> this;
|
||||
};
|
||||
}
|
||||
|
||||
public float get(GuiComponent comp) { return get(comp.getBox()); }
|
||||
public void set(GuiComponent comp, float value) { set(comp.getBox(), value); }
|
||||
public float get(ConstrainedContext context) { return (isPosition() ? context.getOffset(isXAxis()) : context.get(isXAxis())); }
|
||||
|
||||
public float get(IGuiBox box) {
|
||||
return switch(this) {
|
||||
case X -> box.getBaseX();
|
||||
case Y -> box.getBaseY();
|
||||
case WIDTH -> box.getBaseWidth();
|
||||
case HEIGHT -> box.getBaseHeight();
|
||||
};
|
||||
}
|
||||
|
||||
public void set(IGuiBox box, float value) {
|
||||
switch(this) {
|
||||
case X -> box.setX(value);
|
||||
case Y -> box.setY(value);
|
||||
case WIDTH -> box.setWidth(value);
|
||||
case HEIGHT -> box.setHeight(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user