Implement experimental window icon support

This commit is contained in:
JeremyStar™ 2024-10-15 03:30:49 +02:00
parent cdd68833cb
commit 1e6fa79716
Signed by: JeremyStarTM
GPG key ID: E366BAEF67E4704D
5 changed files with 148 additions and 13 deletions

View file

@ -134,9 +134,11 @@ public final class Main {
// Create window // Create window
Window window; Window window;
try { try {
// Build window
window = new Window.Builder() window = new Window.Builder()
.setName("sosengine-testapp") .setName("sosengine-testapp")
.setTitle("test application window") .setTitle("test application window")
//.setIcons(new Path[]{ new File("/home/jeremystartm/Code/StarOpenSource/Engine/dist/branding/current.png").toPath() })
.setSize(new Vec2i(960, 540)) .setSize(new Vec2i(960, 540))
.setPosition(new Vec2i(10, 10)) .setPosition(new Vec2i(10, 10))
.build(); .build();
@ -145,6 +147,7 @@ public final class Main {
return; return;
} }
// Render loop
LinkedHashMap<@NotNull Window, @NotNull Throwable> renderLoopFailures = WindowingSubsystem LinkedHashMap<@NotNull Window, @NotNull Throwable> renderLoopFailures = WindowingSubsystem
.getInstance() .getInstance()
.getApi() .getApi()
@ -153,6 +156,8 @@ public final class Main {
if (shutdown || window.isClosureRequested()) if (shutdown || window.isClosureRequested())
Engine.getInstance().shutdown(); Engine.getInstance().shutdown();
}); });
// Print render loop failures
StringBuilder message = new StringBuilder(); StringBuilder message = new StringBuilder();
message.append("Render loop failed on some windows:\n"); message.append("Render loop failed on some windows:\n");
@ -172,7 +177,6 @@ public final class Main {
System.err.println(Miscellaneous.getStackTraceHeader(exception)); System.err.println(Miscellaneous.getStackTraceHeader(exception));
System.err.println(Miscellaneous.getStackTraceAsString(exception, true)); System.err.println(Miscellaneous.getStackTraceAsString(exception, true));
// Halt JVM
Runtime.getRuntime().halt(255); Runtime.getRuntime().halt(255);
} }
} }

View file

@ -31,13 +31,12 @@ switch (OperatingSystem.current()) {
case OperatingSystem.LINUX: case OperatingSystem.LINUX:
project.dependencyLwjglNatives = "natives-linux" project.dependencyLwjglNatives = "natives-linux"
def osArch = System.getProperty("os.arch") def osArch = System.getProperty("os.arch")
if (osArch.startsWith("arm") || osArch.startsWith("aarch64")) { if (osArch.startsWith("arm") || osArch.startsWith("aarch64"))
project.dependencyLwjglNatives += osArch.contains("64") || osArch.startsWith("armv8") ? "-arm64" : "-arm32" project.dependencyLwjglNatives += osArch.contains("64") || osArch.startsWith("armv8") ? "-arm64" : "-arm32"
} else if (osArch.startsWith("ppc")) { else if (osArch.startsWith("ppc"))
project.dependencyLwjglNatives += "-ppc64le" project.dependencyLwjglNatives += "-ppc64le"
} else if (osArch.startsWith("riscv")) { else if (osArch.startsWith("riscv"))
project.dependencyLwjglNatives += "-riscv64" project.dependencyLwjglNatives += "-riscv64"
}
break break
case OperatingSystem.MAC_OS: case OperatingSystem.MAC_OS:
project.dependencyLwjglNatives = System.getProperty("os.arch").startsWith("aarch64") ? "natives-macos-arm64" : "natives-macos" project.dependencyLwjglNatives = System.getProperty("os.arch").startsWith("aarch64") ? "natives-macos-arm64" : "natives-macos"
@ -64,8 +63,10 @@ dependencies {
implementation(platform("org.lwjgl:lwjgl-bom:${dependencyLwjgl}")) implementation(platform("org.lwjgl:lwjgl-bom:${dependencyLwjgl}"))
implementation("org.lwjgl:lwjgl") implementation("org.lwjgl:lwjgl")
implementation("org.lwjgl:lwjgl-glfw") implementation("org.lwjgl:lwjgl-glfw")
implementation("org.lwjgl:lwjgl-stb")
runtimeOnly("org.lwjgl:lwjgl::${dependencyLwjglNatives}") runtimeOnly("org.lwjgl:lwjgl::${dependencyLwjglNatives}")
runtimeOnly("org.lwjgl:lwjgl-glfw::${dependencyLwjglNatives}") runtimeOnly("org.lwjgl:lwjgl-glfw::${dependencyLwjglNatives}")
runtimeOnly("org.lwjgl:lwjgl-stb::${dependencyLwjglNatives}")
if (project.dependencyLwjglNatives == "natives-macos" || project.dependencyLwjglNatives == "natives-macos-arm64") runtimeOnly("org.lwjgl:lwjgl-vulkan::${dependencyLwjglNatives}") if (project.dependencyLwjglNatives == "natives-macos" || project.dependencyLwjglNatives == "natives-macos-arm64") runtimeOnly("org.lwjgl:lwjgl-vulkan::${dependencyLwjglNatives}")
// -> Testing <- // -> Testing <-
@ -126,7 +127,7 @@ test {
useJUnitPlatform() useJUnitPlatform()
// Pass test configuration to test VMs // Pass test configuration to test VMs
Map<String, String> testConfiguration = new HashMap<>(); Map<String, String> testConfiguration = new HashMap<>()
for (String property : project.properties.keySet()) for (String property : project.properties.keySet())
if (property.startsWith("test.")) if (property.startsWith("test."))
testConfiguration.put(property, project.properties.get(property).toString()) testConfiguration.put(property, project.properties.get(property).toString())

View file

@ -19,6 +19,7 @@
package de.staropensource.engine.windowing.glfw.implementable; package de.staropensource.engine.windowing.glfw.implementable;
import de.staropensource.engine.base.logging.Logger;
import de.staropensource.engine.base.type.vector.Vec2i; import de.staropensource.engine.base.type.vector.Vec2i;
import de.staropensource.engine.base.utility.Miscellaneous; import de.staropensource.engine.base.utility.Miscellaneous;
import de.staropensource.engine.windowing.WindowingSubsystemConfiguration; import de.staropensource.engine.windowing.WindowingSubsystemConfiguration;
@ -33,13 +34,21 @@ import de.staropensource.engine.windowing.implementable.Window;
import de.staropensource.engine.windowing.type.window.VsyncMode; import de.staropensource.engine.windowing.type.window.VsyncMode;
import de.staropensource.engine.windowing.type.window.WindowMode; import de.staropensource.engine.windowing.type.window.WindowMode;
import lombok.Getter; import lombok.Getter;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.glfw.GLFWImage;
import org.lwjgl.glfw.GLFWKeyCallback; import org.lwjgl.glfw.GLFWKeyCallback;
import org.lwjgl.glfw.GLFWMouseButtonCallback; import org.lwjgl.glfw.GLFWMouseButtonCallback;
import org.lwjgl.stb.STBImage;
import org.lwjgl.system.MemoryStack; import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.MemoryUtil; import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer;
import java.nio.IntBuffer; import java.nio.IntBuffer;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects; import java.util.Objects;
import static org.lwjgl.glfw.GLFW.*; import static org.lwjgl.glfw.GLFW.*;
@ -84,6 +93,7 @@ public final class GlfwWindow extends Window {
* *
* @param name name * @param name name
* @param title title * @param title title
* @param icons icons
* @param size size * @param size size
* @param minimumSize minimum size * @param minimumSize minimum size
* @param maximumSize maximum size * @param maximumSize maximum size
@ -99,8 +109,8 @@ public final class GlfwWindow extends Window {
* @throws Exception stuff thrown by the {@link #initializeWindow()} and {@link #render()} methods of the implementing windowing API * @throws Exception stuff thrown by the {@link #initializeWindow()} and {@link #render()} methods of the implementing windowing API
* @since v1-alpha2 * @since v1-alpha2
*/ */
public GlfwWindow(@NotNull String name, @NotNull String title, @NotNull Vec2i size, @NotNull Vec2i minimumSize, @NotNull Vec2i maximumSize, @NotNull Vec2i position, @NotNull WindowMode windowMode, @NotNull Monitor monitor, boolean resizable, boolean borderless, boolean focusable, boolean onTop, boolean transparent, boolean rendering) throws Exception { public GlfwWindow(@NotNull String name, @NotNull String title, @NotNull Path @Nullable [] icons, @NotNull Vec2i size, @NotNull Vec2i minimumSize, @NotNull Vec2i maximumSize, @NotNull Vec2i position, @NotNull WindowMode windowMode, @NotNull Monitor monitor, boolean resizable, boolean borderless, boolean focusable, boolean onTop, boolean transparent, boolean rendering) throws Exception {
super(name, title, size, minimumSize, maximumSize, position, windowMode, monitor, resizable, borderless, focusable, onTop, transparent, rendering); super(name, title, icons, size, minimumSize, maximumSize, position, windowMode, monitor, resizable, borderless, focusable, onTop, transparent, rendering);
} }
/** {@inheritDoc} */ /** {@inheritDoc} */
@ -163,6 +173,7 @@ public final class GlfwWindow extends Window {
glfwSetMouseButtonCallback(identifier, mouseButtonCallback); glfwSetMouseButtonCallback(identifier, mouseButtonCallback);
// Update the window state // Update the window state
setIcons(getIcons());
setSize(getSize()); setSize(getSize());
setMinimumSize(getMinimumSize()); setMinimumSize(getMinimumSize());
setMaximumSize(getMaximumSize()); setMaximumSize(getMaximumSize());
@ -342,6 +353,67 @@ public final class GlfwWindow extends Window {
glfwSetWindowTitle(identifierLong, title); glfwSetWindowTitle(identifierLong, title);
} }
/** {@inheritDoc} */
@ApiStatus.Experimental
@Override
public void setIcons(@NotNull Path @Nullable [] icons) {
getLogger().warn("GlfwWindow#setIcons is experimental and may cause engine or JVM crashes. Here be dragons!");
// Ensure the window is not terminated
if (isTerminated())
return;
super.setIcons(icons);
if (icons != null)
try (GLFWImage.Buffer iconsBuffer = GLFWImage.malloc(icons.length)) {
getLogger().diag("GlfwWindow#setIcons » icons.length = " + icons.length);
List<ByteBuffer> iconBuffers = new ArrayList<>();
IntBuffer width = MemoryUtil.memAllocInt(1);
IntBuffer height = MemoryUtil.memAllocInt(1);
IntBuffer channels = MemoryUtil.memAllocInt(1);
for (Path filepath : icons) {
getLogger().diag("GlfwWindow#setIcons » iterating icons » " + iconBuffers.size() + " » " + filepath);
// Load icon
getLogger().diag("GlfwWindow#setIcons » loading icon");
iconBuffers.add(STBImage.stbi_load(filepath.toAbsolutePath().toString(), width, height, channels, 4));
if (iconBuffers.getLast() == null) {
getLogger().warn("Icon " + iconsBuffer.position() + " could not be loaded" + (STBImage.stbi_failure_reason() == null ? "" : ": " + STBImage.stbi_failure_reason()));
continue;
}
// Save into 'iconsBuffer'
getLogger().diag("GlfwWindow#setIcons » saving into buffer");
iconsBuffer
.position(iconsBuffer.position() + 1)
.width(width.get(0))
.height(height.get(0))
.pixels(iconBuffers.getLast());
}
getLogger().diag("GlfwWindow#setIcons » out of iteration");
// Set icons
getLogger().diag("GlfwWindow#setIcons » setting position");
iconsBuffer.position(0);
getLogger().diag("GlfwWindow#setIcons » setting icons");
Logger.flushLogMessages();
glfwSetWindowIcon(identifierLong, iconsBuffer);
// Free icons
getLogger().diag("GlfwWindow#setIcons » freeing icons");
for (ByteBuffer buffer : iconBuffers)
if (buffer != null) {
getLogger().diag("GlfwWindow#setIcons » freeing buffer");
STBImage.stbi_image_free(buffer);
} else
getLogger().diag("GlfwWindow#setIcons » skipping null buffer");
}
else
getLogger().diag("GlfwWindow#setIcons » icons is null");
}
/** {@inheritDoc} */ /** {@inheritDoc} */
@Override @Override
public void setSize(@NotNull Vec2i size) { public void setSize(@NotNull Vec2i size) {

View file

@ -12,8 +12,8 @@ module sosengine.windowing.glfw {
// -> Libraries // -> Libraries
requires transitive static lombok; requires transitive static lombok;
requires transitive org.jetbrains.annotations; requires transitive org.jetbrains.annotations;
requires org.lwjgl;
requires org.lwjgl.glfw; requires org.lwjgl.glfw;
requires org.lwjgl.stb;
// API access // API access
exports de.staropensource.engine.windowing.glfw; exports de.staropensource.engine.windowing.glfw;

View file

@ -30,6 +30,7 @@ import lombok.Setter;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.nio.file.Path;
import java.util.*; import java.util.*;
/** /**
@ -202,6 +203,27 @@ public abstract class Window implements AutoCloseable {
@Setter @Setter
private @NotNull String title; private @NotNull String title;
/**
* Contains file paths to window
* icons in the PNG format.
*
* @since v1-alpha6
* -- GETTER --
* Returns file paths to window
* icons in the PNG format.
*
* @return file paths to icons
* @since v1-alpha6
* -- SETTER --
* Sets file paths to window
* icons in the PNG format.
*
* @param icons new file paths to icons
* @since v1-alpha6
*/
@Setter
private @NotNull Path @Nullable [] icons;
/** /**
* Contains the size of this window. * Contains the size of this window.
* *
@ -470,6 +492,7 @@ public abstract class Window implements AutoCloseable {
* *
* @param name name * @param name name
* @param title title * @param title title
* @param icons icon file paths
* @param size size * @param size size
* @param minimumSize minimum size * @param minimumSize minimum size
* @param maximumSize maximum size * @param maximumSize maximum size
@ -485,11 +508,12 @@ public abstract class Window implements AutoCloseable {
* @throws Exception stuff thrown by the {@link #initializeWindow()} and {@link #render()} methods of the implementing windowing API * @throws Exception stuff thrown by the {@link #initializeWindow()} and {@link #render()} methods of the implementing windowing API
* @since v1-alpha2 * @since v1-alpha2
*/ */
protected Window(@NotNull String name, @NotNull String title, @NotNull Vec2i size, @NotNull Vec2i minimumSize, @NotNull Vec2i maximumSize, @NotNull Vec2i position, @NotNull WindowMode windowMode, @NotNull Monitor monitor, boolean resizable, boolean borderless, boolean focusable, boolean onTop, boolean transparent, boolean rendering) throws Exception { protected Window(@NotNull String name, @NotNull String title, @NotNull Path @Nullable [] icons, @NotNull Vec2i size, @NotNull Vec2i minimumSize, @NotNull Vec2i maximumSize, @NotNull Vec2i position, @NotNull WindowMode windowMode, @NotNull Monitor monitor, boolean resizable, boolean borderless, boolean focusable, boolean onTop, boolean transparent, boolean rendering) throws Exception {
// Initialize variables // Initialize variables
this.uniqueIdentifier = UUID.randomUUID(); this.uniqueIdentifier = UUID.randomUUID();
this.name = name; this.name = name;
this.title = title; this.title = title;
this.icons = icons;
this.size = size; this.size = size;
this.minimumSize = minimumSize; this.minimumSize = minimumSize;
this.maximumSize = maximumSize; this.maximumSize = maximumSize;
@ -504,7 +528,7 @@ public abstract class Window implements AutoCloseable {
this.rendering = rendering; this.rendering = rendering;
// Log about window creation // Log about window creation
logger.diag("Creating new window with properties: uniqueIdentifier=" + uniqueIdentifier + " name=\"" + name + "\" title=\"" + title + "\" size=" + size + " minimumSize=" + minimumSize + " maximumSize=" + maximumSize + " position=" + position + " windowMode=" + windowMode + " monitor=" + monitor.getUniqueIdentifier() + " (" + monitor.getName() + ") resizable=" + resizable + " borderless=" + borderless + " focusable=" + focusable + " onTop=" + onTop + " transparent=" + transparent + " rendering=" + rendering); logger.diag("Creating new window with properties: uniqueIdentifier=" + uniqueIdentifier + " name=\"" + name + "\" title=\"" + title + "\" icons=" + Arrays.toString(icons) + " size=" + size + " minimumSize=" + minimumSize + " maximumSize=" + maximumSize + " position=" + position + " windowMode=" + windowMode + " monitor=" + monitor.getUniqueIdentifier() + " (" + monitor.getName() + ") resizable=" + resizable + " borderless=" + borderless + " focusable=" + focusable + " onTop=" + onTop + " transparent=" + transparent + " rendering=" + rendering);
// Allow windowing API to initialize window // Allow windowing API to initialize window
initializeWindow(); initializeWindow();
@ -627,6 +651,14 @@ public abstract class Window implements AutoCloseable {
*/ */
private @Nullable String title = null; private @Nullable String title = null;
/**
* Contains the window icons in the PNG format.
*
* @see Window#icons
* @since v1-alpha6
*/
private @NotNull Path @Nullable [] icons = null;
/** /**
* Contains the window size. * Contains the window size.
* *
@ -783,8 +815,8 @@ public abstract class Window implements AutoCloseable {
// Create new Window instance // Create new Window instance
return WindowingSubsystem.getInstance().getApi().getInternalApi().getWindowClass() return WindowingSubsystem.getInstance().getApi().getInternalApi().getWindowClass()
.getDeclaredConstructor(String.class, String.class, Vec2i.class, Vec2i.class, Vec2i.class, Vec2i.class, WindowMode.class, Monitor.class, Boolean.TYPE, Boolean.TYPE, Boolean.TYPE, Boolean.TYPE, Boolean.TYPE, Boolean.TYPE) .getDeclaredConstructor(String.class, String.class, Path[].class, Vec2i.class, Vec2i.class, Vec2i.class, Vec2i.class, WindowMode.class, Monitor.class, Boolean.TYPE, Boolean.TYPE, Boolean.TYPE, Boolean.TYPE, Boolean.TYPE, Boolean.TYPE)
.newInstance(name, title, size, minimumSize, maximumSize, position, windowMode, monitor, resizableBoolean, borderlessBoolean, focusableBoolean, onTopBoolean, transparentBoolean, renderingBoolean); .newInstance(name, title, icons, size, minimumSize, maximumSize, position, windowMode, monitor, resizableBoolean, borderlessBoolean, focusableBoolean, onTopBoolean, transparentBoolean, renderingBoolean);
} }
/** /**
@ -809,6 +841,18 @@ public abstract class Window implements AutoCloseable {
return title; return title;
} }
/**
* Returns file paths to window
* icons in the PNG format.
*
* @return file paths to icons
* @see Window#icons
* @since v1-alpha6
*/
public @NotNull Path @Nullable [] getIcons() {
return icons;
}
/** /**
* Returns the window size. * Returns the window size.
* *
@ -967,6 +1011,20 @@ public abstract class Window implements AutoCloseable {
return this; return this;
} }
/**
* Sets file paths to window
* icons in the PNG format.
*
* @param icons new file paths to icons
* @return builder instance
* @see Window#icons
* @since v1-alpha6
*/
public @NotNull Builder setIcons(@NotNull Path @Nullable [] icons) {
this.icons = icons;
return this;
}
/** /**
* Sets the window size. * Sets the window size.
* *