diff --git a/app/src/main/java/org/toop/app/menu/MainMenu.java b/app/src/main/java/org/toop/app/menu/MainMenu.java index c5a39ca..7d1d2d0 100644 --- a/app/src/main/java/org/toop/app/menu/MainMenu.java +++ b/app/src/main/java/org/toop/app/menu/MainMenu.java @@ -4,6 +4,8 @@ import javafx.geometry.Pos; import javafx.scene.control.Button; import javafx.scene.image.ImageView; import javafx.scene.layout.*; +import javafx.scene.text.Font; +import org.toop.framework.asset.resources.FontAsset; import org.toop.framework.asset.resources.LocalizationAsset; import java.util.Locale; @@ -12,10 +14,11 @@ import org.toop.framework.asset.resources.CssAsset; import org.toop.framework.asset.resources.ImageAsset; public final class MainMenu extends Menu { - private Locale currentLocale = Locale.of("nl"); - private LocalizationAsset loc = AssetManager.get("localization.properties"); + private final Locale currentLocale = Locale.of("nl"); + private final LocalizationAsset loc = AssetManager.get("localization.properties"); public MainMenu() { + final Button tictactoe = createButton(loc.getString("mainMenuSelectTicTacToe", currentLocale), () -> {}); final Button reversi = createButton(loc.getString("mainMenuSelectReversi", currentLocale), () -> {}); final Button sudoku = createButton(loc.getString("mainMenuSelectSudoku", currentLocale), () -> {}); diff --git a/app/src/main/resources/assets/fonts/GroovyManiac.ttf b/app/src/main/resources/assets/fonts/GroovyManiac.ttf new file mode 100644 index 0000000..4202757 Binary files /dev/null and b/app/src/main/resources/assets/fonts/GroovyManiac.ttf differ diff --git a/app/src/main/resources/assets/fonts/Roboto-Regular.ttf b/app/src/main/resources/assets/fonts/Roboto-Regular.ttf new file mode 100644 index 0000000..7e3bb2f Binary files /dev/null and b/app/src/main/resources/assets/fonts/Roboto-Regular.ttf differ diff --git a/app/src/main/resources/assets/style/main.css b/app/src/main/resources/assets/style/main.css index 99e8087..b6abd24 100644 --- a/app/src/main/resources/assets/style/main.css +++ b/app/src/main/resources/assets/style/main.css @@ -1,6 +1,5 @@ .main-button { -fx-background-color: transparent; - -fx-background-image: url("card-default.jpg"); /* fallback image */ -fx-background-size: cover; -fx-background-position: center; -fx-pref-width: 250px; diff --git a/app/src/main/resources/assets/style/quit.css b/app/src/main/resources/assets/style/quit.css index 59c72a3..212734d 100644 --- a/app/src/main/resources/assets/style/quit.css +++ b/app/src/main/resources/assets/style/quit.css @@ -13,7 +13,7 @@ -fx-fill: white; -fx-font-size: 28px; -fx-font-weight: 600; - -fx-font-family: "Segoe UI", sans-serif; + -fx-font-family: "Groovy Maniac Demo", sans-serif; } .quit-button { diff --git a/framework/src/main/java/org/toop/framework/SnowflakeGenerator.java b/framework/src/main/java/org/toop/framework/SnowflakeGenerator.java index a48a8a7..7fbb946 100644 --- a/framework/src/main/java/org/toop/framework/SnowflakeGenerator.java +++ b/framework/src/main/java/org/toop/framework/SnowflakeGenerator.java @@ -5,7 +5,38 @@ import java.time.Instant; import java.util.Collections; import java.util.concurrent.atomic.AtomicLong; +/** + * A thread-safe, distributed unique ID generator following the Snowflake pattern. + *
+ * Each generated 64-bit ID encodes: + *
This implementation ensures: + *
Custom epoch is set to {@code 2025-01-01T00:00:00Z}.
+ * + *Usage example:
+ *{@code
+ * SnowflakeGenerator generator = new SnowflakeGenerator();
+ * long id = generator.nextId();
+ * }
+ */
public class SnowflakeGenerator {
+
+ /**
+ * Custom epoch in milliseconds (2025-01-01T00:00:00Z).
+ */
private static final long EPOCH = Instant.parse("2025-01-01T00:00:00Z").toEpochMilli();
// Bit allocations
@@ -13,19 +44,27 @@ public class SnowflakeGenerator {
private static final long MACHINE_BITS = 10;
private static final long SEQUENCE_BITS = 12;
- // Max values
+ // Maximum values for each component
private static final long MAX_MACHINE_ID = (1L << MACHINE_BITS) - 1;
private static final long MAX_SEQUENCE = (1L << SEQUENCE_BITS) - 1;
private static final long MAX_TIMESTAMP = (1L << TIMESTAMP_BITS) - 1;
- // Bit shifts
+ // Bit shifts for composing the ID
private static final long MACHINE_SHIFT = SEQUENCE_BITS;
private static final long TIMESTAMP_SHIFT = SEQUENCE_BITS + MACHINE_BITS;
+ /**
+ * Unique machine identifier derived from network interfaces (10 bits).
+ */
private static final long machineId = SnowflakeGenerator.genMachineId();
+
private final AtomicLong lastTimestamp = new AtomicLong(-1L);
private long sequence = 0L;
+ /**
+ * Generates a 10-bit machine identifier based on MAC addresses of network interfaces.
+ * Falls back to a random value if MAC cannot be determined.
+ */
private static long genMachineId() {
try {
StringBuilder sb = new StringBuilder();
@@ -35,17 +74,25 @@ public class SnowflakeGenerator {
for (byte b : mac) sb.append(String.format("%02X", b));
}
}
- // limit to 10 bits (0–1023)
- return sb.toString().hashCode() & 0x3FF;
+ return sb.toString().hashCode() & 0x3FF; // limit to 10 bits
} catch (Exception e) {
return (long) (Math.random() * 1024); // fallback
}
}
+ /**
+ * For testing: manually set the last generated timestamp.
+ * @param l timestamp in milliseconds
+ */
void setTime(long l) {
this.lastTimestamp.set(l);
}
+ /**
+ * Constructs a SnowflakeGenerator.
+ * Validates that the machine ID is within allowed range.
+ * @throws IllegalArgumentException if machine ID is invalid
+ */
public SnowflakeGenerator() {
if (machineId < 0 || machineId > MAX_MACHINE_ID) {
throw new IllegalArgumentException(
@@ -53,6 +100,16 @@ public class SnowflakeGenerator {
}
}
+ /**
+ * Generates the next unique ID.
+ * + * If multiple IDs are generated in the same millisecond, a sequence number + * is incremented. If the sequence overflows, waits until the next millisecond. + *
+ * + * @return a unique 64-bit ID + * @throws IllegalStateException if clock moves backwards or timestamp exceeds 41-bit limit + */ public synchronized long nextId() { long currentTimestamp = timestamp(); @@ -67,7 +124,6 @@ public class SnowflakeGenerator { if (currentTimestamp == lastTimestamp.get()) { sequence = (sequence + 1) & MAX_SEQUENCE; if (sequence == 0) { - // Sequence overflow, wait for next millisecond currentTimestamp = waitNextMillis(currentTimestamp); } } else { @@ -81,6 +137,11 @@ public class SnowflakeGenerator { | sequence; } + /** + * Waits until the next millisecond if sequence overflows. + * @param lastTimestamp previous timestamp + * @return new timestamp + */ private long waitNextMillis(long lastTimestamp) { long ts = timestamp(); while (ts <= lastTimestamp) { @@ -89,6 +150,9 @@ public class SnowflakeGenerator { return ts; } + /** + * Returns current system timestamp in milliseconds. + */ private long timestamp() { return System.currentTimeMillis(); } diff --git a/framework/src/main/java/org/toop/framework/asset/AssetLoader.java b/framework/src/main/java/org/toop/framework/asset/AssetLoader.java index aef50bf..cfc04d6 100644 --- a/framework/src/main/java/org/toop/framework/asset/AssetLoader.java +++ b/framework/src/main/java/org/toop/framework/asset/AssetLoader.java @@ -2,7 +2,7 @@ package org.toop.framework.asset; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.toop.framework.asset.events.AssetEvents; +import org.toop.framework.asset.events.AssetLoaderEvents; import org.toop.framework.asset.resources.*; import org.toop.framework.eventbus.EventFlow; import org.reflections.Reflections; @@ -14,6 +14,35 @@ import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; +/** + * Responsible for loading assets from a file system directory into memory. + *+ * The {@code AssetLoader} scans a root folder recursively, identifies files, + * and maps them to registered resource types based on file extensions and + * {@link FileExtension} annotations. + * It supports multiple resource types including {@link PreloadResource} (automatically loaded) + * and {@link BundledResource} (merged across multiple files). + *
+ * + *Assets are stored in a static, thread-safe list and can be retrieved + * through {@link AssetManager}.
+ * + *Features:
+ *Usage example:
+ *{@code
+ * AssetLoader loader = new AssetLoader("assets");
+ * double progress = loader.getProgress();
+ * List> loadedAssets = loader.getAssets();
+ * }
+ */
public class AssetLoader {
private static final Logger logger = LogManager.getLogger(AssetLoader.class);
private static final List+ * {@code AssetManager} maintains a thread-safe registry of {@link Asset} objects + * and provides utility methods to retrieve assets by name, ID, or type. + * It works together with {@link AssetLoader} to register assets automatically + * when they are loaded from the file system. + *
+ * + *Key responsibilities:
+ *Example usage:
+ *{@code
+ * // Load assets from a loader
+ * AssetLoader loader = new AssetLoader(new File("RootFolder"));
+ * AssetManager.loadAssets(loader);
+ *
+ * // Retrieve a single resource
+ * ImageAsset background = AssetManager.get("background.jpg");
+ *
+ * // Retrieve all fonts
+ * List> fonts = AssetManager.getAllOfType(FontAsset.class);
+ *
+ * // Retrieve by asset name or optional lookup
+ * Optional> maybeAsset = AssetManager.findByName("menu.css");
+ * }
+ *
+ * Notes:
+ *Implementing classes allow an {@link org.toop.framework.asset.AssetLoader} + * to automatically merge multiple related files into a single resource instance.
+ * + *Typical use cases include:
+ *Implementing classes must provide:
+ *Example usage:
+ *{@code
+ * public class LocalizationAsset extends BaseResource implements BundledResource {
+ * private final String baseName;
+ *
+ * public LocalizationAsset(File file) {
+ * super(file);
+ * this.baseName = extractBaseName(file.getName());
+ * loadFile(file);
+ * }
+ *
+ * @Override
+ * public void loadFile(File file) {
+ * // merge file into existing bundles
+ * }
+ *
+ * @Override
+ * public String getBaseName() {
+ * return baseName;
+ * }
+ * }
+ * }
+ *
+ * When used with an asset loader, all files sharing the same base name are + * automatically merged into a single resource instance.
+ */ public interface BundledResource { + /** * Load or merge an additional file into this resource. + * + * @param file the file to load or merge */ void loadFile(File file); /** * Return a base name for grouping multiple files into this single resource. + * Files with the same base name are automatically merged by the loader. + * + * @return the base name used to identify this bundled resource */ String getBaseName(); } \ No newline at end of file diff --git a/framework/src/main/java/org/toop/framework/asset/resources/FileExtension.java b/framework/src/main/java/org/toop/framework/asset/resources/FileExtension.java index 4aceb00..1beb405 100644 --- a/framework/src/main/java/org/toop/framework/asset/resources/FileExtension.java +++ b/framework/src/main/java/org/toop/framework/asset/resources/FileExtension.java @@ -5,8 +5,37 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.annotation.ElementType; +/** + * Annotation to declare which file extensions a {@link BaseResource} subclass + * can handle. + * + *This annotation is processed by the {@link org.toop.framework.asset.AssetLoader} + * to automatically register resource types for specific file extensions. + * Each extension listed will be mapped to the annotated resource class, + * allowing the loader to instantiate the correct type when scanning files.
+ * + *Usage example:
+ *{@code
+ * @FileExtension({"png", "jpg"})
+ * public class ImageAsset extends BaseResource implements LoadableResource {
+ * ...
+ * }
+ * }
+ *
+ * Key points:
+ *+ * Any class implementing {@code LoadableResource} is responsible for managing its own + * loading and unloading logic, such as reading files, initializing data structures, + * or allocating external resources. + *
+ * + *Implementing classes must define the following behaviors:
+ *Typical usage:
+ *{@code
+ * public class MyFontAsset extends BaseResource implements LoadableResource {
+ * private boolean loaded = false;
+ *
+ * @Override
+ * public void load() {
+ * // Load font file into memory
+ * loaded = true;
+ * }
+ *
+ * @Override
+ * public void unload() {
+ * // Release resources if needed
+ * loaded = false;
+ * }
+ *
+ * @Override
+ * public boolean isLoaded() {
+ * return loaded;
+ * }
+ * }
+ * }
+ *
+ * This interface is commonly used with {@link PreloadResource} to allow automatic + * loading by an {@link org.toop.framework.asset.AssetLoader} if desired.
+ */ public interface LoadableResource { + /** + * Load the resource into memory or initialize it. + * This method may throw runtime exceptions if loading fails. + */ void load(); + + /** + * Unload the resource and free any associated resources. + * After this call, {@link #isLoaded()} should return false. + */ void unload(); + + /** + * Check whether the resource has been successfully loaded. + * + * @return true if the resource is loaded and ready for use, false otherwise + */ boolean isLoaded(); } diff --git a/framework/src/main/java/org/toop/framework/asset/resources/PreloadResource.java b/framework/src/main/java/org/toop/framework/asset/resources/PreloadResource.java new file mode 100644 index 0000000..6458751 --- /dev/null +++ b/framework/src/main/java/org/toop/framework/asset/resources/PreloadResource.java @@ -0,0 +1,37 @@ +package org.toop.framework.asset.resources; + +/** + * Marker interface for resources that should be **automatically loaded** by the {@link org.toop.framework.asset.AssetLoader}. + * + *Extends {@link LoadableResource}, so any implementing class must provide the standard + * {@link LoadableResource#load()} and {@link LoadableResource#unload()} methods, as well as the + * {@link LoadableResource#isLoaded()} check.
+ * + *When a resource implements {@code PreloadResource}, the {@code AssetLoader} will invoke + * {@link LoadableResource#load()} automatically after the resource is discovered and instantiated, + * without requiring manual loading by the user.
+ * + *Typical usage:
+ *{@code
+ * public class MyFontAsset extends BaseResource implements PreloadResource {
+ * @Override
+ * public void load() {
+ * // load the font into memory
+ * }
+ *
+ * @Override
+ * public void unload() {
+ * // release resources if needed
+ * }
+ *
+ * @Override
+ * public boolean isLoaded() {
+ * return loaded;
+ * }
+ * }
+ * }
+ *
+ * Note: Only use this interface for resources that are safe to load at startup, as it may + * increase memory usage or startup time.
+ */ +public interface PreloadResource extends LoadableResource {}