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