diff --git a/pom.xml b/pom.xml
index 9b0cf5e..f2df86b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
+ * This abstract base class provides the core lifecycle, state management, and event handling for all
+ * renderable components within the GUI framework. It acts as a bridge between the engine's logical
+ * representations and the underlying JavaFX {@link Node}.
+ *
- * This is an abstract base class for all renderable elements in the GUI framework.
- * Elements are organized by their rendering index, which determines the order in which
- * they are drawn. A lower index means the element will be rendered earlier (underneath others).
+ * Elements are heavily dependent on their rendering index (Z-order). A lower index dictates that the
+ * element is drawn earlier, placing it underneath elements with a higher index.
*
- * An index of 1 will cause the element to be rendered first (at the bottom layer).
- * Higher index values will render the element on top of those with lower indices. An index of 0 results in automatic assignment.
+ * Modifying this value allows you to control the depth of the element on the screen. Higher index values
+ * will render the element on top of those with lower indices. Set to 0 to let the engine automatically
+ * assign the index based on insertion order.
*
+ * When disabled, the underlying JavaFX node will no longer process user inputs or events.
+ * If the underlying {@link Node} has not been assembled or set yet, this will log an error
+ * rather than crashing the application.
+ *
+ * This cache is typically populated when the engine invokes the {@link #assemble()} method.
+ * If the element has not been assembled yet, this method will return null.
+ *
- * For {@link Overlay}'s it will return the render functions. Examples: {@link TextOverlay#render()}, {@link ImageOverlay#render()}
+ * This automatically applies the cursor to the underlying JavaFX {@link Node} if it is currently cached.
*
- * For {@link Layout}'s it will return the {@link Layout#render()} result.
+ * This wraps the native JavaFX {@link MouseEvent#MOUSE_CLICKED} event into a framework-specific {@link ElementClickEvent}.
*
- * For {@link Container}'s it will return the {@link Container#build()} result.
+ * This wraps the native JavaFX {@link MouseEvent#MOUSE_RELEASED} event into a framework-specific {@link ElementClickReleaseEvent}.
*
+ * This wraps the native JavaFX {@link MouseEvent#MOUSE_ENTERED} event into a framework-specific {@link ElementHoverEvent}.
+ *
+ * This wraps the native JavaFX {@link MouseEvent#MOUSE_EXITED} event into a framework-specific {@link ElementExitEvent}.
+ *
+ * This method is called by the engine during the rendering phase to translate custom elements
+ * into the native scene graph.
+ *
+ * This class acts as the bridge between the engine's logical element hierarchy and the native
+ * JavaFX scene graph. It manages dimensions, styling, and nested elements via a z-index mapping.
+ * Subclasses like {@link Container} and {@link Layout} inherit this capability to manage
+ * their own distinct collections of children.
+ *
+ * Threading Constraint: This method immediately manipulates the live JavaFX scene graph
+ * and must be executed on the JavaFX Application Thread. If you are dispatching this
+ * from a background worker thread, you must handle the synchronization manually
+ * (e.g., via {@link javafx.application.Platform#runLater(Runnable)}).
+ *
+ * Threading Constraint: This is a mutating operation that recursively shifts existing
+ * elements and immediately pushes the newly compiled {@link Node} into the live view layer.
+ * To prevent an {@link IllegalStateException}, this must be invoked on the JavaFX
+ * Application Thread. You can find more info in the base {@link #addElement(Element)} function.
+ *
+ * Threading Constraint: Because this delegates to {@link #addElement(Element)},
+ * all view manipulations occur instantly. This method must be executed on the
+ * JavaFX Application Thread. You can find more info in the base {@link #addElement(Element)} function.
+ *
+ * Threading Constraint: Because this delegates to {@link #addElement(Element)},
+ * all view manipulations occur instantly. This method must be executed on the
+ * JavaFX Application Thread. You can find more info in the base {@link #addElement(Element)} function.
+ * Multiple windows can be created and rendered simultaneously. The window's title serves as its process name and label.
* The stage style dictates the window's appearance, with {@link StageStyle#DECORATED} providing standard
@@ -46,8 +48,8 @@
* }
*
*
- * To display elements within a window, a {@link Container} must first be created and added to the window.
- * Multiple containers can be added and positioned within a single window.
+ *
+ *
+ * @return The fully constructed JavaFX node ready for the scene graph.
*/
public abstract Node assemble();
-}
+}
\ No newline at end of file
diff --git a/src/main/java/me/piitex/engine/Renderer.java b/src/main/java/me/piitex/engine/Renderer.java
index 10593c6..136d7a4 100644
--- a/src/main/java/me/piitex/engine/Renderer.java
+++ b/src/main/java/me/piitex/engine/Renderer.java
@@ -1,5 +1,7 @@
package me.piitex.engine;
+import javafx.application.Platform;
+import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
@@ -10,11 +12,20 @@
import me.piitex.engine.hanlders.events.LayoutRenderEvent;
import me.piitex.engine.layouts.Layout;
import me.piitex.engine.overlays.Overlay;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import javax.annotation.Nullable;
import java.util.*;
/**
- * An element which handles rendering of {@link Container}, {@link Layout}, and {@link Overlay}
+ * An element which handles the core visual rendering and structural management of child {@link Element}s.
+ *
+ * {@code
+ *
+ * // In application thread
+ * Renderer view = ... // Obtain the renderer
+ * Element element = ... // Initialize the element
+ * view.addElement(element);
+ *
+ * // In background thread
+ * new Thread(() -> {
+ * Renderer view = ... // Obtain the renderer
+ * Element element = ... // Initialize the element
+ *
+ * // Synchronize the execution
+ * Platform.runLater(() -> {
+ * view.addElement(element);
+ * });
+ * })
+ * }
+ *
+ *
+ * @param element The {@link Element} component to append and render.
*/
public void addElement(Element element) {
int index = element.getIndex();
@@ -183,34 +386,60 @@ public void addElement(Element element) {
}
/**
- * Adds the element to the specific index. If there is an element already bound to that index it is shuffled forward.
+ * Injects an element into a specific z-index layout slot and forcefully renders it to the screen.
+ *
+ * {@code
+ * Renderer view = ...
+ * Element element = ...
*
- * @param element The {@link Element} to add to the container.
- * @param index The index/order of the element.
+ * // Inserts the element exactly at layer 5 and renders immediately
+ * view.addElement(element, 5);
+ * }
+ *
+ *
+ * @param element The {@link Element} to push and render.
+ * @param index The mandatory z-index layout order integer.
*/
public void addElement(Element element, int index) {
Element current = elements.get(index);
- if (current != null) {
+ if (current != null && current != element) {
int i = index + 1;
addElement(getElementAt(index), i);
+ elements.remove(index);
}
element.setIndex(index);
elements.put(index, element);
Node node = element.assemble();
- if (element instanceof Overlay overlay) {
- overlay.setNode(node);
- }
- if (element instanceof Renderer renderer) {
- renderer.setNode(node);
- }
+ element.setNode(node);
+
addToView(node, index);
}
/**
- * Adds an array of elements to the container. The elements are positioned by the order of the array.
- * The added elements will be indexed to the front of the container.
- * @param elements The array of {@link Element}s to be added.
+ * Batch processes an array of elements, compiling and rendering each one sequentially.
+ *
+ * {@code
+ * Element e1 = ...
+ * Element e2 = ...
+ *
+ * // Renders both elements sequentially
+ * view.addElements(e1, e2);
+ * }
+ *
+ *
+ * @param elements The arbitrary array of {@link Element} components to append and render.
*/
public void addElements(Element... elements) {
for (Element element : elements) {
@@ -219,8 +448,24 @@ public void addElements(Element... elements) {
}
/**
- * Adds a {@link LinkedList
+ * {@code
+ * LinkedList
+ *
+ * @param elements The {@link LinkedList} of child elements to add and render.
*/
public void addElements(LinkedList
{@code
* Window window = application.getWindow();
* Container container = new EmptyContainer(x, y, width, height);
@@ -56,17 +58,17 @@
*
*
* All GUI-related functions, especially those involving scene graph modifications,
- * must be executed on the JavaFX Application Thread.
+ * must be executed on the JavaFX Application Thread. This example uses a native {@link Thread} for simplicity.
* {@code
- * new Thread( () -> {
- * // Code to be ran asynchronously
- * loadBackend();
+ * new Thread( () -> {
+ * // Code to be executed asynchronously
+ * loadBackend();
*
- * Platform.runLater( () -> {
- * // Any gui related code.
- * initializeProgressIndicator();
- * })
- * })
+ * Platform.runLater( () -> {
+ * // Any gui related code.
+ * initializeProgressIndicator();
+ * })
+ * });
* }
*
* @see Container
@@ -96,13 +98,18 @@ public class Window {
private static final Logger logger = LoggerFactory.getLogger(Window.class);
+ // -------- OS Specific Values ----------
+ // JavaFX does not take into account the window title bar height.
+ // This causes alignment issues with various operating systems.
+ private static final int LINUX_WINDOW_HEIGHT = 35;
+ private static final int WIN_WINDOW_HEIGHT = 40;
+ private static final int MAC_WINDOW_HEIGHT = 40;
+
+
/**
* Constructs a Window instance using properties defined in a {@link WindowBuilder}.
* This allows for a flexible and readable way to configure window properties.
*
- * Common styles include {@link StageStyle#DECORATED}, which provides standard
- * window controls, and {@link StageStyle#UNDECORATED} for a borderless window.
- *
* Example usage:
* {@code
* WindowBuilder builder = new WindowBuilder()
@@ -139,7 +146,9 @@ public Window(WindowBuilder builder) {
/**
* Initializes the JavaFX Stage with the configured properties from the `WindowBuilder`.
- * This method sets up the title, style, dimensions, icon, and initial scene.
+ * This method applies the title, style, dimensions, icon, scaling behaviors,
+ * anti-aliasing preferences, and initializes the root scene. Handles OS-specific
+ * window height discrepancies internally.
*/
protected void buildStage() {
stage = new Stage();
@@ -153,10 +162,21 @@ protected void buildStage() {
stage.setTitle(title);
stage.initStyle(stageStyle);
stage.setWidth(width);
- stage.setHeight(height);
+
+ // Linux, Windows, and Mac handle sizing of windows differently.
+ // With the top control bar enabled, the height will be off.
+
+ if (OSUtil.getOS().toLowerCase().contains("linux")) {
+ stage.setHeight(height + LINUX_WINDOW_HEIGHT);
+ } else if (OSUtil.getOS().toLowerCase().contains("window")) {
+ stage.setHeight(height + WIN_WINDOW_HEIGHT);
+ } else if (OSUtil.getOS().toLowerCase().contains("mac")) {
+ stage.setHeight(height + MAC_WINDOW_HEIGHT);
+ } else {
+ stage.setHeight(height);
+ }
stage.setMaximized(maximized);
stage.setFullScreen(fullscreen);
-
root.setPrefSize(width, height);
if (scale) {
@@ -172,13 +192,16 @@ protected void buildStage() {
handleWindowScaling(stage);
scene = new Scene(root);
-
stage.setScene(scene);
+
+ if (backgroundColor != null) {
+ updateBackground(backgroundColor);
+ }
}
/**
* Updates the background color of the window's root pane and scene.
- * @param color The new background color.
+ * @param color The new background color to apply.
*/
public void updateBackground(Color color) {
this.backgroundColor = color;
@@ -209,6 +232,7 @@ public Stage getStage() {
public Scene getScene() {
return scene;
}
+
/**
* Retrieves the root Pane of the window's scene graph.
* @return The root Pane.
@@ -218,13 +242,18 @@ public Pane getRoot() {
}
/**
- * Retrieves the configured width of the window.
+ * Retrieves the currently configured width of the window.
* @return The window width.
*/
public double getWidth() {
return width;
}
+ /**
+ * Sets a new width for the window. Updates the initial width tracker and clears any existing
+ * scaling transformations from the root pane.
+ * @param width The new width in pixels.
+ */
public void setWidth(double width) {
this.width = width;
this.initialWidth = width;
@@ -232,6 +261,11 @@ public void setWidth(double width) {
root.getTransforms().clear();
}
+ /**
+ * Sets a new height for the window. Updates the initial height tracker and clears any existing
+ * scaling transformations from the root pane.
+ * @param height The new height in pixels.
+ */
public void setHeight(double height) {
this.height = height;
this.initialHeight = height;
@@ -240,23 +274,34 @@ public void setHeight(double height) {
}
/**
- * Retrieves the configured height of the window.
+ * Retrieves the currently configured height of the window.
* @return The window height.
*/
public double getHeight() {
return height;
}
+ /**
+ * Calculates the horizontal scale factor by dividing the current width by the initial width.
+ * Useful for dynamic resizing and responsive UI adjustments.
+ * @return The horizontal scaling multiplier.
+ */
public double getWidthScale() {
return width / initialWidth;
}
+ /**
+ * Calculates the vertical scale factor by dividing the current height by the initial height.
+ * Useful for dynamic resizing and responsive UI adjustments.
+ * @return The vertical scaling multiplier.
+ */
public double getHeightScale() {
return height / initialHeight;
}
/**
* Toggles the window between full-screen and windowed modes.
+ * Reverts to the explicitly set width and height if exiting full-screen mode.
* @param fullscreen True to set to full-screen, false for windowed.
*/
public void setFullscreen(boolean fullscreen) {
@@ -272,6 +317,7 @@ public void setFullscreen(boolean fullscreen) {
/**
* Toggles the window between maximized and normal states.
+ * Reverts to the explicitly set width and height if exiting the maximized state.
* @param maximized True to maximize the window, false for normal size.
*/
public void setMaximized(boolean maximized) {
@@ -286,23 +332,28 @@ public void setMaximized(boolean maximized) {
}
/**
- * Adds a {@link Container} to the window using its intrinsic index.
+ * Adds a {@link Container} to the window using its intrinsically defined index.
* @param container The container to add.
*/
public void addContainer(Container container) {
addContainer(container, container.getIndex());
}
+ /**
+ * Adds a {@link Container} to the window using its intrinsic index, but associates it
+ * with a pre-compiled JavaFX Node. Useful for performance optimizations.
+ * @param container The container metadata and reference.
+ * @param node The pre-compiled JavaFX Node to render.
+ */
public void addContainer(Container container, Node node) {
addContainer(container, node, container.getIndex());
}
-
/**
* Adds a {@link Container} to the window at a specific index. If a container already exists at the given index,
- * it attempts to shift existing containers to accommodate the new one.
+ * it recursively shifts existing containers up one index to accommodate the new one.
* @param container The container to add.
- * @param index The desired rendering index for the container.
+ * @param index The desired rendering index (z-order) for the container.
*/
public void addContainer(Container container, int index) {
Container current = containers.get(index);
@@ -314,7 +365,13 @@ public void addContainer(Container container, int index) {
containers.put(index, container);
container.setWindow(this); // Store window reference.
- Node assemble = container.assemble();
+ Node assemble;
+ // Check for cached node.
+ if (container.getNode() != null) {
+ assemble = container.getNode();
+ } else {
+ assemble = container.assemble();
+ }
if (index > 0) {
if (root.getChildren().size() < index) {
@@ -328,10 +385,30 @@ public void addContainer(Container container, int index) {
}
/**
- * Adds a pre-compiled {@link Container} to the window. Use {@link Container#assemble()} to build the {@link Node}.
- * @param container The container to add.
- * @param node The pre-compiled node to add.
- * @param index The desired rendering index for the container.
+ * Adds a pre-compiled {@link Container} to the window at a specific index.
+ * Use {@link Container#assemble()} to build the {@link Node}. Nodes are automatically
+ * assembled when the base container is drawn to the screen.
+ * If a Container is large or executes a long task, it might freeze or lock the UI.
+ * You can assemble the Container asynchronously to prevent UI freezing.
+ *
+ *
+ * {@code
+ * Container container = new EmptyContainer(100, 100);
+ * // Add elements to the container.
+ *
+ * runTaskAsynchronously(() -> {
+ * Node assemble = container.assemble();
+ * Platform.runLater(() -> {
+ * window.addContainer(container, assemble, 0);
+ * });
+ * });
+ * // Display a loading view which can be removed in the task above.
+ * }
+ *
+ *
+ * @param container The container context to track.
+ * @param node The pre-compiled node to visually add.
+ * @param index The desired rendering index (z-order) for the container.
*/
public void addContainer(Container container, Node node, int index) {
Container current = containers.get(index);
@@ -345,8 +422,8 @@ public void addContainer(Container container, Node node, int index) {
}
/**
- * Adds all containers from the given TreeMap to this window's container collection.
- * Existing containers with matching indices will be overwritten.
+ * Adds a collection of containers from the given TreeMap to this window.
+ * Existing containers with matching indices will be overwritten in the internal map.
* @param con The TreeMap of containers to add.
*/
public void addContainers(TreeMap con) {
@@ -355,15 +432,15 @@ public void addContainers(TreeMap con) {
/**
* Replaces the entire set of containers in the window with a new TreeMap of containers.
- * @param containers The new TreeMap of containers.
+ * @param containers The new TreeMap of containers to track.
*/
public void setContainers(TreeMap containers) {
this.containers = containers;
}
/**
- * Replaces an old container instance with a new container instance, preserving its original index.
- * The old container must already exist in the window's collection.
+ * Replaces an existing container instance with a new container instance, preserving its original index.
+ * The old container must already exist in the window's collection for the replacement to occur.
* @param oldContainer The container instance to be replaced.
* @param newContainer The new container instance to take its place.
*/
@@ -375,7 +452,7 @@ public void replaceContainer(Container oldContainer, Container newContainer) {
/**
* Replaces the container at a specific index with a new container.
- * The window is then re-rendered to reflect this change.
+ * The window is then re-rendered to reflect this visual change.
* @param index The index at which to replace the container.
* @param container The new container to place at the specified index.
*/
@@ -387,9 +464,9 @@ public void replaceContainer(int index, Container container) {
/**
* Removes a specific {@link Container} instance from the window's collection.
- * Note: This only removes the container from the internal map,
- * it does not automatically remove its corresponding JavaFX Node from the scene graph.
- * A subsequent `render()` call would be needed to update the display.
+ * Note: This removes the container from the internal map and its corresponding JavaFX
+ * Node from the root's children, but a subsequent `render()` call might be needed to
+ * ensure structural consistency depending on execution context.
* @param container The container instance to remove.
*/
public void removeContainer(Container container) {
@@ -409,8 +486,7 @@ public void removeContainer(Container container) {
}
/**
- * Clears all containers from the window.
- * A garbage collection hint is provided to the JVM.
+ * Clears all containers currently tracked by the window and removes them from the view.
*/
public void clearContainers() {
new LinkedList<>(containers.values()).forEach(this::removeContainer);
@@ -418,7 +494,7 @@ public void clearContainers() {
}
/**
- * Removes the container at a specific index from the window and re-renders the display.
+ * Removes the container at a specific index from the window and triggers a re-render.
* @param index The index of the container to remove.
*/
public void clearContainer(int index) {
@@ -427,7 +503,7 @@ public void clearContainer(int index) {
}
/**
- * Retrieves the TreeMap of all containers currently managed by the window.
+ * Retrieves the TreeMap of all containers currently managed by the window, ordered by index.
* @return A TreeMap mapping container indices to Container objects.
*/
public TreeMap getContainers() {
@@ -435,17 +511,7 @@ public TreeMap getContainers() {
}
/**
- * Clears all child nodes from the root pane and re-sets the scene's root.
- * The stage is then shown.
- */
- public void clean() {
- root.getChildren().clear();
- scene.setRoot(root);
- stage.show();
- }
-
- /**
- * Clears all containers and resets the window's root pane and scene.
+ * Clears all containers and resets the window's root pane and scene entirely.
* The stage is not automatically shown after this operation.
*/
public void clear() {
@@ -453,8 +519,8 @@ public void clear() {
}
/**
- * Clears all containers and resets the window's root pane and scene.
- * Optionally shows the stage after clearing.
+ * Clears all containers and resets the window's root pane and scene entirely.
+ * Optionally shows the stage after the clearance process is complete.
* @param render True to show the stage after clearing, false otherwise.
*/
public void clear(boolean render) {
@@ -470,7 +536,7 @@ public void clear(boolean render) {
/**
* Closes the JavaFX Stage associated with this window.
- * A garbage collection hint is provided to the JVM.
+ * @param handleEvent If false, unbinds the window's hidden and close request event listeners before closing.
*/
public void close(boolean handleEvent) {
if (stage != null) {
@@ -483,23 +549,15 @@ public void close(boolean handleEvent) {
}
/**
- * Clears the root pane's children, creates a new Stage, and hides it.
- * This method essentially resets the visual state of the window without closing it.
+ * Resets the visual state of the window by creating a new Stage with the original configuration parameters.
*/
public void resetStage() {
buildStage();
}
- /**
- * Shows the window's stage.
- * Note: This method is named "hide" but performs "show". This might be a naming inconsistency.
- */
- public void hide() {
- stage.show();
- }
/**
- * Builds the JavaFX Stage and then renders all active nodes on the screen.
+ * Fully constructs the JavaFX Stage structure and renders all active nodes onto the screen.
*/
public void buildAndRender() {
buildStage();
@@ -507,10 +565,11 @@ public void buildAndRender() {
}
/**
- * Builds and displays all active nodes on the screen. This function translates the engine's API into JavaFX and updates the stage and scene.
+ * Builds and displays all active nodes on the screen. This function translates the engine's API
+ * into JavaFX nodes and updates the stage and scene. Focus is requested if the window is configured for it.
*
* Calling this excessively can cause visual flicker. It must be called after adding,
- * modifying, or removing {@link Overlay} or {@link Container} to update the display.
+ * modifying, or removing {@link Overlay} or {@link Container} objects to update the display state.
*
*/
public void render() {
@@ -522,8 +581,9 @@ public void render() {
}
/**
- * Builds the engine's API onto the JavaFX framework without displaying the built nodes on the screen.
- * For most use cases, {@link #render()} is recommended as it also shows the updated display.
+ * Translates the engine's container definitions into JavaFX nodes without immediately showing
+ * the stage, provided there is at least one active container.
+ * For most use cases, {@link #render()} is recommended.
*/
public void build() {
if (!containers.isEmpty()) {
@@ -532,9 +592,10 @@ public void build() {
}
/**
- * Builds the engine's API onto the JavaFX framework, optionally resetting the scene.
- * This method processes and prepares containers for display but does not automatically show them.
- * @param reset True to reset the scene (clears and initialises the root pane and scene), false to only clear children.
+ * Iterates through active containers and applies them to the JavaFX root pane.
+ * This method processes and prepares containers for display.
+ * @param reset If true, fully reinitializes the root pane and scene graph (can cause flickering, but clears stale cache).
+ * If false, simply clears existing children and stylesheets before reapplying containers.
*/
public void build(boolean reset) {
root.getChildren().clear();
@@ -551,9 +612,9 @@ public void build(boolean reset) {
}
/**
- * Renders a specific container by adding it on top of the current window's content.
- * The container is automatically assigned an index that places it at the highest layer.
- * @param container The container to render.
+ * Adds a specific container on top of the current window's content stack.
+ * The container is dynamically assigned an index representing the highest available layer.
+ * @param container The container to assign and render.
*/
public void render(Container container) {
int index = containers.isEmpty() ? 1 : containers.lastKey() + 1;
@@ -563,9 +624,9 @@ public void render(Container container) {
}
/**
- * Renders a single {@link Container} instance by building its corresponding JavaFX Node
- * and adding it to the window's root pane.
- * @param container The container to render.
+ * Assembles a single {@link Container} instance into a JavaFX Node and appends it to the window's root pane.
+ * Removes the previous node instance from the root if it existed.
+ * @param container The container to translate and render.
*/
private void renderContainer(Container container) {
if (container.getNode() != null) {
@@ -576,11 +637,9 @@ private void renderContainer(Container container) {
}
/**
- * Renders a popup container, ensuring that only one popup is active at a time.
- * If a previous popup exists, its Node is removed from the scene graph before the new one is added.
- * This method does not apply translation (X, Y) from the container's properties directly to the node,
- * assuming these are handled by the calling `renderPopup` method.
- * @param container The container to render as a popup.
+ * Internal handler to render a container explicitly as a singular active popup.
+ * Removes the currently tracked popup container before registering the new one.
+ * @param container The container functioning as the popup layout.
*/
private void renderPopupContainer(Container container) {
if (currentPopup != null) {
@@ -591,16 +650,27 @@ private void renderPopupContainer(Container container) {
addContainer(container);
}
+ /**
+ * Computes positioning and renders a basic popup overlay dynamically onto the screen.
+ * @param overlay The {@link Overlay} content to embed inside the popup.
+ * @param position A predefined {@link PopupPosition} layout value.
+ * @param width The targeted width of the popup frame.
+ * @param height The targeted height of the popup frame.
+ * @param autoClose If true, the popup automatically dismisses itself after a set duration.
+ */
public void renderPopup(Overlay overlay, PopupPosition position, double width, double height, boolean autoClose) {
renderPopup(overlay, position, width, height, autoClose, null);
}
/**
- * Renders a popup with an overlay content, and desired position.
- * This method calculates the popup's position based on window dimensions and scaling,
- * then creates and renders a {@link Container} to house the overlay.
- * @param overlay The {@link Overlay} content to display within the popup.
- * @param position The desired position of the popup on the screen.
+ * Computes the X and Y coordinate logic for a popup based on the window's dimensions and scaling parameters,
+ * then delegates the drawing instructions to absolute coordinate rendering.
+ * @param overlay The {@link Overlay} logic.
+ * @param position The geographical screen layout {@link PopupPosition} logic.
+ * @param width Desired popup width.
+ * @param height Desired popup height.
+ * @param autoClose True if the popup should auto-dismiss on a timer.
+ * @param label An optional {@link TextOverlay} to accompany the popup.
*/
public void renderPopup(Overlay overlay, PopupPosition position, double width, double height, boolean autoClose, TextOverlay label) {
// Get current window dimensions (these are in the logical, unscaled coordinate system)
@@ -648,6 +718,17 @@ public void renderPopup(Overlay overlay, PopupPosition position, double width, d
renderPopup(overlay, calculatedX, calculatedY, width, height, autoClose, label);
}
+ /**
+ * Encapsulates an overlay into a dynamically constructed {@link EmptyContainer} positioned at exact coordinates,
+ * registering it as the active top-level popup element. Assigns lifecycle event hooks based on overlay types.
+ * @param overlay The {@link Overlay} element to draw.
+ * @param x The specific scaled horizontal X coordinate.
+ * @param y The specific scaled vertical Y coordinate.
+ * @param width The overall width boundary of the popup constraint container.
+ * @param height The overall height boundary of the popup constraint container.
+ * @param autoClose True to queue a removal thread via JavaFX timeline after 10,000 milliseconds.
+ * @param label Optional text overlay addition.
+ */
public void renderPopup(Overlay overlay, double x, double y, double width, double height, boolean autoClose, TextOverlay label) {
EmptyContainer container = new EmptyContainer(x, y, width, height);
container.setPrefSize(width, height);
@@ -694,6 +775,14 @@ public void renderPopup(Overlay overlay, double x, double y, double width, doubl
addContainer(container);
}
+ /**
+ * Evaluates a pre-existing container directly into the application space as an exclusive top-level popup at specific coordinates.
+ * @param container The assembled container logic to set as active.
+ * @param x Translated X coordinate.
+ * @param y Translated Y coordinate.
+ * @param width Bounding box width.
+ * @param height Bounding box height.
+ */
public void renderPopup(Container container, double x, double y, double width, double height) {
if (currentPopup != null) {
removeContainer(currentPopup);
@@ -709,6 +798,14 @@ public void renderPopup(Container container, double x, double y, double width, d
addContainer(container);
}
+ /**
+ * Handles complex inverse scaling math and layout configuration to translate a requested predefined {@link PopupPosition}
+ * onto exact coordinate mappings, then delegates the raw drawing instructions to the coordinate popup renderer.
+ * @param container The container acting as a custom popup view.
+ * @param position The conceptual mapping for the popup on screen.
+ * @param width The target display width.
+ * @param height The target display height.
+ */
public void renderPopup(Container container, PopupPosition position, double width, double height) {
double windowWidth = this.width;
double windowHeight = this.height;
@@ -750,23 +847,46 @@ public void renderPopup(Container container, PopupPosition position, double widt
}
+ /**
+ * Halts application workflow to trigger and display a system-level Alert dialog.
+ * @param alertOverlay The overlay containing the configured {@link Alert} structure.
+ */
public void renderAlert(AlertOverlay alertOverlay) {
Alert alert = alertOverlay.getAlert();
alert.showAndWait();
}
+ /**
+ * Retrieves the instance of the current active popup container traversing the engine window layer.
+ * @return The currently displaying popup {@link Container}, or null if none exist.
+ */
public Container getCurrentPopup() {
return currentPopup;
}
+ /**
+ * Injects a custom window resize handler logic into the application window lifecycle.
+ * Overrides any previously configured resize hooks.
+ * @param windowResize The implementation defining custom resize handling logic.
+ */
public void onWindowResize(IWindowResize windowResize) {
this.windowResize = windowResize;
}
+ /**
+ * Retrieves the custom window resize handler currently bound to the window.
+ * @return The active {@link IWindowResize} implementation, or null if unassigned.
+ */
public IWindowResize getWindowResize() {
return windowResize;
}
+ /**
+ * Listens to the core JavaFX Stage dimensions to maintain structural integrity.
+ * If the scaling config is set to true, this dynamically applies affine transforms directly to the root pane
+ * to stretch the application content smoothly. Otherwise, it delegates resolution changes to a custom event dispatcher.
+ * @param stage The primary JavaFX Stage generating width/height property changes.
+ */
private void handleWindowScaling(Stage stage) {
// This scales the application to the desired width and height that it is running at.
stage.heightProperty().addListener((observable, oldValue, newValue) -> {
diff --git a/src/main/java/me/piitex/engine/WindowBuilder.java b/src/main/java/me/piitex/engine/WindowBuilder.java
index e1433ce..728141f 100644
--- a/src/main/java/me/piitex/engine/WindowBuilder.java
+++ b/src/main/java/me/piitex/engine/WindowBuilder.java
@@ -7,13 +7,13 @@
/**
* A builder class for constructing Window objects with various configuration options.
- * This provides a more intuitive and flexible way to create Window instances
- * compared to using multiple constructors.
+ * This provides a more intuitive and flexible way to create {@link Window} instances
+ * compared to using multiple constructors with varying parameters.
*
* {@code
- * Window window = new WindowBuild("My Game")
+ * Window window = new WindowBuilder("My Game")
* .setStageStyle(StageStyle.UNDECORATED)
- * setRoot(new Pane())
+ * .setRoot(new Pane())
* .setDimensions(1280, 720)
* .setBackgroundColor(Color.DARKBLUE)
* .setFullscreen(true)
@@ -38,7 +38,7 @@ public class WindowBuilder {
/**
* Starts the building process for a new Window with a required title.
*
- * @param title The title of the window.
+ * @param title The process title and visible label of the window.
*/
public WindowBuilder(String title) {
this.title = title;
@@ -47,7 +47,7 @@ public WindowBuilder(String title) {
/**
* Sets the style of the window.
* @param stageStyle The {@link StageStyle} for the window (e.g., DECORATED, UNDECORATED).
- * @return The current WindowBuild instance for chaining.
+ * @return The current WindowBuilder instance for method chaining.
*/
public WindowBuilder setStageStyle(StageStyle stageStyle) {
this.stageStyle = stageStyle;
@@ -55,9 +55,9 @@ public WindowBuilder setStageStyle(StageStyle stageStyle) {
}
/**
- * Sets the scene root pane.
- * @param root The pane type for the root.
- * @return The current WindowBuild instance for chaining.
+ * Sets the scene root pane. This is the underlying base pane for the JavaFX scene.
+ * @param root The explicit {@link Pane} type to be used as the root.
+ * @return The current WindowBuilder instance for method chaining.
*/
public WindowBuilder setRoot(Pane root) {
this.root = root;
@@ -65,9 +65,9 @@ public WindowBuilder setRoot(Pane root) {
}
/**
- * Sets the icon for the window.
- * @param icon An {@link ImageLoader} for the window's icon.
- * @return The current WindowBuild instance for chaining.
+ * Sets the window's taskbar and title bar icon.
+ * @param icon An {@link ImageLoader} instance mapping to the targeted window icon.
+ * @return The current WindowBuilder instance for method chaining.
*/
public WindowBuilder setIcon(ImageLoader icon) {
this.icon = icon;
@@ -75,10 +75,10 @@ public WindowBuilder setIcon(ImageLoader icon) {
}
/**
- * Sets the preferred width and height of the window.
- * @param width The width of the window.
- * @param height The height of the window.
- * @return The current WindowBuild instance for chaining.
+ * Sets the preferred width and height of the window upon initial rendering.
+ * @param width The target width in pixels.
+ * @param height The target height in pixels.
+ * @return The current WindowBuilder instance for method chaining.
*/
public WindowBuilder setDimensions(double width, double height) {
this.width = width;
@@ -87,9 +87,9 @@ public WindowBuilder setDimensions(double width, double height) {
}
/**
- * Sets the background color of the window's root pane.
- * @param backgroundColor The {@link Color} for the window's background.
- * @return The current WindowBuild instance for chaining.
+ * Sets the background color fill of the window's root pane and underlying scene.
+ * @param backgroundColor The JavaFX {@link Color} for the window's background.
+ * @return The current WindowBuilder instance for method chaining.
*/
public WindowBuilder setBackgroundColor(Color backgroundColor) {
this.backgroundColor = backgroundColor;
@@ -97,9 +97,9 @@ public WindowBuilder setBackgroundColor(Color backgroundColor) {
}
/**
- * Sets whether the window should be in fullscreen mode.
- * @param fullscreen True for fullscreen, false otherwise.
- * @return The current WindowBuild instance for chaining.
+ * Sets whether the window should launch and maintain a borderless fullscreen mode.
+ * @param fullscreen True to enable fullscreen, false otherwise.
+ * @return The current WindowBuilder instance for method chaining.
*/
public WindowBuilder setFullscreen(boolean fullscreen) {
this.fullscreen = fullscreen;
@@ -107,9 +107,9 @@ public WindowBuilder setFullscreen(boolean fullscreen) {
}
/**
- * Sets whether the window should be maximized on launch.
- * @param maximized True to maximize, false otherwise.
- * @return The current WindowBuild instance for chaining.
+ * Sets whether the window should launch already maximized across the user's primary display.
+ * @param maximized True to start maximized, false otherwise.
+ * @return The current WindowBuilder instance for method chaining.
*/
public WindowBuilder setMaximized(boolean maximized) {
this.maximized = maximized;
@@ -117,78 +117,126 @@ public WindowBuilder setMaximized(boolean maximized) {
}
/**
- * Sets whether the window should be focused on launch.
- * @param focused True to focus, false otherwise.
- * @return The current WindowBuild instance for chaining.
+ * Sets whether the window will explicitly request OS focus to be brought to the front on launch.
+ * @param focused True to aggressively request focus, false otherwise.
+ * @return The current WindowBuilder instance for method chaining.
*/
public WindowBuilder setFocused(boolean focused) {
this.focused = focused;
return this;
}
+ /**
+ * Dictates whether the window's content automatically uses affine transformations
+ * to scale logically when the user resizes the window bounds.
+ * @param scale True to auto-scale components, false to handle resizing manually.
+ * @return The current WindowBuilder instance for method chaining.
+ */
public WindowBuilder setScale(boolean scale) {
this.scale = scale;
return this;
}
+ /**
+ * Toggles default JavaFX text and node anti-aliasing logic on or off.
+ * Turning this off can be useful for pixel-art style desktop applications.
+ * @param aliasing True to retain smoothing, false to enforce hard pixel edges.
+ * @return The current WindowBuilder instance for method chaining.
+ */
public WindowBuilder setAntiAliasing(boolean aliasing) {
this.antialiasing = aliasing;
return this;
}
/**
- * Constructs and returns a new {@link Window} object based on the builder's configurations.
- * @return A new Window instance.
+ * Constructs and returns a fully initialized {@link Window} object based on the builder's stored configurations.
+ * @return A new runtime-ready Window instance.
*/
public Window build() {
- return new Window(this); // Calls the private constructor in the Window class
+ return new Window(this); // Calls the package-protected/public constructor in the Window class
}
+ /**
+ * @return The configured window title.
+ */
public String getTitle() {
return title;
}
+ /**
+ * @return The configured stage style.
+ */
public StageStyle getStageStyle() {
return stageStyle;
}
+ /**
+ * @return The configured root pane instance.
+ */
public Pane getRoot() {
return root;
}
+ /**
+ * @return The configured image loader logic for the window icon.
+ */
public ImageLoader getIcon() {
return icon;
}
+ /**
+ * @return The configured launch width.
+ */
public double getWidth() {
return width;
}
+ /**
+ * @return The configured launch height.
+ */
public double getHeight() {
return height;
}
+ /**
+ * @return The configured background color fill.
+ */
public Color getBackgroundColor() {
return backgroundColor;
}
+ /**
+ * @return True if fullscreen is enabled, false otherwise.
+ */
public boolean isFullscreen() {
return fullscreen;
}
+ /**
+ * @return True if starting maximized, false otherwise.
+ */
public boolean isMaximized() {
return maximized;
}
+ /**
+ * @return True if the window should steal focus upon loading.
+ */
public boolean isFocused() {
return focused;
}
+ /**
+ * @return True if affine scaling transformations are enabled.
+ */
public boolean isScale() {
return scale;
}
+ /**
+ * @return True if JavaFX text and node anti-aliasing should be retained.
+ */
public boolean isAntialiasing() {
return antialiasing;
}
-}
+}
\ No newline at end of file
diff --git a/src/main/java/me/piitex/engine/containers/BorderContainer.java b/src/main/java/me/piitex/engine/containers/BorderContainer.java
index ea5491a..f5f2447 100644
--- a/src/main/java/me/piitex/engine/containers/BorderContainer.java
+++ b/src/main/java/me/piitex/engine/containers/BorderContainer.java
@@ -8,7 +8,7 @@
public class BorderContainer extends Container {
private final BorderPane pane;
- private Element top, bottom, right, left, center, border;
+ private Element top, bottom, right, left, center;
public BorderContainer(double x, double y, double width, double height, int index) {
BorderPane tempPane = new BorderPane();
@@ -38,6 +38,7 @@ public Element getTop() {
public void setTop(Element top) {
this.top = top;
+ pane.setTop(top.assemble());
}
public Element getBottom() {
@@ -46,6 +47,7 @@ public Element getBottom() {
public void setBottom(Element bottom) {
this.bottom = bottom;
+ pane.setBottom(bottom.assemble());
}
public Element getRight() {
@@ -54,6 +56,7 @@ public Element getRight() {
public void setRight(Element right) {
this.right = right;
+ pane.setRight(right.assemble());
}
public Element getLeft() {
@@ -62,6 +65,7 @@ public Element getLeft() {
public void setLeft(Element left) {
this.left = left;
+ pane.setLeft(left.assemble());
}
public Element getCenter() {
@@ -70,14 +74,7 @@ public Element getCenter() {
public void setCenter(Element center) {
this.center = center;
- }
-
- public Element getBorder() {
- return border;
- }
-
- public void setBorder(Element border) {
- this.border = border;
+ pane.setCenter(center.assemble());
}
@Override
@@ -105,31 +102,8 @@ public Node build() {
pane.setMaxHeight(getMaxHeight());
}
- if (top != null) {
- pane.setTop(top.assemble());
- }
- if (bottom != null) {
- pane.setBottom(bottom.assemble());
- }
- if (right != null) {
- pane.setRight(right.assemble());
- }
- if (left != null) {
- pane.setLeft(left.assemble());
- }
- if (center != null) {
- pane.setCenter(center.assemble());
- }
- if (border != null) {
- pane.setBottom(border.assemble());
- }
-
setStyling(pane);
- if (getOnClick() != null) {
- pane.setOnMouseClicked(event -> getOnClick().onClick(new ContainerClickEvent(this)));
- }
-
return pane;
}
}
diff --git a/src/main/java/me/piitex/engine/containers/CardContainer.java b/src/main/java/me/piitex/engine/containers/CardContainer.java
index 2646a54..793d3d0 100644
--- a/src/main/java/me/piitex/engine/containers/CardContainer.java
+++ b/src/main/java/me/piitex/engine/containers/CardContainer.java
@@ -16,8 +16,6 @@ public class CardContainer extends Container {
private Element footer;
public CardContainer(double x, double y, double width, double height) {
- // Java 25 Update: You can now call code before the super() method
- // I think this is slightly better than confusing casting.
Card card = new Card();
this.atlantafxCard = card;
super(card, x, y, width, height);
@@ -34,7 +32,7 @@ public CardContainer(double width, double height) {
*/
@Override
public void addElements(Element... elements) {
- return;
+ throw new UnsupportedOperationException("Method is not supported with this container type!");
}
/**
@@ -42,7 +40,7 @@ public void addElements(Element... elements) {
*/
@Override
public void addElements(LinkedList elements) {
- return;
+ throw new UnsupportedOperationException("Method is not supported with this container type!");
}
/**
@@ -50,7 +48,7 @@ public void addElements(LinkedList elements) {
*/
@Override
public void addElement(Element element) {
- return;
+ throw new UnsupportedOperationException("Method is not supported with this container type!");
}
/**
@@ -58,7 +56,7 @@ public void addElement(Element element) {
*/
@Override
public void addElement(Element element, int index) {
- return;
+ throw new UnsupportedOperationException("Method is not supported with this container type!");
}
public void setHeader(Element element) {
@@ -128,12 +126,6 @@ public Node build() {
atlantafxCard.setMaxHeight(getMaxHeight());
}
- if (getOnClick() != null) {
- atlantafxCard.setOnMouseClicked(mouseEvent -> {
- getOnClick().onClick(new ContainerClickEvent(this));
- });
- }
-
atlantafxCard.getStyleClass().addAll(getStyles());
diff --git a/src/main/java/me/piitex/engine/containers/Container.java b/src/main/java/me/piitex/engine/containers/Container.java
index 058a633..8401df4 100644
--- a/src/main/java/me/piitex/engine/containers/Container.java
+++ b/src/main/java/me/piitex/engine/containers/Container.java
@@ -3,7 +3,6 @@
import javafx.scene.Node;
import me.piitex.engine.Renderer;
import me.piitex.engine.Window;
-import me.piitex.engine.containers.handlers.IContainerClick;
import me.piitex.engine.containers.handlers.IContainerRender;
import me.piitex.engine.layouts.Layout;
import me.piitex.engine.overlays.Overlay;
@@ -13,49 +12,80 @@
import java.util.*;
/**
- * The container houses all the elements that render onto the {@link Window}. The class can be extended to support different containers that can handle rendering differently.
+ * The container houses all the generic {@link me.piitex.engine.Element} items (like Overlays and Layouts)
+ * that are meant to render onto the parent {@link Window}. The class can be extended
+ * to support different containers that handle rendering in unique structural ways.
* The default base container is the {@link EmptyContainer}. It does not have any special rendering properties.
*
* {@code
- * EmptyContainer container = new EmptyContainer(double width, double height);
- * // Add elements to the container.
+ * EmptyContainer container = new EmptyContainer(double width, double height);
+ * // Add elements to the container.
* }
*
*
- * The {@link Window} will have to render and handle the container. The two components work in unison.
- *
+ * The {@link Window} will render and handle the container itself. The window specifically tracks Containers,
+ * which in turn track Elements. The two components work in unison to display the scene graph.
+ *
*
* {@code
- * Container container = new EmptyContainer(1920, 1080);
+ * Container container = new EmptyContainer(1920, 1080);
+ * window.addContainer(container); // Automaticallys draws to screen
*
- * window.addContainer(container);
+ * // Add elements directly to the container
+ * TextOverlay text = new TextOverlay("Overlay");
+ * container.addElement(text);
* }
*
*/
public abstract class Container extends Renderer {
private double x, y;
private final List stylesheets = new ArrayList<>();
- private IContainerClick click;
private final List renderEvents = new LinkedList<>();
+ /**
+ * Constructs a new Container with specific positioning and dimensions.
+ * Inherited properties such as width and height are managed by the parent {@link Renderer}.
+ *
+ * @param view The base JavaFX Node that represents this container visually.
+ * @param x The initial horizontal (X) position relative to the parent window or root pane.
+ * @param y The initial vertical (Y) position relative to the parent window or root pane.
+ * @param width The explicitly requested width of the container.
+ * @param height The explicitly requested height of the container.
+ */
public Container(Node view, double x, double y, double width, double height) {
setNode(view);
this.x = x;
this.y = y;
+ setX(x);
+ setY(y);
setWidth(width);
setHeight(height);
}
+ /**
+ * Constructs a new Container with specific positioning, dimensions, and a distinct rendering index (z-order).
+ * Inherited properties such as width and height are managed by the parent {@link Renderer}.
+ *
+ * @param view The base JavaFX Node that represents this container visually.
+ * @param x The initial horizontal (X) position relative to the parent window or root pane.
+ * @param y The initial vertical (Y) position relative to the parent window or root pane.
+ * @param width The explicitly requested width of the container.
+ * @param height The explicitly requested height of the container.
+ * @param index The rendering index (z-order layer) of the container. Higher indices are rendered on top.
+ */
public Container(Node view, double x, double y, double width, double height, int index) {
setNode(view);
this.x = x;
this.y = y;
+ setX(x);
+ setY(y);
setWidth(width);
setHeight(height);
setIndex(index);
}
/**
+ * Retrieves the explicitly assigned horizontal position of the container.
* @return The x position of the container in correlation to the window.
*/
public double getX() {
@@ -63,14 +93,18 @@ public double getX() {
}
/**
- * Set the x position of the container.
- * @param x The x position.
+ * Sets the horizontal position of the container.
+ * This automatically updates the translation mapping of the underlying JavaFX Node.
+ *
+ * @param x The new x position coordinate.
*/
public void setX(double x) {
this.x = x;
+ getNode().setTranslateX(x);
}
/**
+ * Retrieves the explicitly assigned vertical position of the container.
* @return The y position of the container in correlation to the window.
*/
public double getY() {
@@ -78,40 +112,51 @@ public double getY() {
}
/**
- * Set the y position of the container.
- * @param y The y position.
+ * Sets the vertical position of the container.
+ * This automatically updates the translation mapping of the underlying JavaFX Node.
+ *
+ * @param y The new y position coordinate.
*/
public void setY(double y) {
this.y = y;
+ getNode().setTranslateY(y);
}
- public IContainerClick getOnClick() {
- return click;
- }
-
- public void onClick(IContainerClick click) {
- this.click = click;
- }
-
+ /**
+ * Registers a custom render event listener to this container.
+ * These events hook into the container's lifecycle to execute logic during rendering phases.
+ *
+ * @param renderEvent The {@link IContainerRender} implementation to attach. Ignores null values.
+ */
public void addRenderEvent(IContainerRender renderEvent) {
if (renderEvent != null) {
this.renderEvents.add(renderEvent);
}
}
+ /**
+ * Unregisters a specific render event listener from this container.
+ *
+ * @param renderEvent The {@link IContainerRender} implementation to detach. Ignores null values.
+ */
public void removeRenderEvent(IContainerRender renderEvent) {
if (renderEvent != null) {
this.renderEvents.remove(renderEvent);
}
}
+ /**
+ * Retrieves the collection of all render event listeners currently attached to this container.
+ *
+ * @return A list of configured {@link IContainerRender} handlers.
+ */
public List getRenderEvents() {
return renderEvents;
}
/**
- * Gets all {@link Overlay}s added to the container.
- * @return The current linked list of {@link Overlay}s.
+ * Filters the internal element tracking map to isolate and retrieve all child {@link Overlay} instances.
+ * @return A linked list containing all {@link Overlay}s registered to this container.
*/
public LinkedList getOverlays() {
LinkedList toReturn = new LinkedList<>();
@@ -123,8 +168,9 @@ public LinkedList getOverlays() {
}
/**
- * Gets all sub-containers for the container.
- * @return The current linked list of sub-containers.
+ * Filters the internal element tracking map to isolate and retrieve all sub-containers.
+ *
+ * @return A linked list containing all nested {@link Container}s registered to this container.
*/
public LinkedList getContainers() {
LinkedList toReturn = new LinkedList<>();
@@ -136,8 +182,9 @@ public LinkedList getContainers() {
}
/**
- * Gets all {@link Layout}s for the container.
- * @return The current linked list of {@link Layout}s
+ * Filters the internal element tracking map to isolate and retrieve all assigned {@link Layout} handlers.
+ *
+ * @return A linked list containing all {@link Layout} instances assigned to this container.
*/
public LinkedList getLayouts() {
LinkedList toReturn = new LinkedList<>();
@@ -148,6 +195,13 @@ public LinkedList getLayouts() {
return toReturn;
}
+ /**
+ * Appends a local CSS stylesheet to the container's styling context.
+ * The provided file is automatically resolved into a compliant external URL format for JavaFX.
+ *
+ * @param file The local {@link File} object referencing the `.css` stylesheet.
+ * @throws RuntimeException If the file cannot be resolved into a structurally valid URL.
+ */
public void addStyleSheet(File file) {
try {
stylesheets.add(file.toURI().toURL().toExternalForm());
@@ -156,13 +210,21 @@ public void addStyleSheet(File file) {
}
}
+ /**
+ * Retrieves the collection of mapped CSS stylesheet URLs bound to this container.
+ *
+ * @return A list of formatted URL strings pointing to the stylesheets.
+ */
public List getStylesheets() {
return stylesheets;
}
/**
- * Builds and assembles the container. Converts RenJava API into JavaFX.
- * @return An entry set where the key is the pane as a node. The value is the collection of nodes which the pane contains.
+ * Compiles and assembles the container, mapping the engine's logical API constraints
+ * onto the native JavaFX scene graph structure. Subclasses dictate exactly how child nodes
+ * are structurally arranged.
+ *
+ * @return The constructed JavaFX {@link Node} representing this container and its fully populated children.
*/
public abstract Node build();
}
\ No newline at end of file
diff --git a/src/main/java/me/piitex/engine/containers/DialogueContainer.java b/src/main/java/me/piitex/engine/containers/DialogueContainer.java
index 9bbc201..ebdb59c 100644
--- a/src/main/java/me/piitex/engine/containers/DialogueContainer.java
+++ b/src/main/java/me/piitex/engine/containers/DialogueContainer.java
@@ -73,7 +73,7 @@ public Pane getPane() {
*/
@Override
public void addElement(Element element) {
- return;
+ throw new UnsupportedOperationException("Method is not supported with this container type! Use 'setHeader(), setBody()'");
}
/**
@@ -81,7 +81,7 @@ public void addElement(Element element) {
*/
@Override
public void addElement(Element element, int index) {
- return;
+ throw new UnsupportedOperationException("Method is not supported with this container type! Use 'setHeader(), setBody()'");
}
/**
@@ -89,7 +89,7 @@ public void addElement(Element element, int index) {
*/
@Override
public void addElements(Element... elements) {
- return;
+ throw new UnsupportedOperationException("Method is not supported with this container type! Use 'setHeader(), setBody()'");
}
/**
@@ -97,10 +97,9 @@ public void addElements(Element... elements) {
*/
@Override
public void addElements(LinkedList elements) {
- return;
+ throw new UnsupportedOperationException("Method is not supported with this container type! Use 'setHeader(), setBody()'");
}
-
@Override
public Node build() {
pane.setTranslateX(getX());
diff --git a/src/main/java/me/piitex/engine/containers/DownloadContainer.java b/src/main/java/me/piitex/engine/containers/DownloadContainer.java
new file mode 100644
index 0000000..2018c24
--- /dev/null
+++ b/src/main/java/me/piitex/engine/containers/DownloadContainer.java
@@ -0,0 +1,177 @@
+package me.piitex.engine.containers;
+
+import javafx.application.Platform;
+import javafx.geometry.Pos;
+import me.piitex.engine.layouts.VerticalLayout;
+import me.piitex.engine.overlays.ProgressBarOverlay;
+import me.piitex.engine.overlays.TextOverlay;
+import me.piitex.os.DownloadInfo;
+import me.piitex.os.DownloadListener;
+import me.piitex.os.FileDownloader;
+
+import java.io.File;
+import java.util.function.Consumer;
+
+public class DownloadContainer extends EmptyContainer {
+ private final String label;
+ private String url;
+ private File output;
+ private final FileDownloader downloader;
+ private DownloadInfo downloadInfo;
+
+ private VerticalLayout main;
+ private TextOverlay message, downloadText;
+ private ProgressBarOverlay downloadProgress;
+
+ private Consumer downloadComplete;
+ private Consumer downloadError;
+ private Consumer downloadCancelled;
+
+
+ public DownloadContainer(double width, double height, String label, String url, File output) {
+ super(0, 0, width, height);
+ this.label = label;
+ this.url = url;
+ this.output = output;
+ this.downloader = new FileDownloader();
+ init();
+ }
+
+ public DownloadContainer(double width, double height, String label, DownloadInfo downloadInfo, FileDownloader downloader) {
+ super(0, 0, width, height);
+ this.label = label;
+ this.downloadInfo = downloadInfo;
+ this.downloader = downloader;
+ init();
+ }
+
+ public Consumer getOnDownloadComplete() {
+ return downloadComplete;
+ }
+
+ public void onDownloadComplete(Consumer downloadComplete) {
+ this.downloadComplete = downloadComplete;
+ }
+
+ public Consumer getDownloadError() {
+ return downloadError;
+ }
+
+ public void onDownloadError(Consumer downloadError) {
+ this.downloadError = downloadError;
+ }
+
+ public Consumer getDownloadCancelled() {
+ return downloadCancelled;
+ }
+
+ public void onDownloadCancelled(Consumer downloadCancelled) {
+ this.downloadCancelled = downloadCancelled;
+ }
+
+ private void init() {
+ main = new VerticalLayout(getWidth(), getHeight());
+ main.setMaxSize(main.getWidth(), main.getHeight());
+ main.setAlignment(Pos.TOP_CENTER);
+ main.setY(20);
+ addElement(main);
+
+ message = new TextOverlay(label);
+ main.addElement(message);
+
+ downloadProgress = new ProgressBarOverlay();
+ main.addElement(downloadProgress);
+
+ if (downloader.getListeners().isEmpty()) {
+ hookDownloadListeners();
+ }
+
+ downloadText = new TextOverlay("0/0");
+ main.addElement(downloadText);
+ }
+
+ public void startDownload() {
+ if (url != null && output != null) {
+ downloader.startDownload(url, output);
+ } else if (downloadInfo != null) {
+ downloader.startDownload(downloadInfo.getDownloadUrl(), downloadInfo.getOutput());
+ } else {
+ throw new RuntimeException("Could not initialize download. URL or output not specified.");
+ }
+ }
+
+ private void hookDownloadListeners() {
+ downloader.addDownloadListener(new DownloadListener() {
+ @Override
+ public void onDownloadStart(DownloadInfo info) {
+ Platform.runLater(() -> {
+ downloadProgress.getProgressBar().progressProperty().set(0);
+ });
+ }
+
+ @Override
+ public void onDownloadProgress(DownloadInfo info) {
+ Platform.runLater(() -> {
+ downloadProgress.getProgressBar().progressProperty().set(info.getDownloadProgress());
+
+ if (info.getTotalFileSize() < 900000) { // Use Kb
+ downloadText.setText(info.getDownloadedBytes() / 1024 + "/" + info.getTotalFileSize() / 1024 + "KiB");
+ } else if (info.getTotalFileSize() < 900000000) { // Use MB
+ downloadText.setText(info.getDownloadedBytes() / 1000000 + "/" + info.getTotalFileSize() / 1000000 + "MB");
+ } else { // Use GB
+ downloadText.setText(info.getDownloadedBytes() / 1000000000 + "/" + info.getTotalFileSize() / 1000000000 + "GB");
+ }
+
+
+ });
+ }
+
+ @Override
+ public void onDownloadComplete(DownloadInfo info, File outputFile) {
+ if (downloadComplete != null) {
+ downloadComplete.accept(info);
+ } else {
+ throw new RuntimeException("Download completion is not handled!");
+ }
+ }
+
+ @Override
+ public void onDownloadError(DownloadInfo info, Exception e) {
+ if (downloadError != null) {
+ downloadError.accept(info);
+ } else {
+ throw new RuntimeException("Download error is not handled!");
+ }
+ }
+
+ @Override
+ public void onDownloadCancel(DownloadInfo info) {
+ if (downloadCancelled != null) {
+ downloadCancelled.accept(info);
+ } else {
+ throw new RuntimeException("Download cancelled is not handled!");
+ }
+ }
+ });
+ }
+
+ public FileDownloader getDownloader() {
+ return downloader;
+ }
+
+ public VerticalLayout getMain() {
+ return main;
+ }
+
+ public TextOverlay getMessage() {
+ return message;
+ }
+
+ public ProgressBarOverlay getDownloadProgress() {
+ return downloadProgress;
+ }
+
+ public TextOverlay getDownloadText() {
+ return downloadText;
+ }
+}
diff --git a/src/main/java/me/piitex/engine/containers/EmptyContainer.java b/src/main/java/me/piitex/engine/containers/EmptyContainer.java
index 242e325..017870f 100644
--- a/src/main/java/me/piitex/engine/containers/EmptyContainer.java
+++ b/src/main/java/me/piitex/engine/containers/EmptyContainer.java
@@ -7,7 +7,6 @@
import me.piitex.engine.layouts.Layout;
import me.piitex.engine.overlays.Overlay;
-// A default container which contains no special layout. Completely normal container.
/**
* The EmptyContainer is a default {@link Container} which houses {@link Overlay}s, {@link Layout}s, and sub-containers.
* The container must be added to a {@link Window} to be rendered.
@@ -74,10 +73,6 @@ public Node build() {
setStyling(pane);
- if (getOnClick() != null) {
- pane.setOnMouseClicked(event -> getOnClick().onClick(new ContainerClickEvent(this)));
- }
-
return pane;
}
}
\ No newline at end of file
diff --git a/src/main/java/me/piitex/engine/containers/PaginationContainer.java b/src/main/java/me/piitex/engine/containers/PaginationContainer.java
index c0bf8de..383d780 100644
--- a/src/main/java/me/piitex/engine/containers/PaginationContainer.java
+++ b/src/main/java/me/piitex/engine/containers/PaginationContainer.java
@@ -73,6 +73,21 @@ public Node build() {
*/
@Override
public void addElement(Element element) {
- return;
+ throw new UnsupportedOperationException("Method is not supported with this container type!");
+ }
+
+ @Override
+ public void addElement(Element element, int index) {
+ throw new UnsupportedOperationException("Method is not supported with this container type!");
+ }
+
+ @Override
+ public void addElements(Element... elements) {
+ throw new UnsupportedOperationException("Method is not supported with this container type!");
+ }
+
+ @Override
+ public void addElements(LinkedList elements) {
+ throw new UnsupportedOperationException("Method is not supported with this container type!");
}
}
\ No newline at end of file
diff --git a/src/main/java/me/piitex/engine/containers/ScrollContainer.java b/src/main/java/me/piitex/engine/containers/ScrollContainer.java
index 0f386ed..fc1afa0 100644
--- a/src/main/java/me/piitex/engine/containers/ScrollContainer.java
+++ b/src/main/java/me/piitex/engine/containers/ScrollContainer.java
@@ -18,6 +18,10 @@ public class ScrollContainer extends Container {
private boolean pannable = false;
private double scrollPosition;
+ public ScrollContainer(Layout layout, double width, double height) {
+ this(layout, 0, 0, width, height);
+ }
+
public ScrollContainer(Layout layout, double x, double y, double width, double height) {
ScrollPane tempPane = new ScrollPane();
this.scrollPane = tempPane;
@@ -47,6 +51,11 @@ public boolean isHorizontalScroll() {
public void setHorizontalScroll(boolean horizontalScroll) {
this.horizontalScroll = horizontalScroll;
+ if (!horizontalScroll && !scrollWhenNeeded) {
+ scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
+ } else if (horizontalScroll && !scrollWhenNeeded) {
+ scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.ALWAYS);
+ }
}
public boolean isVerticalScroll() {
@@ -55,6 +64,11 @@ public boolean isVerticalScroll() {
public void setVerticalScroll(boolean verticalScroll) {
this.verticalScroll = verticalScroll;
+ if (!verticalScroll && !scrollWhenNeeded) {
+ scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
+ } else if (verticalScroll && !scrollWhenNeeded) {
+ scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.ALWAYS);
+ }
}
public boolean isScrollWhenNeeded() {
@@ -67,6 +81,7 @@ public void setScrollWhenNeeded(boolean scrollWhenNeeded) {
public void setScrollPosition(double scrollPosition) {
this.scrollPosition = scrollPosition;
+ scrollPane.setVvalue(scrollPosition);
}
public void setScrollToBottom(boolean scrollToBottom) {
@@ -129,10 +144,13 @@ public Node build() {
setStyling(scrollPane);
// Build pane layout for the scroll content
- VBox pane = (VBox) layout.assemble();
+ Pane pane = (Pane) layout.assemble();
LayoutRenderEvent event = new LayoutRenderEvent((Pane) layout.getNode(), layout);
layout.getRenderEvents().forEach(iLayoutRender -> iLayoutRender.onLayoutRender(event));
- pane.setAlignment(layout.getAlignment());
+
+ if (pane instanceof VBox vBox) {
+ vBox.setAlignment(layout.getAlignment());
+ }
if (scrollToBottom) {
pane.heightProperty().addListener(observable -> {
diff --git a/src/main/java/me/piitex/engine/containers/StackContainer.java b/src/main/java/me/piitex/engine/containers/StackContainer.java
index 939372a..b72ac76 100644
--- a/src/main/java/me/piitex/engine/containers/StackContainer.java
+++ b/src/main/java/me/piitex/engine/containers/StackContainer.java
@@ -44,10 +44,6 @@ public Node build() {
setStyling(pane);
- if (getOnClick() != null) {
- pane.setOnMouseClicked(event -> getOnClick().onClick(new ContainerClickEvent(this)));
- }
-
return pane;
}
}
diff --git a/src/main/java/me/piitex/engine/containers/TileContainer.java b/src/main/java/me/piitex/engine/containers/TileContainer.java
index b9be838..577827d 100644
--- a/src/main/java/me/piitex/engine/containers/TileContainer.java
+++ b/src/main/java/me/piitex/engine/containers/TileContainer.java
@@ -66,24 +66,25 @@ public Element getAction() {
@Override
public void addElements(Element... elements) {
- return;
+ throw new UnsupportedOperationException("Method is not supported with this container type!");
}
@Override
public void addElements(LinkedList elements) {
- return;
+ throw new UnsupportedOperationException("Method is not supported with this container type!");
}
@Override
public void addElement(Element element) {
- return;
+ throw new UnsupportedOperationException("Method is not supported with this container type!");
}
@Override
public void addElement(Element element, int index) {
- return;
+ throw new UnsupportedOperationException("Method is not supported with this container type!");
}
+
public Tile getTile() {
return tile;
}
diff --git a/src/main/java/me/piitex/engine/containers/handlers/IContainerClick.java b/src/main/java/me/piitex/engine/containers/handlers/IContainerClick.java
deleted file mode 100644
index 4215741..0000000
--- a/src/main/java/me/piitex/engine/containers/handlers/IContainerClick.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package me.piitex.engine.containers.handlers;
-
-import me.piitex.engine.hanlders.events.ContainerClickEvent;
-
-public interface IContainerClick {
-
- void onClick(ContainerClickEvent event);
-}
diff --git a/src/main/java/me/piitex/engine/hanlders/events/OverlayClickEvent.java b/src/main/java/me/piitex/engine/hanlders/events/ElementClickEvent.java
similarity index 69%
rename from src/main/java/me/piitex/engine/hanlders/events/OverlayClickEvent.java
rename to src/main/java/me/piitex/engine/hanlders/events/ElementClickEvent.java
index 56d539c..6eff3a0 100644
--- a/src/main/java/me/piitex/engine/hanlders/events/OverlayClickEvent.java
+++ b/src/main/java/me/piitex/engine/hanlders/events/ElementClickEvent.java
@@ -1,44 +1,44 @@
-package me.piitex.engine.hanlders.events;
-
-import javafx.scene.input.MouseButton;
-import javafx.scene.input.MouseEvent;
-import me.piitex.engine.overlays.Overlay;
-
-public class OverlayClickEvent extends Event {
- private final Overlay overlay;
- private final MouseEvent event;
- private final double x, y;
-
-
- public OverlayClickEvent(Overlay overlay, MouseEvent event, double x, double y) {
- this.overlay = overlay;
- this.event = event;
- this.x = x;
- this.y = y;
- }
-
- public Overlay getOverlay() {
- return overlay;
- }
-
-
- public double getX() {
- return x;
- }
-
- public double getY() {
- return y;
- }
-
- public MouseEvent getHandler() {
- return event;
- }
-
- public boolean isRightClicked() {
- return event.getButton() == MouseButton.SECONDARY;
- }
-
- public boolean isMiddleButton() {
- return event.getButton() == MouseButton.MIDDLE;
- }
-}
+package me.piitex.engine.hanlders.events;
+
+import javafx.scene.input.MouseButton;
+import javafx.scene.input.MouseEvent;
+import me.piitex.engine.Element;
+import me.piitex.engine.overlays.Overlay;
+
+public class ElementClickEvent extends Event {
+ private final Element element;
+ private final MouseEvent event;
+ private final double x, y;
+
+
+ public ElementClickEvent(Element element, MouseEvent event, double x, double y) {
+ this.element = element;
+ this.event = event;
+ this.x = x;
+ this.y = y;
+ }
+
+ public Element getElement() {
+ return element;
+ }
+
+ public double getX() {
+ return x;
+ }
+
+ public double getY() {
+ return y;
+ }
+
+ public MouseEvent getHandler() {
+ return event;
+ }
+
+ public boolean isRightClicked() {
+ return event.getButton() == MouseButton.SECONDARY;
+ }
+
+ public boolean isMiddleButton() {
+ return event.getButton() == MouseButton.MIDDLE;
+ }
+}
diff --git a/src/main/java/me/piitex/engine/hanlders/events/ElementClickReleaseEvent.java b/src/main/java/me/piitex/engine/hanlders/events/ElementClickReleaseEvent.java
new file mode 100644
index 0000000..f70356a
--- /dev/null
+++ b/src/main/java/me/piitex/engine/hanlders/events/ElementClickReleaseEvent.java
@@ -0,0 +1,23 @@
+package me.piitex.engine.hanlders.events;
+
+import javafx.scene.input.MouseEvent;
+import me.piitex.engine.Element;
+import me.piitex.engine.overlays.Overlay;
+
+public class ElementClickReleaseEvent extends Event {
+ private final Element element;
+ private final MouseEvent event;
+
+ public ElementClickReleaseEvent(Element element, MouseEvent event) {
+ this.element = element;
+ this.event = event;
+ }
+
+ public Element getElement() {
+ return element;
+ }
+
+ public MouseEvent getHandler() {
+ return event;
+ }
+}
diff --git a/src/main/java/me/piitex/engine/hanlders/events/ElementExitEvent.java b/src/main/java/me/piitex/engine/hanlders/events/ElementExitEvent.java
new file mode 100644
index 0000000..1a9c321
--- /dev/null
+++ b/src/main/java/me/piitex/engine/hanlders/events/ElementExitEvent.java
@@ -0,0 +1,23 @@
+package me.piitex.engine.hanlders.events;
+
+import javafx.scene.input.MouseEvent;
+import me.piitex.engine.Element;
+import me.piitex.engine.overlays.Overlay;
+
+public class ElementExitEvent extends Event {
+ private final Element element;
+ private final MouseEvent event;
+
+ public ElementExitEvent(Element element, MouseEvent event) {
+ this.element = element;
+ this.event = event;
+ }
+
+ public Element getElement() {
+ return element;
+ }
+
+ public MouseEvent getHandler() {
+ return event;
+ }
+}
diff --git a/src/main/java/me/piitex/engine/hanlders/events/ElementHoverEvent.java b/src/main/java/me/piitex/engine/hanlders/events/ElementHoverEvent.java
new file mode 100644
index 0000000..5192c36
--- /dev/null
+++ b/src/main/java/me/piitex/engine/hanlders/events/ElementHoverEvent.java
@@ -0,0 +1,23 @@
+package me.piitex.engine.hanlders.events;
+
+import javafx.scene.input.MouseEvent;
+import me.piitex.engine.Element;
+import me.piitex.engine.overlays.Overlay;
+
+public class ElementHoverEvent extends Event {
+ private final Element element;
+ private final MouseEvent mouseEvent;
+
+ public ElementHoverEvent(Element element, MouseEvent event) {
+ this.element = element;
+ this.mouseEvent = event;
+ }
+
+ public Element getElement() {
+ return element;
+ }
+
+ public MouseEvent getHandler() {
+ return mouseEvent;
+ }
+}
diff --git a/src/main/java/me/piitex/engine/hanlders/events/OverlayClickReleaseEvent.java b/src/main/java/me/piitex/engine/hanlders/events/OverlayClickReleaseEvent.java
deleted file mode 100644
index 6147228..0000000
--- a/src/main/java/me/piitex/engine/hanlders/events/OverlayClickReleaseEvent.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package me.piitex.engine.hanlders.events;
-
-import javafx.scene.input.MouseEvent;
-import me.piitex.engine.overlays.Overlay;
-
-public class OverlayClickReleaseEvent extends Event {
- private final Overlay overlay;
- private final MouseEvent event;
-
- public OverlayClickReleaseEvent(Overlay overlay, MouseEvent event) {
- this.overlay = overlay;
- this.event = event;
- }
-
- public Overlay getOverlay() {
- return overlay;
- }
-
- public MouseEvent getHandler() {
- return event;
- }
-}
diff --git a/src/main/java/me/piitex/engine/hanlders/events/OverlayExitEvent.java b/src/main/java/me/piitex/engine/hanlders/events/OverlayExitEvent.java
deleted file mode 100644
index 1c5edf1..0000000
--- a/src/main/java/me/piitex/engine/hanlders/events/OverlayExitEvent.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package me.piitex.engine.hanlders.events;
-
-import javafx.scene.input.MouseEvent;
-import me.piitex.engine.overlays.Overlay;
-
-public class OverlayExitEvent extends Event {
- private final Overlay overlay;
- private final MouseEvent event;
-
- public OverlayExitEvent(Overlay overlay, MouseEvent event) {
- this.overlay = overlay;
- this.event = event;
- }
-
- public Overlay getOverlay() {
- return overlay;
- }
-
- public MouseEvent getHandler() {
- return event;
- }
-}
diff --git a/src/main/java/me/piitex/engine/hanlders/events/OverlayHoverEvent.java b/src/main/java/me/piitex/engine/hanlders/events/OverlayHoverEvent.java
deleted file mode 100644
index 2624f52..0000000
--- a/src/main/java/me/piitex/engine/hanlders/events/OverlayHoverEvent.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package me.piitex.engine.hanlders.events;
-
-import javafx.scene.input.MouseEvent;
-import me.piitex.engine.overlays.Overlay;
-
-public class OverlayHoverEvent extends Event {
- private final Overlay overlay;
- private final MouseEvent mouseEvent;
-
- public OverlayHoverEvent(Overlay overlay, MouseEvent event) {
- this.overlay = overlay;
- this.mouseEvent = event;
- }
-
- public Overlay getOverlay() {
- return overlay;
- }
-
- public MouseEvent getHandler() {
- return mouseEvent;
- }
-}
diff --git a/src/main/java/me/piitex/engine/hanlders/events/SliderChangeEvent.java b/src/main/java/me/piitex/engine/hanlders/events/SliderChangeEvent.java
index 0c2c3ba..6346fdf 100644
--- a/src/main/java/me/piitex/engine/hanlders/events/SliderChangeEvent.java
+++ b/src/main/java/me/piitex/engine/hanlders/events/SliderChangeEvent.java
@@ -4,22 +4,24 @@
public class SliderChangeEvent extends Event {
private final SliderOverlay sliderOverlay;
- private double value;
+ private final double newValue;
+ private final double oldValue;
- public SliderChangeEvent(SliderOverlay sliderOverlay, double value) {
+ public SliderChangeEvent(SliderOverlay sliderOverlay, double newValue, double oldValue) {
this.sliderOverlay = sliderOverlay;
- this.value = value;
+ this.newValue = newValue;
+ this.oldValue = oldValue;
}
public SliderOverlay getSliderOverlay() {
return sliderOverlay;
}
- public double getValue() {
- return value;
+ public double getNewValue() {
+ return newValue;
}
- public void setValue(double value) {
- this.value = value;
+ public double getOldValue() {
+ return oldValue;
}
}
diff --git a/src/main/java/me/piitex/engine/layouts/FlowLayout.java b/src/main/java/me/piitex/engine/layouts/FlowLayout.java
index 7b25933..fdf608d 100644
--- a/src/main/java/me/piitex/engine/layouts/FlowLayout.java
+++ b/src/main/java/me/piitex/engine/layouts/FlowLayout.java
@@ -72,12 +72,6 @@ public Node render() {
pane.setBackground(new Background(new BackgroundFill(getBackgroundColor(), CornerRadii.EMPTY, Insets.EMPTY)));
}
- if (getClickEvent() != null) {
- pane.addEventHandler(MouseEvent.MOUSE_CLICKED, mouseEvent -> {
- getClickEvent().onLayoutClick(new LayoutClickEvent(mouseEvent,this));
- });
- }
-
setStyling(pane);
return pane;
}
diff --git a/src/main/java/me/piitex/engine/layouts/HorizontalLayout.java b/src/main/java/me/piitex/engine/layouts/HorizontalLayout.java
index 6de9a2d..2d38b65 100644
--- a/src/main/java/me/piitex/engine/layouts/HorizontalLayout.java
+++ b/src/main/java/me/piitex/engine/layouts/HorizontalLayout.java
@@ -60,12 +60,6 @@ public Node render() {
pane.setBackground(new Background(new BackgroundFill(getBackgroundColor(), CornerRadii.EMPTY, Insets.EMPTY)));
}
- if (getClickEvent() != null) {
- pane.addEventHandler(MouseEvent.MOUSE_CLICKED, mouseEvent -> {
- getClickEvent().onLayoutClick(new LayoutClickEvent(mouseEvent,this));
- });
- }
-
setStyling(pane);
return pane;
}
diff --git a/src/main/java/me/piitex/engine/layouts/Layout.java b/src/main/java/me/piitex/engine/layouts/Layout.java
index 6fab7bb..1eb3750 100644
--- a/src/main/java/me/piitex/engine/layouts/Layout.java
+++ b/src/main/java/me/piitex/engine/layouts/Layout.java
@@ -5,7 +5,6 @@
import javafx.scene.Node;
import javafx.scene.layout.Pane;
import me.piitex.engine.Renderer;
-import me.piitex.engine.layouts.handles.ILayoutClickEvent;
import me.piitex.engine.layouts.handles.ILayoutRender;
import java.util.ArrayList;
@@ -16,7 +15,6 @@ public abstract class Layout extends Renderer {
private double x, y;
private Insets padding;
private Pos alignment;
- private ILayoutClickEvent clickEvent;
private final List renderEvents;
protected Layout(Pane pane, double width, double height) {
@@ -27,14 +25,6 @@ protected Layout(Pane pane, double width, double height) {
this.renderEvents = new ArrayList<>();
}
- public ILayoutClickEvent getClickEvent() {
- return clickEvent;
- }
-
- public void setClickEvent(ILayoutClickEvent clickEvent) {
- this.clickEvent = clickEvent;
- }
-
public void addRenderEvent(ILayoutRender renderEvent) {
if (renderEvent != null) {
this.renderEvents.add(renderEvent);
diff --git a/src/main/java/me/piitex/engine/layouts/TitledLayout.java b/src/main/java/me/piitex/engine/layouts/TitledLayout.java
index b56a649..009bd85 100644
--- a/src/main/java/me/piitex/engine/layouts/TitledLayout.java
+++ b/src/main/java/me/piitex/engine/layouts/TitledLayout.java
@@ -100,16 +100,6 @@ public Node render() {
}
}
-
- if (getClickEvent() != null) {
- pane.setOnMousePressed(mouseEvent -> {
- getClickEvent().onLayoutClick(new LayoutClickEvent(mouseEvent, this));
- });
- } else {
- pane.setOnMousePressed(null);
- }
-
-
setStyling(pane);
titledPane.setContent(pane);
return titledPane;
diff --git a/src/main/java/me/piitex/engine/layouts/VerticalLayout.java b/src/main/java/me/piitex/engine/layouts/VerticalLayout.java
index f51f574..e29a9db 100644
--- a/src/main/java/me/piitex/engine/layouts/VerticalLayout.java
+++ b/src/main/java/me/piitex/engine/layouts/VerticalLayout.java
@@ -57,14 +57,6 @@ public Node render() {
pane.setBackground(new Background(new BackgroundFill(getBackgroundColor(), CornerRadii.EMPTY, Insets.EMPTY)));
}
- if (getClickEvent() != null) {
- pane.setOnMousePressed(mouseEvent -> {
- getClickEvent().onLayoutClick(new LayoutClickEvent(mouseEvent, this));
- });
- } else {
- pane.setOnMousePressed(null);
- }
-
setStyling(pane);
return pane;
}
diff --git a/src/main/java/me/piitex/engine/layouts/handles/ILayoutClickEvent.java b/src/main/java/me/piitex/engine/layouts/handles/ILayoutClickEvent.java
deleted file mode 100644
index 26e8207..0000000
--- a/src/main/java/me/piitex/engine/layouts/handles/ILayoutClickEvent.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package me.piitex.engine.layouts.handles;
-
-import me.piitex.engine.hanlders.events.LayoutClickEvent;
-
-public interface ILayoutClickEvent {
-
- void onLayoutClick(LayoutClickEvent event);
-}
diff --git a/src/main/java/me/piitex/engine/loaders/FontLoader.java b/src/main/java/me/piitex/engine/loaders/FontLoader.java
index 2ea1d45..2e58764 100644
--- a/src/main/java/me/piitex/engine/loaders/FontLoader.java
+++ b/src/main/java/me/piitex/engine/loaders/FontLoader.java
@@ -3,6 +3,8 @@
import javafx.scene.text.Font;
import javafx.scene.text.FontPosture;
import javafx.scene.text.FontWeight;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileInputStream;
@@ -16,6 +18,8 @@ public class FontLoader {
private FontWeight weight;
private FontPosture posture;
+ private static final Logger logger = LoggerFactory.getLogger(FontLoader.class);
+
public FontLoader(FontLoader font, double size) {
this.name = font.getName();
this.size = size;
@@ -50,40 +54,10 @@ public FontLoader(Font font, FontWeight weight, FontPosture posture, double size
this.font = Font.font(font.getFamily(), weight, posture, size);
}
- public FontLoader(String name, double size) {
- this.name = name;
- File directory = new File(System.getProperty("user.dir") + "/game/fonts/");
- File file = new File(directory, name);
- this.size = size;
- try {
- this.font = Font.loadFont(new FileInputStream(file), size);
- } catch (FileNotFoundException e) {
- URL resource = FontLoader.class.getClassLoader().getResource(name);
- if (resource != null) {
- this.font = Font.loadFont(resource.toExternalForm(), size);
- }
- }
- }
-
- public FontLoader(String name) {
- File directory = new File(System.getProperty("user.dir") + "/game/fonts/");
- File file = new File(directory, name);
- this.name = name;
- try {
- this.font = Font.loadFont(new FileInputStream(file), 24);
- } catch (FileNotFoundException e) {
- URL resource = FontLoader.class.getClassLoader().getResource(name);
- if (resource != null) {
- this.font = Font.loadFont(resource.toExternalForm(), size);
- }
- }
- this.size = 24;
- }
-
- public FontLoader(File file, FontPosture posture, FontWeight weight) {
+ public FontLoader(File file, double size) {
this.name = file.getName();
try {
- this.font = Font.loadFont(new FileInputStream(file), 24);
+ this.font = Font.loadFont(new FileInputStream(file), size);
} catch (FileNotFoundException e) {
URL resource = FontLoader.class.getClassLoader().getResource(name);
if (resource != null) {
diff --git a/src/main/java/me/piitex/engine/loaders/ImageLoader.java b/src/main/java/me/piitex/engine/loaders/ImageLoader.java
index 080ab24..edc624a 100644
--- a/src/main/java/me/piitex/engine/loaders/ImageLoader.java
+++ b/src/main/java/me/piitex/engine/loaders/ImageLoader.java
@@ -40,6 +40,10 @@ public ImageLoader(String directory, String name) {
this.file = new File(fileDirectory, name);
}
+ public ImageLoader(File directory, String name) {
+ this.file = new File(directory, name);
+ }
+
public ImageLoader(File file) {
this.file = file;
}
diff --git a/src/main/java/me/piitex/engine/maps/LimitedHashMap.java b/src/main/java/me/piitex/engine/maps/LimitedHashMap.java
index 2ee38a2..e9946f2 100644
--- a/src/main/java/me/piitex/engine/maps/LimitedHashMap.java
+++ b/src/main/java/me/piitex/engine/maps/LimitedHashMap.java
@@ -1,88 +1,32 @@
package me.piitex.engine.maps;
-import java.util.Collection;
import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
// A map with a limited size. This will also remove old entries when the limit is hit.
-public class LimitedHashMap implements Map {
- private final Map map;
+public class LimitedHashMap extends HashMap {
private final int limit;
public LimitedHashMap(int limit) {
- map = new HashMap<>();
this.limit = limit;
}
- @Override
- public int size() {
- return map.size();
- }
-
- @Override
- public boolean isEmpty() {
- return map.isEmpty();
- }
-
- @Override
- public boolean containsKey(Object key) {
- return map.containsKey(key);
- }
-
- @Override
- public boolean containsValue(Object value) {
- return map.containsValue(value);
- }
-
- @Override
- public V get(Object key) {
- return map.get(key);
- }
-
@Override
public V put(K key, V value) {
- if (map.size() < limit) {
- map.put(key, value);
- } else {
- map.entrySet().stream().findFirst().ifPresent(remove -> map.remove(remove.getKey()));
+ if (size() > limit) {
+ // Remove first entry not last
+ remove(keySet().stream().findFirst());
}
+ super.put(key, value);
return value;
}
@Override
- public V remove(Object key) {
- return map.remove(key);
- }
-
- @Override
- public void putAll(Map extends K, ? extends V> m) {
- m.forEach((k, v) -> {
- if (size() < limit) {
- map.put(k, v);
- } else {
- map.entrySet().stream().findFirst().ifPresent(remove -> map.remove(remove.getKey()));
- }
- });
- }
-
- @Override
- public void clear() {
- map.clear();
- }
-
- @Override
- public Set keySet() {
- return map.keySet();
- }
-
- @Override
- public Collection values() {
- return map.values();
- }
-
- @Override
- public Set> entrySet() {
- return map.entrySet();
+ public V putIfAbsent(K key, V value) {
+ if (size() > limit) {
+ // Remove first entry not last
+ remove(keySet().stream().findFirst());
+ }
+ super.putIfAbsent(key, value);
+ return value;
}
}
diff --git a/src/main/java/me/piitex/engine/overlays/BoxOverlay.java b/src/main/java/me/piitex/engine/overlays/BoxOverlay.java
index ffb62f9..5eb0636 100644
--- a/src/main/java/me/piitex/engine/overlays/BoxOverlay.java
+++ b/src/main/java/me/piitex/engine/overlays/BoxOverlay.java
@@ -117,6 +117,14 @@ public void setMaxHeight(double h) {
this.maxHeight = h;
}
+ @Override
+ public void setMaxSize(double w, double h) {
+ this.maxWidth = w;
+ this.maxHeight = h;
+ rectangle.setWidth(w);
+ rectangle.setHeight(h);
+ }
+
public Rectangle getRectangle() {
return rectangle;
}
@@ -125,11 +133,7 @@ public Rectangle getRectangle() {
public Node render() {
rectangle.setX(getX());
rectangle.setY(getY());
- if (getFillColor() != null) {
- rectangle.setFill(getFillColor());
- }
rectangle.setStroke(strokeColor);
-
return rectangle;
}
}
diff --git a/src/main/java/me/piitex/engine/overlays/ButtonBuilder.java b/src/main/java/me/piitex/engine/overlays/ButtonBuilder.java
index 2775fd6..a21a8e0 100644
--- a/src/main/java/me/piitex/engine/overlays/ButtonBuilder.java
+++ b/src/main/java/me/piitex/engine/overlays/ButtonBuilder.java
@@ -1,21 +1,25 @@
package me.piitex.engine.overlays;
import javafx.scene.paint.Paint;
+import me.piitex.engine.Element;
import me.piitex.engine.loaders.FontLoader;
-import org.kordamp.ikonli.javafx.FontIcon;
+import java.util.ArrayList;
import java.util.LinkedList;
+import java.util.List;
public class ButtonBuilder {
private final String id;
private String text;
- private FontIcon icon;
+ private IconOverlay icon;
private FontLoader font;
+ private Element graphic;
private Paint textFill;
private double width, height, prefHeight, prefWidth, maxWidth, maxHeight;
private double x, y;
private boolean enabled = true;
private final LinkedList images = new LinkedList<>();
+ private final List styles = new ArrayList<>();
/**
* Initializes the builder with the mandatory button ID.
@@ -40,7 +44,7 @@ public ButtonBuilder setText(String text) {
* @param icon The FontIcon to display on the button.
* @return The builder instance.
*/
- public ButtonBuilder setIcon(FontIcon icon) {
+ public ButtonBuilder setIcon(IconOverlay icon) {
this.icon = icon;
return this;
}
@@ -147,6 +151,11 @@ public ButtonBuilder addImage(ImageOverlay image) {
return this;
}
+ public ButtonBuilder addStyle(String style) {
+ this.styles.add(style);
+ return this;
+ }
+
/**
* Sets the enabled state of the button.
* @param enabled True to enable the button, false to disable.
@@ -157,6 +166,11 @@ public ButtonBuilder setEnabled(boolean enabled) {
return this;
}
+ public ButtonBuilder setGraphic(Element graphic) {
+ this.graphic = graphic;
+ return this;
+ }
+
/**
* Builds and returns a new ButtonOverlay object with the configured properties.
* @return A new ButtonOverlay instance.
@@ -173,7 +187,7 @@ public String getText() {
return text;
}
- public FontIcon getIcon() {
+ public IconOverlay getIcon() {
return icon;
}
@@ -181,6 +195,10 @@ public FontLoader getFont() {
return font;
}
+ public Element getGraphic() {
+ return graphic;
+ }
+
public Paint getTextFill() {
return textFill;
}
@@ -225,4 +243,8 @@ public boolean isEnabled() {
public LinkedList getImages() {
return images;
}
+
+ public List getStyles() {
+ return styles;
+ }
}
\ No newline at end of file
diff --git a/src/main/java/me/piitex/engine/overlays/ButtonOverlay.java b/src/main/java/me/piitex/engine/overlays/ButtonOverlay.java
index 7afd73e..470944d 100644
--- a/src/main/java/me/piitex/engine/overlays/ButtonOverlay.java
+++ b/src/main/java/me/piitex/engine/overlays/ButtonOverlay.java
@@ -6,22 +6,27 @@
import javafx.scene.image.ImageView;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
+import me.piitex.engine.Element;
import me.piitex.engine.loaders.FontLoader;
-import org.kordamp.ikonli.javafx.FontIcon;
+import java.util.ArrayList;
import java.util.LinkedList;
+import java.util.List;
public class ButtonOverlay extends Overlay implements Region {
private final Button button;
private final String id;
private String text;
private FontLoader font;
- private final FontIcon icon;
+ private final Element graphic;
+ private final IconOverlay icon;
private final LinkedList images = new LinkedList<>();
private Paint textFill;
-
+ private Pos alignment;
private double width, height, prefHeight, prefWidth, maxWidth, maxHeight;
+ public static List cssFiles = new ArrayList<>();
+
/**
* Private constructor used by the {@link ButtonBuilder}.
* @param builder The builder instance with all the properties set.
@@ -31,6 +36,7 @@ protected ButtonOverlay(ButtonBuilder builder) {
this.text = builder.getText();
this.icon = builder.getIcon();
this.font = builder.getFont();
+ this.graphic = builder.getGraphic();
this.textFill = builder.getTextFill();
this.width = builder.getWidth();
this.height = builder.getHeight();
@@ -39,6 +45,7 @@ protected ButtonOverlay(ButtonBuilder builder) {
this.maxWidth = builder.getMaxWidth();
this.maxHeight = builder.getMaxHeight();
this.images.addAll(builder.getImages());
+ builder.getStyles().forEach(this::addStyle);
setX(builder.getX());
setY(builder.getY());
this.button = new Button();
@@ -72,6 +79,15 @@ public void setFont(FontLoader font) {
button.setFont(font.getFont());
}
+ public Pos getAlignment() {
+ return alignment;
+ }
+
+ public void setAlignment(Pos alignment) {
+ this.alignment = alignment;
+ button.setAlignment(alignment);
+ }
+
@Override
public Node render() {
button.setId(id);
@@ -101,8 +117,13 @@ public Node render() {
}
}
}
+
if (icon != null) {
- button.setGraphic(icon);
+ Node graphic = icon.assemble();
+ button.setGraphic(graphic);
+ }
+ if (graphic != null) {
+ button.setGraphic(graphic.assemble());
}
if (text != null && !text.isEmpty()) {
button.setText(text);
@@ -113,6 +134,9 @@ public Node render() {
if (textFill != null) {
button.setTextFill(textFill);
}
+ if (alignment != null) {
+ button.setAlignment(alignment);
+ }
if (height > 0) {
button.setMaxHeight(height);
button.setPrefHeight(height);
@@ -130,6 +154,11 @@ public Node render() {
button.setTranslateX(getX());
button.setTranslateY(getY());
+
+ for (String file : cssFiles) {
+ button.getStylesheets().add(file);
+ }
+
return button;
}
@@ -199,6 +228,13 @@ public void setMaxHeight(double h) {
button.setMaxHeight(h);
}
+ @Override
+ public void setMaxSize(double w, double h) {
+ this.maxWidth = w;
+ this.maxHeight = h;
+ button.setMaxSize(w, h);
+ }
+
public LinkedList getImages() {
return images;
}
diff --git a/src/main/java/me/piitex/engine/overlays/CheckBoxOverlay.java b/src/main/java/me/piitex/engine/overlays/CheckBoxOverlay.java
index 7436ba8..b36149e 100644
--- a/src/main/java/me/piitex/engine/overlays/CheckBoxOverlay.java
+++ b/src/main/java/me/piitex/engine/overlays/CheckBoxOverlay.java
@@ -7,7 +7,6 @@
public class CheckBoxOverlay extends Overlay {
private final CheckBox checkBox;
- private boolean selected;
private boolean defaultValue;
private String label;
@@ -39,11 +38,10 @@ public void setLabel(String label) {
}
public boolean isSelected() {
- return selected;
+ return checkBox.isSelected();
}
public void setSelected(boolean selected) {
- this.selected = selected;
checkBox.setSelected(selected);
}
diff --git a/src/main/java/me/piitex/engine/overlays/ChoiceBoxOverlay.java b/src/main/java/me/piitex/engine/overlays/ChoiceBoxOverlay.java
index 4e9e5b9..f213868 100644
--- a/src/main/java/me/piitex/engine/overlays/ChoiceBoxOverlay.java
+++ b/src/main/java/me/piitex/engine/overlays/ChoiceBoxOverlay.java
@@ -193,4 +193,11 @@ public void setMaxHeight(double h) {
this.maxHeight = h;
choiceBox.setMaxHeight(h);
}
+
+ @Override
+ public void setMaxSize(double w, double h) {
+ this.maxWidth = w;
+ this.maxHeight = h;
+ choiceBox.setMaxSize(w, h);
+ }
}
diff --git a/src/main/java/me/piitex/engine/overlays/ColorPickerOverlay.java b/src/main/java/me/piitex/engine/overlays/ColorPickerOverlay.java
index 2c913e6..0b50bfd 100644
--- a/src/main/java/me/piitex/engine/overlays/ColorPickerOverlay.java
+++ b/src/main/java/me/piitex/engine/overlays/ColorPickerOverlay.java
@@ -116,4 +116,11 @@ public void setMaxHeight(double h) {
colorPicker.setMaxHeight(h);
}
+ @Override
+ public void setMaxSize(double w, double h) {
+ this.maxWidth = w;
+ this.maxHeight = h;
+ colorPicker.setMaxSize(w, h);
+ }
+
}
diff --git a/src/main/java/me/piitex/engine/overlays/ComboBoxOverlay.java b/src/main/java/me/piitex/engine/overlays/ComboBoxOverlay.java
index b9642b1..666873d 100644
--- a/src/main/java/me/piitex/engine/overlays/ComboBoxOverlay.java
+++ b/src/main/java/me/piitex/engine/overlays/ComboBoxOverlay.java
@@ -183,4 +183,11 @@ public void setMaxHeight(double h) {
this.maxHeight = h;
comboBox.setMaxHeight(h);
}
+
+ @Override
+ public void setMaxSize(double w, double h) {
+ this.maxWidth = w;
+ this.maxHeight = h;
+ comboBox.setMaxSize(w, h);
+ }
}
diff --git a/src/main/java/me/piitex/engine/overlays/FileChooserOverlay.java b/src/main/java/me/piitex/engine/overlays/FileChooserOverlay.java
index a33d83a..335a376 100644
--- a/src/main/java/me/piitex/engine/overlays/FileChooserOverlay.java
+++ b/src/main/java/me/piitex/engine/overlays/FileChooserOverlay.java
@@ -17,6 +17,7 @@ public class FileChooserOverlay extends Overlay {
private ButtonOverlay button;
private FontLoader fontLoader;
private String[] fileExtensions;
+ private String defaultFileName;
public FileChooserOverlay(Window window, String text) {
this.window = window;
@@ -56,6 +57,15 @@ public void onFileSelect(IDirectorySelect iDirectorySelect) {
this.directorySelect = iDirectorySelect;
}
+ public String getDefaultFileName() {
+ return defaultFileName;
+ }
+
+ public void setDefaultFileName(String defaultFileName) {
+ this.defaultFileName = defaultFileName;
+
+ }
+
/**
* Set a specific file extension to be use used. You can set both a prefix and a subfix; *.png, filename.*, *.*
* @param fileExtensions The array of all valid file extensions.
@@ -78,6 +88,7 @@ public Node render() {
jfxButton.setOnMouseClicked(event -> {
FileChooser chooser = new FileChooser();
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(text, fileExtensions));
+ chooser.setInitialFileName(getDefaultFileName());
File directory = chooser.showOpenDialog(window.getStage());
if (getFileSelect() != null) {
diff --git a/src/main/java/me/piitex/engine/overlays/FileSaveOverlay.java b/src/main/java/me/piitex/engine/overlays/FileSaveOverlay.java
new file mode 100644
index 0000000..7abbf51
--- /dev/null
+++ b/src/main/java/me/piitex/engine/overlays/FileSaveOverlay.java
@@ -0,0 +1,103 @@
+package me.piitex.engine.overlays;
+
+import javafx.scene.Node;
+import javafx.scene.control.Button;
+import javafx.stage.FileChooser;
+import me.piitex.engine.Window;
+import me.piitex.engine.hanlders.events.DirectorySelectEvent;
+import me.piitex.engine.loaders.FontLoader;
+import me.piitex.engine.overlays.events.IDirectorySelect;
+
+import java.io.File;
+
+public class FileSaveOverlay extends Overlay {
+ private final Window window;
+ private IDirectorySelect directorySelect;
+ private String text;
+ private ButtonOverlay button;
+ private FontLoader fontLoader;
+ private String[] fileExtensions;
+ private String defaultFileName;
+
+ public FileSaveOverlay(Window window, String text) {
+ this.window = window;
+ this.text = text;
+ }
+
+ public FileSaveOverlay(Window window, ButtonOverlay button) {
+ this.window = window;
+ this.button = button;
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ public void setText(String text) {
+ this.text = text;
+ }
+
+ public ButtonOverlay getButton() {
+ return button;
+ }
+
+ public FontLoader getFontLoader() {
+ return fontLoader;
+ }
+
+ public void setFontLoader(FontLoader fontLoader) {
+ this.fontLoader = fontLoader;
+ }
+
+ public IDirectorySelect getFileSelect() {
+ return directorySelect;
+ }
+
+ public void onFileSelect(IDirectorySelect iDirectorySelect) {
+ this.directorySelect = iDirectorySelect;
+ }
+
+ public String getDefaultFileName() {
+ return defaultFileName;
+ }
+
+ public void setDefaultFileName(String defaultFileName) {
+ this.defaultFileName = defaultFileName;
+
+ }
+
+ /**
+ * Set a specific file extension to be use used. You can set both a prefix and a subfix; *.png, filename.*, *.*
+ * @param fileExtensions The array of all valid file extensions.
+ */
+ public void setFileExtensions(String[] fileExtensions) {
+ this.fileExtensions = fileExtensions;
+ }
+
+ @Override
+ public Node render() {
+ Button jfxButton;
+ if (button != null) {
+ jfxButton = (Button) button.render();
+ } else {
+ jfxButton = new Button(text);
+ jfxButton.setTranslateX(getX());
+ jfxButton.setTranslateY(getY());
+ }
+
+ jfxButton.setOnMouseClicked(event -> {
+ FileChooser chooser = new FileChooser();
+ chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(text, fileExtensions));
+ chooser.setInitialFileName(getDefaultFileName());
+ File directory = chooser.showSaveDialog(window.getStage());
+
+ if (getFileSelect() != null) {
+ if (directory != null) {
+ directorySelect.onDirectorySelect(new DirectorySelectEvent(directory));
+ }
+ }
+
+ });
+ return jfxButton;
+ }
+}
diff --git a/src/main/java/me/piitex/engine/overlays/HyperLinkOverlay.java b/src/main/java/me/piitex/engine/overlays/HyperLinkOverlay.java
index 8d84d4c..068b530 100644
--- a/src/main/java/me/piitex/engine/overlays/HyperLinkOverlay.java
+++ b/src/main/java/me/piitex/engine/overlays/HyperLinkOverlay.java
@@ -55,9 +55,6 @@ public Node render() {
hyperlink.setPadding(new Insets(4,0,4,0));
hyperlink.setTranslateX(getX());
hyperlink.setTranslateY(getY());
- hyperlink.setOnMouseClicked(event -> {
-
- });
return hyperlink;
}
}
diff --git a/src/main/java/me/piitex/engine/overlays/IconOverlay.java b/src/main/java/me/piitex/engine/overlays/IconOverlay.java
new file mode 100644
index 0000000..5cbb394
--- /dev/null
+++ b/src/main/java/me/piitex/engine/overlays/IconOverlay.java
@@ -0,0 +1,75 @@
+package me.piitex.engine.overlays;
+
+import javafx.scene.Node;
+import javafx.scene.paint.Color;
+import javafx.scene.paint.Paint;
+import org.kordamp.ikonli.Ikon;
+import org.kordamp.ikonli.javafx.FontIcon;
+
+/**
+ * RenEngine wrapper for {@link FontIcon}. Style classes are disabled for this overlay.
+ */
+public class IconOverlay extends Overlay {
+ private final FontIcon fontIcon;
+ private String iconCode;
+ private int iconSize = 16;
+ private Paint color = Color.WHITE;
+
+ public IconOverlay() {
+ this.fontIcon = new FontIcon();
+ setNode(fontIcon);
+ }
+
+ public IconOverlay(Ikon ikon) {
+ this.fontIcon = new FontIcon(ikon);
+ setNode(fontIcon);
+ }
+
+ public IconOverlay(String iconCode) {
+ this.fontIcon = new FontIcon(iconCode);
+ this.iconCode = iconCode;
+ setNode(fontIcon);
+ }
+
+ public void setIconCode(String iconCode) {
+ this.iconCode = iconCode;
+ fontIcon.setIconLiteral(iconCode);
+ }
+
+ public int getIconSize() {
+ return iconSize;
+ }
+
+ public void setIconSize(int iconSize) {
+ this.iconSize = iconSize;
+ fontIcon.setIconSize(iconSize);
+ }
+
+ public Paint getColor() {
+ return color;
+ }
+
+ public void setColor(Paint color) {
+ this.color = color;
+ fontIcon.setIconColor(color);
+ }
+
+ @Override
+ protected Node render() {
+ fontIcon.getStyleClass().clear();
+ fontIcon.setTranslateX(getX());
+ fontIcon.setTranslateY(getY());
+ if (iconCode != null) {
+ fontIcon.setIconLiteral(iconCode);
+ }
+ fontIcon.setIconSize(iconSize);
+ if (color != null) {
+ fontIcon.setIconColor(color);
+ }
+ return fontIcon;
+ }
+
+ public FontIcon getFontIcon() {
+ return fontIcon;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/me/piitex/engine/overlays/ImageOverlay.java b/src/main/java/me/piitex/engine/overlays/ImageOverlay.java
index 3448349..0133b41 100644
--- a/src/main/java/me/piitex/engine/overlays/ImageOverlay.java
+++ b/src/main/java/me/piitex/engine/overlays/ImageOverlay.java
@@ -53,17 +53,6 @@ public ImageOverlay(String imagePath) {
setNode(imageView);
}
- public ImageOverlay(String directory, String imagePath) {
- ImageLoader loader = new ImageLoader(directory, imagePath);
- this.image = loader.build();
- this.path = loader.getFile().getAbsolutePath();
- this.fileName = loader.getFile().getName();
- this.fitWidth = image.getWidth();
- this.fitHeight = image.getHeight();
- this.imageView = new ImageView();
- setNode(imageView);
- }
-
public ImageOverlay(Image image, double x, double y) {
this.image = image;
this.fitWidth = image.getWidth();
diff --git a/src/main/java/me/piitex/engine/overlays/MessageOverlay.java b/src/main/java/me/piitex/engine/overlays/MessageOverlay.java
index 5a6d249..18fe959 100644
--- a/src/main/java/me/piitex/engine/overlays/MessageOverlay.java
+++ b/src/main/java/me/piitex/engine/overlays/MessageOverlay.java
@@ -217,4 +217,11 @@ public void setMaxHeight(double h) {
this.maxHeight = h;
atlantafxMessage.setMaxHeight(h);
}
+
+ @Override
+ public void setMaxSize(double w, double h) {
+ this.maxWidth = w;
+ this.maxHeight = h;
+ atlantafxMessage.setMaxSize(w, h);
+ }
}
\ No newline at end of file
diff --git a/src/main/java/me/piitex/engine/overlays/NotificationOverlay.java b/src/main/java/me/piitex/engine/overlays/NotificationOverlay.java
index 19071e5..df9be66 100644
--- a/src/main/java/me/piitex/engine/overlays/NotificationOverlay.java
+++ b/src/main/java/me/piitex/engine/overlays/NotificationOverlay.java
@@ -137,4 +137,11 @@ public void setMaxHeight(double h) {
notification.setMaxHeight(h);
}
+ @Override
+ public void setMaxSize(double w, double h) {
+ this.maxWidth = w;
+ this.maxHeight = h;
+ notification.setMaxSize(w, h);
+ }
+
}
\ No newline at end of file
diff --git a/src/main/java/me/piitex/engine/overlays/Overlay.java b/src/main/java/me/piitex/engine/overlays/Overlay.java
index d832a67..6d5bf85 100644
--- a/src/main/java/me/piitex/engine/overlays/Overlay.java
+++ b/src/main/java/me/piitex/engine/overlays/Overlay.java
@@ -20,51 +20,45 @@
import java.util.List;
/**
- * An overlay is a visual element which can be rendered. The overlay class is the JavaFX equivalent of a {@link Node}.
- * All overlays have generic events that are fired. For example, the {@link OverlayClickEvent} is fired if the overlay is clicked.
+ * An overlay is a visual element which can be rendered. The overlay class is the framework's equivalent of a native JavaFX {@link Node}.
+ * All overlays have generic events that are fired. For example, the {@link ElementClickEvent} is fired if the overlay is clicked.
*
*
* To render an overlay you first need to add it to a {@link Container}. The container will have to be managed to a {@link Window}.
* The window is used to render the screen.
*
- * {@code
- * // Create the overlay
- * TextOverlay overlay = new TextOverlay("Text");
+ * {@code
+ * // Create the overlay
+ * TextOverlay overlay = new TextOverlay("Text");
*
- * // Create or fetch the container.
- * Container container = new EmptyContainer(x, y, width, height, displayOrder);
+ * // Create or fetch the container.
+ * Container container = new EmptyContainer(x, y, width, height, displayOrder);
*
- * // Add the overlay to the container.
- * container.addOverlay(overlay);
+ * // Add the overlay to the container's element map.
+ * container.addElement(overlay);
*
- * // Add the container to the window if needed.
- * window.addContainer(container);
- * }
+ * // Add the container to the window if needed.
+ * window.addContainer(container);
+ * }
*
*
- * Handling overlay events are key to creation a functional game. During the rendering process, logical programming must be executed with events.
+ * Handling overlay events are key to creating a functional application. During the rendering process, logical programming must be executed with events.
*
- * {@code
- * // Create the overlay
- * TextOverlay overlay = new TextOverlay("Text");
+ * {@code
+ * // Create the overlay
+ * TextOverlay overlay = new TextOverlay("Text");
*
- * // Handle code when the overlay is clicked.
- * overlay.onClick(event -> {
- * // Handle logic
- * System.out.println("The overlay was clicked!");
- * });
- * }
+ * // Handle code when the overlay is clicked.
+ * overlay.onClick(event -> {
+ * // Handle logic
+ * System.out.println("The overlay was clicked!");
+ * });
+ * }
*
*/
public abstract class Overlay extends Element {
private double x,y;
- private double scaleX, scaleY;
private String tooltip;
-
- private IOverlayHover iOverlayHover;
- private IOverlayHoverExit iOverlayHoverExit;
- private IOverlayClick iOverlayClick;
- private IOverlayClickRelease iOverlayClickRelease;
private IOverlaySubmit iOverlaySubmit;
private Cursor cursor;
@@ -75,22 +69,42 @@ public abstract class Overlay extends Element {
// Style classes.
private final List styles = new LinkedList<>();
+ /**
+ * Retrieves the explicit X coordinate offset of the overlay relative to its parent container.
+ * @return The horizontal layout position.
+ */
public double getX() {
return x;
}
+ /**
+ * Sets the explicit horizontal layout position of the overlay.
+ * Translates the underlying JavaFX node dynamically if it has already been assembled.
+ *
+ * @param x The new X layout constraint.
+ */
public void setX(double x) {
this.x = x;
if (getNode() != null) {
- getNode().setTranslateY(x);
+ getNode().setTranslateX(x); // Note: Corrected to setTranslateX from setTranslateY
}
}
+ /**
+ * Retrieves the explicit Y coordinate offset of the overlay relative to its parent container.
+ * @return The vertical layout position.
+ */
public double getY() {
return y;
}
+ /**
+ * Sets the explicit vertical layout position of the overlay.
+ * Translates the underlying JavaFX node dynamically if it has already been assembled.
+ *
+ * @param y The new Y layout constraint.
+ */
public void setY(double y) {
this.y = y;
@@ -99,100 +113,97 @@ public void setY(double y) {
}
}
+ /**
+ * Retrieves the configured tooltip text meant to display when a user hovers over this overlay.
+ * @return The string tooltip data, or null if unassigned.
+ */
public String getTooltip() {
return tooltip;
}
+ /**
+ * Assigns a text-based tooltip to this overlay. The tooltip framework integrates this automatically
+ * into the JavaFX node during the assembly phase.
+ *
+ * @param tooltip The informative hover text to display.
+ */
public void setTooltip(String tooltip) {
this.tooltip = tooltip;
}
- public double getScaleX() {
- return scaleX;
- }
-
- public void setScaleX(double scaleX) {
- this.scaleX = scaleX;
- }
-
- public double getScaleY() {
- return scaleY;
- }
-
- public void setScaleY(double scaleY) {
- this.scaleY = scaleY;
- }
-
- public void onClick(IOverlayClick iOverlayClick) {
- this.iOverlayClick = iOverlayClick;
- }
-
- public void onHover(IOverlayHover iOverlayHover) {
- this.iOverlayHover = iOverlayHover;
- }
-
- public void onClickRelease(IOverlayClickRelease iOverlayClickRelease) {
- this.iOverlayClickRelease = iOverlayClickRelease;
- }
-
- public void onHoverExit(IOverlayHoverExit iOverlayHoverExit) {
- this.iOverlayHoverExit = iOverlayHoverExit;
- }
-
+ /**
+ * Registers a specialized submit event handler, primarily utilized by overlays capable of receiving input
+ * (e.g., text areas capturing an ENTER key press without shift modifiers).
+ *
+ * @param iOverlaySubmit The execution logic mapped to the submit event.
+ */
public void onOverlaySubmit(IOverlaySubmit iOverlaySubmit) {
this.iOverlaySubmit = iOverlaySubmit;
}
- public IOverlayClick getOnClick() {
- return iOverlayClick;
- }
-
- public IOverlayHover getOnHover() {
- return iOverlayHover;
- }
-
- public IOverlayHoverExit getOnHoverExit() {
- return iOverlayHoverExit;
- }
-
- public IOverlayClickRelease getOnRelease() {
- return iOverlayClickRelease;
- }
-
- public IOverlayClick getiOverlayClick() {
- return iOverlayClick;
- }
-
+ /**
+ * Retrieves the collection of local CSS stylesheet files assigned explicitly to this specific overlay.
+ * @return A list of {@link File} references for styles.
+ */
public List getStyleSheets() {
return styleSheets;
}
+ /**
+ * Links an external CSS file specifically to this overlay instance.
+ * @param file The targeted local {@link File}.
+ */
public void addStyleSheet(File file) {
this.styleSheets.add(file);
}
+ /**
+ * Attaches a native JavaFX CSS class name string to this overlay.
+ * @param style The CSS class definition.
+ */
public void addStyle(String style) {
styles.add(style);
}
+ /**
+ * Retrieves the collection of CSS style classes currently mapped to the overlay logic.
+ * @return A list of CSS class name strings.
+ */
public List getStyles() {
return styles;
}
+ /**
+ * @return The current configured hover cursor mapping.
+ */
+ @Override
public Cursor getCursor() {
return cursor;
}
+ /**
+ * Overrides the default mouse cursor visualization when the mouse pointer bounds enter the overlay.
+ * @param cursor The required JavaFX {@link Cursor}.
+ */
+ @Override
public void setCursor(Cursor cursor) {
this.cursor = cursor;
}
/**
- * Converts the overlay into a {@link Node} which is used for the JavaFX API.
- * @return The converted {@link Node} for the overlay.
+ * Converts the logical engine overlay instructions exclusively into a raw {@link Node} which is used for the native JavaFX API.
+ * Subclasses define their specific node implementations here (e.g., ImageView, Text, TextField).
+ *
+ * @return The newly constructed native JavaFX {@link Node} representing the overlay data.
*/
protected abstract Node render();
+ /**
+ * The primary assembly lifecycle phase. Generates the underlying node and safely enforces JavaFX
+ * UI thread checks before assigning structural input/control event wrappers like tooltips and key binds.
+ *
+ * @return The fully compiled and interactive {@link Node}.
+ */
@Override
public Node assemble() {
Node node = render();
@@ -213,6 +224,12 @@ public Node assemble() {
return node;
}
+ /**
+ * Injects standard input configuration, cursors, hover delays, and text area key binds directly
+ * onto the native node's event listener chains. This must be invoked exclusively on the JavaFX Application thread.
+ *
+ * @param node The physically rendering JavaFX node.
+ */
public void setInputControls(Node node) {
if (cursor != null) {
node.setCursor(cursor);
@@ -236,37 +253,6 @@ public void setInputControls(Node node) {
}
}
- if (node.getOnDragEntered() == null) {
- node.setOnMouseEntered(event -> {
- //RenJava.getEventHandler().callEvent(new OverlayHoverEvent(this, event));
- if (getOnHover() != null) {
- getOnHover().onHover(new OverlayHoverEvent(this, event));
- }
- });
- }
- if (node.getOnMouseClicked() == null) {
- node.setOnMouseClicked(event -> {
- OverlayClickEvent overlayClickEvent = new OverlayClickEvent(this, event, event.getSceneX(), event.getSceneY());
- if (getOnClick() != null) {
- getOnClick().onClick(overlayClickEvent);
- }
- });
- }
- if (node.getOnMouseExited() == null) {
- node.setOnMouseExited(event -> {
- if (getOnHoverExit() != null) {
- getOnHoverExit().onHoverExit(new OverlayExitEvent(this, event));
- }
- });
- }
- if (node.getOnMouseReleased() == null) {
- node.setOnMouseReleased(event -> {
- if (getOnRelease() != null) {
- getOnRelease().onClickRelease(new OverlayClickReleaseEvent(this, event));
- }
- });
- }
-
if (node instanceof TextArea textArea) {
if (node.getOnKeyPressed() == null) {
node.setOnKeyPressed(event -> {
diff --git a/src/main/java/me/piitex/engine/overlays/PasswordFieldOverlay.java b/src/main/java/me/piitex/engine/overlays/PasswordFieldOverlay.java
index a2affb0..af2972f 100644
--- a/src/main/java/me/piitex/engine/overlays/PasswordFieldOverlay.java
+++ b/src/main/java/me/piitex/engine/overlays/PasswordFieldOverlay.java
@@ -147,4 +147,11 @@ public void setMaxHeight(double h) {
this.maxHeight = h;
textField.setMaxHeight(h);
}
+
+ @Override
+ public void setMaxSize(double w, double h) {
+ this.maxWidth = w;
+ this.maxHeight = h;
+ textField.setMaxSize(w, h);
+ }
}
diff --git a/src/main/java/me/piitex/engine/overlays/ProgressBarOverlay.java b/src/main/java/me/piitex/engine/overlays/ProgressBarOverlay.java
index fc72ef8..a993b45 100644
--- a/src/main/java/me/piitex/engine/overlays/ProgressBarOverlay.java
+++ b/src/main/java/me/piitex/engine/overlays/ProgressBarOverlay.java
@@ -107,4 +107,11 @@ public void setMaxHeight(double h) {
this.maxHeight = h;
progressBar.setMaxHeight(h);
}
+
+ @Override
+ public void setMaxSize(double w, double h) {
+ this.maxWidth = w;
+ this.maxHeight = h;
+ progressBar.setMaxSize(w, h);
+ }
}
diff --git a/src/main/java/me/piitex/engine/overlays/Region.java b/src/main/java/me/piitex/engine/overlays/Region.java
index 27fad2e..aba04bc 100644
--- a/src/main/java/me/piitex/engine/overlays/Region.java
+++ b/src/main/java/me/piitex/engine/overlays/Region.java
@@ -21,4 +21,5 @@ public interface Region {
void setMaxWidth(double w);
void setMaxHeight(double h);
+ void setMaxSize(double w, double h);
}
diff --git a/src/main/java/me/piitex/engine/overlays/RichTextAreaOverlay.java b/src/main/java/me/piitex/engine/overlays/RichTextAreaOverlay.java
index 2940721..db75317 100644
--- a/src/main/java/me/piitex/engine/overlays/RichTextAreaOverlay.java
+++ b/src/main/java/me/piitex/engine/overlays/RichTextAreaOverlay.java
@@ -166,6 +166,16 @@ public ITextAreaSubmit getiOverlaySubmit() {
public void onSubmit(ITextAreaSubmit iOverlaySubmit) {
this.iOverlaySubmit = iOverlaySubmit;
+ textArea.addEventFilter(KeyEvent.KEY_PRESSED, keyEvent -> {
+ if (keyEvent.getCode() == KeyCode.ENTER) {
+ keyEvent.consume();
+ if (keyEvent.isShiftDown()) {
+ textArea.insertText(textArea.getCaretPosition(), "\n");
+ } else {
+ getiOverlaySubmit().onSubmit(new TextAreaSubmitEvent(this, textArea.getText()));
+ }
+ }
+ });
}
@Override
@@ -305,20 +315,6 @@ public Node render() {
}, 200, TimeUnit.MILLISECONDS);
}
- if (getiOverlaySubmit() != null) {
- textArea.addEventFilter(KeyEvent.KEY_PRESSED, keyEvent -> {
- if (keyEvent.getCode() == KeyCode.ENTER) {
- keyEvent.consume();
- if (keyEvent.isShiftDown()) {
- textArea.insertText(textArea.getCaretPosition(), "\n");
- } else {
- getiOverlaySubmit().onSubmit(new TextAreaSubmitEvent(this, textArea.getText()));
- }
- }
- });
- }
-
-
return textArea;
}
@@ -480,4 +476,11 @@ public void setMaxHeight(double h) {
this.maxHeight = h;
textArea.setMaxHeight(h);
}
+
+ @Override
+ public void setMaxSize(double w, double h) {
+ this.maxWidth = w;
+ this.maxHeight = h;
+ textArea.setMaxSize(w, h);
+ }
}
diff --git a/src/main/java/me/piitex/engine/overlays/RingProgressOverlay.java b/src/main/java/me/piitex/engine/overlays/RingProgressOverlay.java
new file mode 100644
index 0000000..28bc1a8
--- /dev/null
+++ b/src/main/java/me/piitex/engine/overlays/RingProgressOverlay.java
@@ -0,0 +1,117 @@
+package me.piitex.engine.overlays;
+
+import atlantafx.base.controls.RingProgressIndicator;
+import javafx.beans.value.ObservableValue;
+import javafx.scene.Node;
+
+public class RingProgressOverlay extends Overlay implements Region {
+ private double width, height, prefWidth, prefHeight, maxWidth, maxHeight;
+ private double scaleWidth, scaleHeight;
+ private final RingProgressIndicator progressBar;
+
+ public RingProgressOverlay() {
+ this.progressBar = new RingProgressIndicator();
+ setNode(progressBar);
+ }
+
+ public void bind(ObservableValue extends Number> binding) {
+ progressBar.progressProperty().bind(binding);
+ }
+
+ @Override
+ public Node render() {
+ progressBar.setTranslateX(getX());
+ progressBar.setTranslateY(getY());
+
+ if (getWidth() > 0 || getHeight() > 0) {
+ progressBar.setMinSize(width, height);
+ }
+ if (getPrefWidth() > 0 || getPrefHeight() > 0) {
+ progressBar.setPrefSize(getPrefWidth(), getPrefHeight());
+ }
+ if (getMaxWidth() > 0 || getMaxHeight() > 0) {
+ progressBar.setMaxSize(getMaxWidth(), getMaxHeight());
+ }
+
+ progressBar.getStyleClass().addAll(getStyles());
+
+ return progressBar;
+ }
+
+ public RingProgressIndicator getProgressBar() {
+ return progressBar;
+ }
+
+ @Override
+ public double getWidth() {
+ return width;
+ }
+
+ @Override
+ public double getHeight() {
+ return height;
+ }
+
+ @Override
+ public void setWidth(double w) {
+ this.width = w;
+ progressBar.setMinWidth(w);
+ }
+
+ @Override
+ public void setHeight(double h) {
+ this.height = h;
+ progressBar.setMinHeight(h);
+ }
+
+ @Override
+ public double getPrefWidth() {
+ return prefWidth;
+ }
+
+ @Override
+ public double getPrefHeight() {
+ return prefHeight;
+ }
+
+ @Override
+ public void setPrefWidth(double w) {
+ this.prefWidth = w;
+ progressBar.setPrefWidth(w);
+ }
+
+ @Override
+ public void setPrefHeight(double h) {
+ this.prefHeight = h;
+ progressBar.setPrefHeight(h);
+ }
+
+ @Override
+ public double getMaxWidth() {
+ return maxWidth;
+ }
+
+ @Override
+ public double getMaxHeight() {
+ return maxHeight;
+ }
+
+ @Override
+ public void setMaxWidth(double w) {
+ this.maxWidth = w;
+ progressBar.setMaxWidth(w);
+ }
+
+ @Override
+ public void setMaxHeight(double h) {
+ this.maxHeight = h;
+ progressBar.setMaxHeight(h);
+ }
+
+ @Override
+ public void setMaxSize(double w, double h) {
+ this.maxWidth = w;
+ this.maxHeight = h;
+ progressBar.setMaxSize(w, h);
+ }
+}
diff --git a/src/main/java/me/piitex/engine/overlays/SeparatorOverlay.java b/src/main/java/me/piitex/engine/overlays/SeparatorOverlay.java
index 87a784f..5e80301 100644
--- a/src/main/java/me/piitex/engine/overlays/SeparatorOverlay.java
+++ b/src/main/java/me/piitex/engine/overlays/SeparatorOverlay.java
@@ -8,7 +8,6 @@ public class SeparatorOverlay extends Overlay implements Region {
private final Separator separator;
private Orientation orientation;
private double width, height, prefWidth, prefHeight, maxWidth, maxHeight;
- private double scaleWidth, scaleHeight;
public SeparatorOverlay(Orientation orientation) {
this.separator = new Separator(orientation);
@@ -90,6 +89,13 @@ public void setMaxHeight(double h) {
separator.setMaxHeight(h);
}
+ @Override
+ public void setMaxSize(double w, double h) {
+ this.maxWidth = w;
+ this.maxHeight = h;
+ separator.setMaxSize(w, h);
+ }
+
@Override
public Node render() {
separator.getStyleClass().addAll(getStyles());
diff --git a/src/main/java/me/piitex/engine/overlays/SliderOverlay.java b/src/main/java/me/piitex/engine/overlays/SliderOverlay.java
index 9442881..96ac019 100644
--- a/src/main/java/me/piitex/engine/overlays/SliderOverlay.java
+++ b/src/main/java/me/piitex/engine/overlays/SliderOverlay.java
@@ -1,5 +1,6 @@
package me.piitex.engine.overlays;
+import atlantafx.base.controls.ProgressSliderSkin;
import javafx.scene.Node;
import javafx.scene.control.Slider;
import me.piitex.engine.hanlders.events.SliderChangeEvent;
@@ -7,15 +8,14 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.io.File;
-import java.net.MalformedURLException;
-
public class SliderOverlay extends Overlay implements Region {
private final Slider slider;
- private double width, height, prefWidth, prefHeight, maxWidth, maxHeight;
private double maxValue, minValue, currentValue;
private double blockIncrement;
+ private boolean tickLabels = false;
+ private ProgressSliderSkin sliderSkin;
private ISliderChange sliderChange;
+ private double width, height, prefWidth, prefHeight, maxWidth, maxHeight;
private static final Logger logger = LoggerFactory.getLogger(SliderOverlay.class);
@@ -36,6 +36,10 @@ public SliderOverlay(double minValue, double maxValue, double currentValue, doub
setHeight(height);
}
+ public Slider getSlider() {
+ return slider;
+ }
+
public double getMaxValue() {
return maxValue;
}
@@ -72,6 +76,22 @@ public void setBlockIncrement(double blockIncrement) {
slider.setBlockIncrement(blockIncrement);
}
+ public boolean isTickLabels() {
+ return tickLabels;
+ }
+
+ public void setTickLabels(boolean tickLabels) {
+ this.tickLabels = tickLabels;
+ }
+
+ public ProgressSliderSkin getSliderSkin() {
+ return sliderSkin;
+ }
+
+ public void setSliderSkin(ProgressSliderSkin sliderSkin) {
+ this.sliderSkin = sliderSkin;
+ }
+
public ISliderChange getSliderChange() {
return sliderChange;
}
@@ -82,45 +102,14 @@ public void onSliderMove(ISliderChange sliderChange) {
@Override
public Node render() {
- if (getWidth() > 0) {
- slider.setMinWidth(width);
- }
-
- if (getHeight() > 0) {
- slider.setMinHeight(height);
- }
-
- if (getPrefWidth() > 0) {
- slider.setPrefWidth(prefWidth);
- }
-
- if (getPrefHeight() > 0) {
- slider.setPrefHeight(prefHeight);
- }
-
- if (getMaxWidth() > 0) {
- slider.setMaxWidth(maxWidth);
- }
-
- if (getMaxHeight() > 0) {
- slider.setMaxHeight(maxHeight);
- }
slider.setTranslateX(getX());
slider.setTranslateY(getY());
slider.setBlockIncrement(blockIncrement);
- // To design sliders we NEED a css file which contains the styling. I'm not able to inline this via code which sucks.
- // Hopefully the slider gets improvements in JavaFX.
-
- // Check if they have css files
- File sliderCss = new File(System.getProperty("user.dir") + "/game/css/slider.css");
- try {
- slider.getStylesheets().add(sliderCss.toURI().toURL().toExternalForm());
- } catch (MalformedURLException e) {
- logger.error("Could not css file for the slider.", e);
- }
- // Handle slider events
- slider.setOnMouseDragged(event -> {
- sliderChange.onSliderChange(new SliderChangeEvent(this, slider.getValue()));
+ slider.setSkin(new ProgressSliderSkin(slider));
+ slider.valueProperty().addListener((_, oldValue, newValue) -> {
+ if (getSliderChange() != null) {
+ getSliderChange().onSliderChange(new SliderChangeEvent(this, newValue.doubleValue(), oldValue.doubleValue()));
+ }
});
return slider;
@@ -182,6 +171,7 @@ public double getMaxHeight() {
@Override
public void setMaxWidth(double w) {
+ this.maxWidth = w;
slider.setMaxWidth(w);
}
@@ -190,4 +180,11 @@ public void setMaxHeight(double h) {
this.maxHeight = h;
slider.setMaxHeight(h);
}
+
+ @Override
+ public void setMaxSize(double w, double h) {
+ this.maxWidth = w;
+ this.maxHeight = h;
+ slider.setMaxSize(w, h);
+ }
}
diff --git a/src/main/java/me/piitex/engine/overlays/SpinnerNumberOverlay.java b/src/main/java/me/piitex/engine/overlays/SpinnerNumberOverlay.java
index 997c228..1b165dc 100644
--- a/src/main/java/me/piitex/engine/overlays/SpinnerNumberOverlay.java
+++ b/src/main/java/me/piitex/engine/overlays/SpinnerNumberOverlay.java
@@ -156,4 +156,11 @@ public void setMaxHeight(double h) {
this.maxHeight = h;
spinner.setMaxHeight(h);
}
+
+ @Override
+ public void setMaxSize(double w, double h) {
+ this.maxWidth = w;
+ this.maxHeight = h;
+ spinner.setMaxSize(w, h);
+ }
}
diff --git a/src/main/java/me/piitex/engine/overlays/TextAreaOverlay.java b/src/main/java/me/piitex/engine/overlays/TextAreaOverlay.java
index 92ae38e..bcd76e8 100644
--- a/src/main/java/me/piitex/engine/overlays/TextAreaOverlay.java
+++ b/src/main/java/me/piitex/engine/overlays/TextAreaOverlay.java
@@ -22,14 +22,16 @@ public class TextAreaOverlay extends Overlay implements Region {
private IInputSetEvent iInputSetEvent;
private ITextAreaSubmit iTextAreaSubmit;
- public TextAreaOverlay(String defaultInput, double x, double y, double width, double height) {
- this.textArea = new TextArea(defaultInput);
- this.defaultInput = defaultInput;
- this.width = width;
- this.height = height;
- setNode(textArea);
- setX(x);
- setY(y);
+ public TextAreaOverlay(String defaultInput) {
+ this(defaultInput, "", 0, 0, 0, 0);
+ }
+
+ public TextAreaOverlay(String defaultInput, String hintText) {
+ this(defaultInput, hintText, 0, 0, 0, 0);
+ }
+
+ public TextAreaOverlay(String defaultInput, String hintText, double width, double height) {
+ this(defaultInput, hintText, 0, 0, width, height);
}
public TextAreaOverlay(String defaultInput, String hintText, double x, double y, double width, double height) {
@@ -175,6 +177,13 @@ public void setMaxHeight(double h) {
textArea.setMaxHeight(h);
}
+ @Override
+ public void setMaxSize(double w, double h) {
+ this.maxWidth = w;
+ this.maxHeight = h;
+ textArea.setMaxSize(w, h);
+ }
+
public IInputSetEvent getiInputSetEvent() {
return iInputSetEvent;
}
diff --git a/src/main/java/me/piitex/engine/overlays/InputFieldOverlay.java b/src/main/java/me/piitex/engine/overlays/TextFieldOverlay.java
similarity index 82%
rename from src/main/java/me/piitex/engine/overlays/InputFieldOverlay.java
rename to src/main/java/me/piitex/engine/overlays/TextFieldOverlay.java
index 2da2c3f..9411779 100644
--- a/src/main/java/me/piitex/engine/overlays/InputFieldOverlay.java
+++ b/src/main/java/me/piitex/engine/overlays/TextFieldOverlay.java
@@ -7,33 +7,37 @@
import me.piitex.engine.loaders.FontLoader;
import me.piitex.engine.overlays.events.IInputSetEvent;
-public class InputFieldOverlay extends Overlay implements Region {
+public class TextFieldOverlay extends Overlay implements Region {
private final TextField textField;
private double width, height, prefWidth, prefHeight, maxWidth, maxHeight;
- private double scaleWidth, scaleHeight;
private final String defaultInput;
private FontLoader fontLoader;
private String hintText = "";
private IInputSetEvent iInputSetEvent;
- public InputFieldOverlay(String defaultInput, double x, double y, double width, double height) {
- this.textField = new TextField();
- this.defaultInput = defaultInput;
- this.width = width;
- this.height = height;
- setNode(textField);
- setX(x);
- setY(y);
+ public TextFieldOverlay(String defaultInput, String hintText) {
+ this(defaultInput, hintText, 0, 0, 0, 0);
}
- public InputFieldOverlay(String defaultInput, String hintText, double x, double y, double width, double height) {
+
+ public TextFieldOverlay(String defaultInput, String hintText, double width, double height) {
+ this(defaultInput, hintText, 0, 0, width, height);
+ }
+
+ public TextFieldOverlay(String defaultInput, double x, double y, double width, double height) {
+ this(defaultInput, "", x, y, width, height);
+ }
+
+ public TextFieldOverlay(String defaultInput, String hintText, double x, double y, double width, double height) {
this.textField = new TextField();
this.defaultInput = defaultInput;
this.hintText = hintText;
this.width = width;
this.height = height;
setNode(textField);
+ textField.setText(defaultInput);
+ textField.setPromptText(hintText);
setX(x);
setY(y);
}
@@ -84,11 +88,7 @@ public Node render() {
if (getMaxWidth() > 0 || getMaxHeight() > 0) {
textField.setMaxSize(getMaxWidth(), getMaxHeight());
}
- textField.setPromptText(hintText);
- textField.setText(defaultInput);
textField.setAlignment(Pos.TOP_LEFT);
-
-
textField.textProperty().addListener((observable, oldValue, newValue) -> {
if (getiInputSetEvent() != null) {
iInputSetEvent.onInputSet(new InputSetEvent(this, newValue));
@@ -163,6 +163,13 @@ public void setMaxHeight(double h) {
textField.setMaxHeight(h);
}
+ @Override
+ public void setMaxSize(double w, double h) {
+ this.maxWidth = w;
+ this.maxHeight = h;
+ textField.setMaxSize(w, h);
+ }
+
public IInputSetEvent getiInputSetEvent() {
return iInputSetEvent;
}
@@ -170,4 +177,8 @@ public IInputSetEvent getiInputSetEvent() {
public void onInputSetEvent(IInputSetEvent iInputSetEvent) {
this.iInputSetEvent = iInputSetEvent;
}
+
+ public TextField getTextField() {
+ return textField;
+ }
}
diff --git a/src/main/java/me/piitex/engine/overlays/TextFlowOverlay.java b/src/main/java/me/piitex/engine/overlays/TextFlowOverlay.java
index ca0d8ff..def034c 100644
--- a/src/main/java/me/piitex/engine/overlays/TextFlowOverlay.java
+++ b/src/main/java/me/piitex/engine/overlays/TextFlowOverlay.java
@@ -6,8 +6,11 @@
import javafx.scene.paint.Color;
import javafx.scene.text.FontSmoothingType;
import javafx.scene.text.Text;
+import javafx.scene.text.TextAlignment;
import javafx.scene.text.TextFlow;
import me.piitex.engine.loaders.FontLoader;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import java.util.LinkedList;
@@ -18,42 +21,40 @@ public class TextFlowOverlay extends Overlay implements Region {
private Color textFillColor;
private FontLoader font;
private FontSmoothingType fontSmoothingType = FontSmoothingType.GRAY;
+ private TextAlignment textAlignment;
private double width, height, prefWidth, prefHeight, maxWidth, maxHeight;
- private double scaleWidth, scaleHeight;
+ private static final Logger logger = LoggerFactory.getLogger(TextFlowOverlay.class);
public TextFlowOverlay(String text, double width, double height) {
- this.textFlow = new TextFlow();
- this.width = width;
- this.height = height;
- this.text = text;
- setNode(textFlow);
+ this(text, null, width, height);
}
- public TextFlowOverlay(String text, FontLoader fontLoader, double width, double height) {
+ public TextFlowOverlay(TextOverlay text, double width, double height) {
this.textFlow = new TextFlow();
- this.texts.add(new TextOverlay(text));
- this.font = fontLoader;
this.width = width;
this.height = height;
+ texts.add(text);
setNode(textFlow);
}
- public TextFlowOverlay(TextOverlay text, double width, double height) {
+ public TextFlowOverlay(LinkedList texts, double width, double height) {
this.textFlow = new TextFlow();
this.width = width;
this.height = height;
- texts.add(text);
+ this.texts = texts;
setNode(textFlow);
}
- public TextFlowOverlay(LinkedList texts, double width, double height) {
+ public TextFlowOverlay(String text, FontLoader fontLoader, double width, double height) {
this.textFlow = new TextFlow();
+ this.font = fontLoader;
this.width = width;
this.height = height;
- this.texts = texts;
setNode(textFlow);
+ setText(text);
}
+
public LinkedList getTexts() {
return texts;
}
@@ -120,6 +121,14 @@ public void setText(String text) {
}
}
+ public TextAlignment getTextAlignment() {
+ return textAlignment;
+ }
+
+ public void setTextAlignment(TextAlignment textAlignment) {
+ this.textAlignment = textAlignment;
+ }
+
public TextFlow getTextFlow() {
return textFlow;
}
@@ -129,6 +138,10 @@ public Node render() {
// Creates text that overflows over the box.
textFlow.getChildren().clear();
+ if (textAlignment != null) {
+ textFlow.setTextAlignment(textAlignment);
+ }
+
if (text != null) {
setText(text);
}
@@ -137,7 +150,7 @@ public Node render() {
// Check node type
switch (overlay) {
case TextOverlay text1 -> {
- text1.setText(text1.getText().replace("\\n", System.lineSeparator()));
+ text1.setText(text1.getTextNode().replace("\\n", System.lineSeparator()));
if (font != null && text1.getFontLoader() == null) {
// Passes
text1.setFont(font);
@@ -162,12 +175,12 @@ public Node render() {
button.setTextFill(textFillColor);
}
}
- case InputFieldOverlay inputField -> {
+ case TextFieldOverlay inputField -> {
if (font != null) {
inputField.setFont(font);
}
}
- default -> System.out.println("Unsupported overlay in TextFlow. {}" + overlay.toString());
+ default -> logger.error("Unsupported overlay in TextFlow. {}", overlay.toString());
}
Node node = overlay.assemble();
@@ -296,4 +309,11 @@ public void setMaxHeight(double h) {
this.maxHeight = h;
textFlow.setMaxHeight(h);
}
+
+ @Override
+ public void setMaxSize(double w, double h) {
+ this.maxWidth = w;
+ this.maxHeight = h;
+ textFlow.setMaxSize(w, h);
+ }
}
diff --git a/src/main/java/me/piitex/engine/overlays/TextOverlay.java b/src/main/java/me/piitex/engine/overlays/TextOverlay.java
index d07fbff..5138b48 100644
--- a/src/main/java/me/piitex/engine/overlays/TextOverlay.java
+++ b/src/main/java/me/piitex/engine/overlays/TextOverlay.java
@@ -4,56 +4,52 @@
import javafx.scene.paint.Color;
import javafx.scene.text.FontSmoothingType;
import javafx.scene.text.Text;
+import javafx.scene.text.TextAlignment;
import me.piitex.engine.loaders.FontLoader;
import org.kordamp.ikonli.javafx.FontIcon;
public class TextOverlay extends Overlay {
- private final Node node;
private String string;
private Color textFillColor;
private FontLoader fontLoader;
private FontSmoothingType fontSmoothingType;
+ private TextAlignment textAlignment;
private boolean strikeout, underline;
-
+ private final Text textNode;
public TextOverlay(String text) {
this.string = text;
- this.node = new Text();
- setNode(node);
- }
-
- public TextOverlay(FontIcon icon) {
- this.node = icon;
- setNode(node);
+ this.textNode = new Text();
+ setNode(textNode);
}
public TextOverlay(String text, FontLoader fontLoader) {
this.string = text;
this.fontLoader = fontLoader;
- this.node = new Text();
- setNode(node);
+ this.textNode = new Text();
+ setNode(textNode);
}
public TextOverlay(String text, Color textFillColor) {
this.string = text;
this.textFillColor = textFillColor;
- this.node = new Text();
- setNode(node);
+ this.textNode = new Text();
+ setNode(textNode);
}
public TextOverlay(String text, Color textFillColor, FontLoader fontLoader) {
this.string = text;
this.textFillColor = textFillColor;
this.fontLoader = fontLoader;
- this.node = new Text();
- setNode(node);
+ this.textNode = new Text();
+ setNode(textNode);
}
public TextOverlay(String text, FontLoader fontLoader, double x, double y) {
this.string = text;
this.fontLoader = fontLoader;
- this.node = new Text();
- setNode(node);
+ this.textNode = new Text();
+ setNode(textNode);
setX(x);
setY(y);
}
@@ -62,8 +58,8 @@ public TextOverlay(String text, Color textFillColor, FontLoader fontLoader, int
this.string = text;
this.textFillColor = textFillColor;
this.fontLoader = fontLoader;
- this.node = new Text();
- setNode(node);
+ this.textNode = new Text();
+ setNode(textNode);
setX(x);
setY(y);
}
@@ -78,47 +74,40 @@ public void setFontSmoothingType(FontSmoothingType fontSmoothingType) {
@Override
public Node render() {
- if (node instanceof FontIcon icon) {
- if (textFillColor != null) {
- icon.setIconColor(textFillColor);
- }
- } else if (node instanceof Text text) {
- // If text is set, render a Text node.
- text.setText(string);
- text.setFontSmoothingType(fontSmoothingType);
- if (textFillColor != null) {
- text.setFill(textFillColor);
- }
-
- if (fontLoader != null) {
- text.setFont(fontLoader.getFont());
- }
- if (getTextFillColor() != null) {
- text.setFill(getTextFillColor());
- }
- text.setStrikethrough(strikeout);
- text.setUnderline(underline);
+ // If text is set, render a Text node.
+ textNode.setText(string);
+ textNode.setFontSmoothingType(fontSmoothingType);
+ if (textFillColor != null) {
+ textNode.setFill(textFillColor);
}
- if (node != null) {
- node.setTranslateX(getX());
- node.setTranslateY(getY());
- node.getStyleClass().addAll(getStyles());
+ if (fontLoader != null) {
+ textNode.setFont(fontLoader.getFont());
+ }
+ if (getTextFillColor() != null) {
+ textNode.setFill(getTextFillColor());
+ }
+ if (textAlignment != null) {
+ textNode.setTextAlignment(textAlignment);
}
+ textNode.setStrikethrough(strikeout);
+ textNode.setUnderline(underline);
- return node;
+
+ textNode.setTranslateX(getX());
+ textNode.setTranslateY(getY());
+ textNode.getStyleClass().addAll(getStyles());
+
+ return textNode;
}
- public String getText() {
+ public String getTextNode() {
return string;
}
public void setText(String text) {
this.string = text;
-
- if (node instanceof Text t) {
- t.setText(text);
- }
+ textNode.setText(text);
}
public boolean isStrikeout() {
@@ -127,10 +116,7 @@ public boolean isStrikeout() {
public void setStrikeout(boolean strikeout) {
this.strikeout = strikeout;
-
- if (node instanceof Text t) {
- t.setStrikethrough(strikeout);
- }
+ textNode.setStrikethrough(strikeout);
}
public boolean isUnderline() {
@@ -139,10 +125,7 @@ public boolean isUnderline() {
public void setUnderline(boolean underline) {
this.underline = underline;
-
- if (node instanceof Text t) {
- t.setUnderline(strikeout);
- }
+ textNode.setUnderline(strikeout);
}
public FontLoader getFontLoader() {
@@ -151,10 +134,7 @@ public FontLoader getFontLoader() {
public void setFont(FontLoader fontLoader) {
this.fontLoader = fontLoader;
-
- if (node instanceof Text t) {
- t.setFont(fontLoader.getFont());
- }
+ textNode.setFont(fontLoader.getFont());
}
public Color getTextFillColor() {
@@ -163,10 +143,12 @@ public Color getTextFillColor() {
public void setTextFill(Color textFillColor) {
this.textFillColor = textFillColor;
+ textNode.setFill(textFillColor);
+ }
- if (node instanceof Text t) {
- t.setFill(textFillColor);
- }
+ public void setTextAlignment(TextAlignment textAlignment) {
+ this.textAlignment = textAlignment;
+ textNode.setTextAlignment(textAlignment);
}
}
diff --git a/src/main/java/me/piitex/engine/overlays/events/IOverlayClick.java b/src/main/java/me/piitex/engine/overlays/events/IOverlayClick.java
index 90ed213..4656411 100644
--- a/src/main/java/me/piitex/engine/overlays/events/IOverlayClick.java
+++ b/src/main/java/me/piitex/engine/overlays/events/IOverlayClick.java
@@ -1,8 +1,8 @@
package me.piitex.engine.overlays.events;
-import me.piitex.engine.hanlders.events.OverlayClickEvent;
+import me.piitex.engine.hanlders.events.ElementClickEvent;
public interface IOverlayClick {
- void onClick(OverlayClickEvent event);
+ void onClick(ElementClickEvent event);
}
diff --git a/src/main/java/me/piitex/engine/overlays/events/IOverlayClickRelease.java b/src/main/java/me/piitex/engine/overlays/events/IOverlayClickRelease.java
index 3782f9f..239a038 100644
--- a/src/main/java/me/piitex/engine/overlays/events/IOverlayClickRelease.java
+++ b/src/main/java/me/piitex/engine/overlays/events/IOverlayClickRelease.java
@@ -1,8 +1,8 @@
package me.piitex.engine.overlays.events;
-import me.piitex.engine.hanlders.events.OverlayClickReleaseEvent;
+import me.piitex.engine.hanlders.events.ElementClickReleaseEvent;
public interface IOverlayClickRelease {
- void onClickRelease(OverlayClickReleaseEvent event);
+ void onClickRelease(ElementClickReleaseEvent event);
}
diff --git a/src/main/java/me/piitex/engine/overlays/events/IOverlayHover.java b/src/main/java/me/piitex/engine/overlays/events/IOverlayHover.java
index 66458d0..d44b565 100644
--- a/src/main/java/me/piitex/engine/overlays/events/IOverlayHover.java
+++ b/src/main/java/me/piitex/engine/overlays/events/IOverlayHover.java
@@ -1,8 +1,8 @@
package me.piitex.engine.overlays.events;
-import me.piitex.engine.hanlders.events.OverlayHoverEvent;
+import me.piitex.engine.hanlders.events.ElementHoverEvent;
public interface IOverlayHover {
- void onHover(OverlayHoverEvent event);
+ void onHover(ElementHoverEvent event);
}
diff --git a/src/main/java/me/piitex/engine/overlays/events/IOverlayHoverExit.java b/src/main/java/me/piitex/engine/overlays/events/IOverlayHoverExit.java
index e06f1bd..9fca4d8 100644
--- a/src/main/java/me/piitex/engine/overlays/events/IOverlayHoverExit.java
+++ b/src/main/java/me/piitex/engine/overlays/events/IOverlayHoverExit.java
@@ -1,8 +1,8 @@
package me.piitex.engine.overlays.events;
-import me.piitex.engine.hanlders.events.OverlayExitEvent;
+import me.piitex.engine.hanlders.events.ElementExitEvent;
public interface IOverlayHoverExit {
- void onHoverExit(OverlayExitEvent event);
+ void onHoverExit(ElementExitEvent event);
}
diff --git a/src/main/java/me/piitex/os/DownloadInfo.java b/src/main/java/me/piitex/os/DownloadInfo.java
index 68005b8..477516d 100644
--- a/src/main/java/me/piitex/os/DownloadInfo.java
+++ b/src/main/java/me/piitex/os/DownloadInfo.java
@@ -1,15 +1,19 @@
package me.piitex.os;
+import java.io.File;
+
public class DownloadInfo {
private final String fileName;
+ private final File output;
private final long totalFileSize; // Total size in bytes
private long downloadedBytes;
private double downloadProgress; // Between 0.0 and 1.0
private final String downloadUrl;
private boolean isComplete;
- public DownloadInfo(String fileName, long totalFileSize, String downloadUrl) {
+ public DownloadInfo(String fileName, File output, long totalFileSize, String downloadUrl) {
this.fileName = fileName;
+ this.output = output;
this.totalFileSize = totalFileSize;
this.downloadUrl = downloadUrl;
this.downloadedBytes = 0;
@@ -21,6 +25,10 @@ public String getFileName() {
return fileName;
}
+ public File getOutput() {
+ return output;
+ }
+
public long getTotalFileSize() {
return totalFileSize;
}
diff --git a/src/main/java/me/piitex/os/DownloadListener.java b/src/main/java/me/piitex/os/DownloadListener.java
index a6ae127..71f115a 100644
--- a/src/main/java/me/piitex/os/DownloadListener.java
+++ b/src/main/java/me/piitex/os/DownloadListener.java
@@ -31,7 +31,7 @@ public interface DownloadListener {
void onDownloadError(DownloadInfo info, Exception e);
/**
- * Called when the download is explicitly cancelled by the user via cancelDownload(url).
+ * Called when the download is explicitly canceled by the user via cancelDownload(url).
* @param info The state of the download at the time of cancellation.
*/
void onDownloadCancel(DownloadInfo info);
diff --git a/src/main/java/me/piitex/os/FileDownloader.java b/src/main/java/me/piitex/os/FileDownloader.java
index f46e729..991d3ce 100644
--- a/src/main/java/me/piitex/os/FileDownloader.java
+++ b/src/main/java/me/piitex/os/FileDownloader.java
@@ -10,6 +10,7 @@
import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
+import java.security.MessageDigest;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@@ -22,34 +23,57 @@ public class FileDownloader {
private final ConcurrentHashMap activeDownloads = new ConcurrentHashMap<>();
private final ConcurrentMap activeConnections = new ConcurrentHashMap<>();
private final Set listeners = Collections.newSetFromMap(new ConcurrentHashMap<>());
-
private final Map requestProperties = new HashMap<>();
+ private final ExecutorService executorService;
/**
* Initializes the FileDownloader and its thread pool.
*/
public FileDownloader() {
+ this.executorService = Executors.newCachedThreadPool();
+ }
+ /**
+ * Initializes the FileDownloader and adds GitHub API support.
+ * @param githubUrl Set to true to use GitHub API streams.
+ */
+ public FileDownloader(boolean githubUrl) {
+ this.executorService = Executors.newCachedThreadPool();
+ if (githubUrl) {
+ addRequestProperty("Accept", "application/octet-stream");
+ }
}
/**
- * Starts the download task asynchronously.
+ * Starts the download task asynchronously without hash checking.
* @param fileUrl The URL of the file to download.
* @param outputFile The destination of the file.
*/
public void startDownload(String fileUrl, File outputFile) {
+ startDownload(fileUrl, outputFile, null, null);
+ }
+
+ /**
+ * Starts the download task asynchronously with security hash checking.
+ * @param fileUrl The URL of the file to download.
+ * @param outputFile The destination of the file.
+ * @param expectedHash The expected checksum hash of the file (e.g., SHA-256).
+ * @param hashAlgorithm The algorithm used for the hash (e.g., "SHA-256", "MD5"). Defaults to SHA-256 if null.
+ */
+ public void startDownload(String fileUrl, File outputFile, String expectedHash, String hashAlgorithm) {
logger.info("Submitting download task for: {}", fileUrl);
+ // Call on the FX Thread
if (Platform.isFxApplicationThread()) {
Exception exception = new InvalidDownloadThreadException();
logger.error(exception.getMessage(), exception);
return;
}
- performDownload(fileUrl, outputFile);
+ executorService.submit(() -> performDownload(fileUrl, outputFile, expectedHash, hashAlgorithm));
}
- private void performDownload(String fileUrl, File outputFile) {
+ private void performDownload(String fileUrl, File outputFile, String expectedHash, String hashAlgorithm) {
DownloadInfo info = null;
try {
URL url = new URI(fileUrl).toURL();
@@ -57,17 +81,23 @@ private void performDownload(String fileUrl, File outputFile) {
connection.setConnectTimeout(5000);
requestProperties.forEach(connection::setRequestProperty);
- activeConnections.put(fileUrl, connection); // Store the connection
+ activeConnections.put(fileUrl, connection);
long fileSize = connection.getContentLengthLong();
String fileName = url.getFile().substring(url.getFile().lastIndexOf('/') + 1);
- // Initialize Info and Report Start
- info = new DownloadInfo(fileName, fileSize, fileUrl);
- activeDownloads.put(fileUrl, info); // Track active download
+ info = new DownloadInfo(fileName, outputFile, fileSize, fileUrl);
+ activeDownloads.put(fileUrl, info);
handleStart(info);
- //
+ // Ensure the parent directories exist before writing
+ File parentDir = outputFile.getParentFile();
+ if (parentDir != null && !parentDir.exists()) {
+ if (!parentDir.mkdirs()) {
+ throw new IOException("Failed to create parent directories for output file.");
+ }
+ }
+
try (InputStream inputStream = connection.getInputStream();
FileOutputStream outputStream = new FileOutputStream(outputFile)) {
@@ -76,41 +106,49 @@ private void performDownload(String fileUrl, File outputFile) {
long totalBytesDownloaded = 0;
long lastNotificationTime = System.currentTimeMillis();
- // Loop the download bytes until completed.
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
totalBytesDownloaded += bytesRead;
info.setDownloadedBytes(totalBytesDownloaded);
- // Throttle progress updates
if (System.currentTimeMillis() - lastNotificationTime > 200) {
handleProgress(info);
lastNotificationTime = System.currentTimeMillis();
}
}
- info.setComplete(true);
- handleProgress(info); // Final progress update
- handleComplete(info, outputFile);
-
} catch (IOException e) {
String message = e.getMessage() != null ? e.getMessage().toLowerCase() : "";
- // Check for controlled cancellation errors (stream/socket closure)
- // These messages signal that the stream was closed by cancelDownload(url)
if (message.contains("socket closed") || message.contains("stream closed") || message.contains("operation canceled")) {
- // This is a controlled stop. We don't treat it as a failure.
- // The handleCancel event has already fired.
logger.info("Download task for {} stopped by user or interruption.", info.getFileName());
+ return; // Exit early since it was cancelled
} else {
- handleError(info, e);
+ throw e; // Rethrow to be caught by the outer catch block
}
}
+ if (expectedHash != null && !expectedHash.trim().isEmpty()) {
+ String algorithm = (hashAlgorithm != null && !hashAlgorithm.trim().isEmpty()) ? hashAlgorithm : "SHA-256";
+ logger.info("Verifying file integrity using {}...", algorithm);
+
+ boolean isHashValid = verifyFileHash(outputFile, expectedHash, algorithm);
+ if (!isHashValid) {
+ if (outputFile.exists() && !outputFile.delete()) {
+ logger.warn("Failed to delete tampered file: {}", outputFile.getAbsolutePath());
+ }
+ throw new SecurityException("Hash mismatch! The file may be corrupted or maliciously altered.");
+ }
+ logger.info("File integrity verified successfully.");
+ }
+
+ info.setComplete(true);
+ handleProgress(info); // Final progress update
+ handleComplete(info, outputFile);
+
} catch (Exception e) {
- // URL/Connection setup error
if (info == null) {
- info = new DownloadInfo("Unknown", -1, fileUrl);
+ info = new DownloadInfo("Unknown", outputFile, -1, fileUrl);
}
handleError(info, e);
} finally {
@@ -121,30 +159,53 @@ private void performDownload(String fileUrl, File outputFile) {
}
}
+ /**
+ * Verifies the cryptographic hash of a downloaded file.
+ */
+ private boolean verifyFileHash(File file, String expectedHash, String algorithm) throws Exception {
+ MessageDigest digest = MessageDigest.getInstance(algorithm);
+
+ try (InputStream fis = new FileInputStream(file)) {
+ byte[] buffer = new byte[8192];
+ int bytesRead;
+ while ((bytesRead = fis.read(buffer)) != -1) {
+ digest.update(buffer, 0, bytesRead);
+ }
+ }
+
+ byte[] hashBytes = digest.digest();
+ StringBuilder hexString = new StringBuilder();
+
+ for (byte b : hashBytes) {
+ String hex = Integer.toHexString(0xff & b);
+ if (hex.length() == 1) {
+ hexString.append('0');
+ }
+ hexString.append(hex);
+ }
+
+ return hexString.toString().equalsIgnoreCase(expectedHash);
+ }
+
public boolean cancelDownload(String fileUrl) {
DownloadInfo info = activeDownloads.remove(fileUrl);
URLConnection connection = activeConnections.remove(fileUrl);
- // Force the stream/connection to close to unstick the I/O thread
if (connection != null) {
try {
if (connection instanceof HttpURLConnection) {
((HttpURLConnection) connection).disconnect();
}
} catch (Exception ignored) {
- // Ignore any exception on forced close
}
}
- // Interrupt the thread
- boolean wasRunning = false;
-
- // Call the cancel listeners
if (info != null) {
handleCancel(info);
+ return true;
}
- return wasRunning;
+ return false;
}
public long getRemoteFileSize(String fileUrl) throws IOException {
@@ -175,7 +236,7 @@ public void removeDownloadListener(DownloadListener listener) {
private void handleStart(DownloadInfo info) {
logger.info("Download Started: {}", info.getFileName());
- logger.info("Total Size: {}", (info.getTotalFileSize() > 0 ? info.getTotalFileSize() + "bytes" : "Unknown"));
+ logger.info("Total Size: {} bytes", (info.getTotalFileSize() > 0 ? info.getTotalFileSize() : "Unknown"));
for (DownloadListener listener : listeners) {
listener.onDownloadStart(info);
}
@@ -209,28 +270,31 @@ private void handleCancel(DownloadInfo info) {
}
}
- /**
- * Gathers and returns the download info for a specific file URL.
- * @param fileUrl The unique identifier (URL) of the download task.
- * @return The DownloadInfo object for that task, or null if it was never started.
- */
+
public DownloadInfo getDownloadInfo(String fileUrl) {
return activeDownloads.get(fileUrl);
}
- /**
- * @return A map of all tracked downloads (active, completed, or failed).
- */
public ConcurrentHashMap getAllDownloadStatuses() {
return activeDownloads;
}
+ public Set getListeners() {
+ return listeners;
+ }
+
/**
* Shuts down the executor service cleanly. Should be called when the application exits.
*/
public void shutdown() {
activeDownloads.clear();
listeners.clear();
+
+ // Shut down the thread pool to prevent application hang on exit
+ if (executorService != null && !executorService.isShutdown()) {
+ executorService.shutdownNow();
+ }
+
logger.info("Downloader service shut down.");
}
}
\ No newline at end of file
diff --git a/src/main/java/me/piitex/os/GitHubUtil.java b/src/main/java/me/piitex/os/GitHubUtil.java
index 523f542..dea594a 100644
--- a/src/main/java/me/piitex/os/GitHubUtil.java
+++ b/src/main/java/me/piitex/os/GitHubUtil.java
@@ -1,44 +1,32 @@
package me.piitex.os;
-import me.piitex.os.FileDownloader;
import org.json.JSONArray;
import org.json.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import java.io.*;
-import java.net.HttpURLConnection;
-import java.net.URL;
+import java.net.*;
/**
* Utility for GitHub REST API. Used for automatically updating and pulling information from releases and assets.
*/
public class GitHubUtil {
- private final String repositoryUrl; //https://api.github.com/repos/owner/repo/
+ private final String repositoryUrl;
+ private static final Logger logger = LoggerFactory.getLogger(GitHubUtil.class);
- /**
- * Initializes the base url for the repository. To get the repository link, use the following format.
- *
- * https://api.github.com/repos/$OWNER/$REPO/
- *
- * @param repositoryUrl The GitHub REST api repository link.
- */
public GitHubUtil(String repositoryUrl) {
this.repositoryUrl = repositoryUrl;
}
- /**
- * Gathers the latest release of the repository into a {@link JSONObject}.
- * @return {@link JSONObject} of the latest release.
- * @throws IOException If a connection cannot be made.
- */
- public JSONObject getLatestReleaseJson() throws IOException {
- URL url = new URL(repositoryUrl + "releases/latest");
+ public JSONObject getLatestReleaseJson() throws IOException, URISyntaxException {
+ URL url = new URI(repositoryUrl + "releases/latest").toURL();
+ logger.info("Fetching latest release request '{}'", url.toString());
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
-
+ connection.setConnectTimeout(1000);
connection.setRequestMethod("GET");
- int responseCode = connection.getResponseCode();
-
- if (responseCode == HttpURLConnection.HTTP_OK) {
+ if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
try (BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
String inputLine;
StringBuilder response = new StringBuilder();
@@ -51,20 +39,18 @@ public JSONObject getLatestReleaseJson() throws IOException {
return null;
}
- public int getLatestReleaseID() throws IOException {
+ public int getLatestReleaseID() throws IOException, URISyntaxException {
JSONObject object = getLatestReleaseJson();
- return object.getInt("id");
+ return object != null ? object.getInt("id") : -1;
}
- public JSONArray getReleaseAssets(int releaseID) throws IOException {
- URL url = new URL(repositoryUrl + "releases/" + releaseID + "/assets");
+ public JSONArray getReleaseAssets(int releaseID) throws IOException, URISyntaxException {
+ URL url = new URI(repositoryUrl + "releases/" + releaseID + "/assets").toURL();
+ logger.info("Fetching release asset '{}' '{}'", releaseID, url.toString());
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
-
connection.setRequestMethod("GET");
- int responseCode = connection.getResponseCode();
-
- if (responseCode == HttpURLConnection.HTTP_OK) {
+ if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
try (BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
String inputLine;
StringBuilder response = new StringBuilder();
@@ -74,11 +60,11 @@ public JSONArray getReleaseAssets(int releaseID) throws IOException {
return new JSONArray(response.toString());
}
} else {
- throw new RuntimeException("GitHub API request failed. Response Code: " + responseCode);
+ throw new RuntimeException("GitHub API request failed. Response Code: " + connection.getResponseCode());
}
}
- public JSONObject getReleaseAsset(int releaseID, String pattern) throws IOException {
+ public JSONObject getReleaseAsset(int releaseID, String pattern) throws IOException, URISyntaxException {
JSONArray array = getReleaseAssets(releaseID);
for (int i = 0; i < array.length(); i++) {
JSONObject asset = array.getJSONObject(i);
@@ -89,10 +75,100 @@ public JSONObject getReleaseAsset(int releaseID, String pattern) throws IOExcept
return null;
}
- public FileDownloader downloadAsset(int assetId, File output) throws IOException {
+ public JSONObject getAsset(int assetId) throws IOException, URISyntaxException {
+ URL url = new URI(repositoryUrl + "releases/assets/" + assetId).toURL();
+ logger.info("Fetching asset '{}' '{}'", assetId, url.toString());
+ HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+ connection.setRequestMethod("GET");
+
+ if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
+ try (BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
+ String inputLine;
+ StringBuilder response = new StringBuilder();
+ while ((inputLine = in.readLine()) != null) {
+ response.append(inputLine);
+ }
+ return new JSONObject(response.toString());
+ }
+ } else {
+ throw new RuntimeException("GitHub API request failed. Response Code: " + connection.getResponseCode());
+ }
+ }
+
+ public long getAssetFileSize(int assetId) throws URISyntaxException, IOException {
+ return getAsset(assetId).getLong("size");
+ }
+
+ /**
+ * Downloads an asset by ID. Extracts the hash directly from the asset's "digest" JSON field.
+ */
+ public FileDownloader downloadAsset(int assetId, File output, DownloadListener callback) {
FileDownloader downloader = new FileDownloader();
+ if (callback != null) {
+ downloader.addDownloadListener(callback);
+ }
downloader.addRequestProperty("Accept", "application/octet-stream");
- downloader.startDownload(repositoryUrl + "releases/" + "assets/" + assetId, output);
+
+ String expectedHash = null;
+ String hashAlgorithm = null;
+
+ try {
+ URL assetUrl = new URI(repositoryUrl + "releases/assets/" + assetId).toURL();
+ HttpURLConnection assetConn = (HttpURLConnection) assetUrl.openConnection();
+ assetConn.setRequestMethod("GET");
+ assetConn.setRequestProperty("Accept", "application/vnd.github.v3+json");
+
+ if (assetConn.getResponseCode() == HttpURLConnection.HTTP_OK) {
+ try (BufferedReader in = new BufferedReader(new InputStreamReader(assetConn.getInputStream()))) {
+ StringBuilder response = new StringBuilder();
+ String line;
+ while ((line = in.readLine()) != null) response.append(line);
+
+ JSONObject assetJson = new JSONObject(response.toString());
+ String digest = assetJson.optString("digest", "");
+
+ if (digest.contains(":")) {
+ String[] parts = digest.split(":", 2);
+ hashAlgorithm = formatAlgorithm(parts[0]);
+ expectedHash = parts[1];
+ }
+ }
+ }
+ } catch (Exception e) {
+ logger.warn("Failed to retrieve digest via GitHub API for asset ID {}. Proceeding without verification.", assetId, e);
+ }
+
+ // Only start download if a callback is present.
+ if (callback != null) {
+ if (expectedHash != null) {
+ logger.info("Found {} hash for asset {}: {}", hashAlgorithm, assetId, expectedHash);
+ downloader.startDownload(repositoryUrl + "releases/assets/" + assetId, output, expectedHash, hashAlgorithm);
+ } else {
+ logger.warn("No digest found for asset {}. Downloading without verification.", assetId);
+ downloader.startDownload(repositoryUrl + "releases/assets/" + assetId, output);
+ }
+ }
+
return downloader;
}
-}
+
+ /**
+ * Prepares a GitHub asset for download.
+ */
+ public DownloadInfo downloadAsset(int assetId, File output) throws URISyntaxException, IOException {
+ return new DownloadInfo(output.getName(), output, getAssetFileSize(assetId), repositoryUrl + "releases/assets/" + assetId);
+ }
+
+ /**
+ * Maps GitHub's algorithm string to standard Java MessageDigest algorithm names.
+ */
+ private String formatAlgorithm(String rawAlg) {
+ String upperAlg = rawAlg.toUpperCase();
+ return switch (upperAlg) {
+ case "SHA256" -> "SHA-256";
+ case "SHA512" -> "SHA-512";
+ case "SHA1" -> "SHA-1";
+ default -> upperAlg;
+ };
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/me/piitex/os/OSPathing.java b/src/main/java/me/piitex/os/OSPathing.java
index e40247b..dcd7d51 100644
--- a/src/main/java/me/piitex/os/OSPathing.java
+++ b/src/main/java/me/piitex/os/OSPathing.java
@@ -4,31 +4,30 @@
import org.slf4j.LoggerFactory;
import java.io.File;
+import java.io.IOException;
public class OSPathing {
private static final Logger logger = LoggerFactory.getLogger(OSPathing.class);
public static File getDocumentsDirectory() {
String userHome = System.getProperty("user.home");
- if (OSUtil.getOS().toLowerCase().contains("window")) {
- return new File(userHome + File.separator + "Documents");
- } else {
- return new File(userHome + File.separator + ".local" + File.separator + "share");
- }
+ return new File(userHome + File.separator + "Documents");
}
public static File getAppDataDirectory() {
String userHome = System.getProperty("user.home");
String os = OSUtil.getOS();
- if (os.equalsIgnoreCase("Windows")) {
+ if (os.contains("Windows")) {
String localAppData = System.getenv("APPDATA");
if (localAppData != null) {
return new File(localAppData);
}
+ } else if (os.contains("Linux")) {
+ return new File(userHome + "/.local/share/");
}
- return new File(userHome + File.separator + ".local" + File.separator + "share");
+ return new File(userHome + "/");
}
public static File getHomeDirectory() {
@@ -62,5 +61,4 @@ public static void initiateProjectDirectories(String project, boolean documents,
}
}
}
-
}
diff --git a/src/main/java/me/piitex/os/OSUtil.java b/src/main/java/me/piitex/os/OSUtil.java
index 8439e0a..6855e1a 100644
--- a/src/main/java/me/piitex/os/OSUtil.java
+++ b/src/main/java/me/piitex/os/OSUtil.java
@@ -1,17 +1,27 @@
package me.piitex.os;
-import org.apache.commons.lang3.SystemUtils;
+import oshi.SystemInfo;
+import oshi.hardware.GraphicsCard;
+import oshi.software.os.OperatingSystem;
+
+import java.util.List;
/**
* Simple utility for gathering operating system information.
*/
public class OSUtil {
+ private static final SystemInfo SYSTEM_INFO = new SystemInfo();
+ private static final OperatingSystem OS = SYSTEM_INFO.getOperatingSystem();
public static String getOS() {
- return SystemUtils.OS_NAME;
+ return OS.toString(); // Returns something like "Ubuntu 22.04.1 LTS"
+ }
+
+ public List getGraphicsCards() {
+ return SYSTEM_INFO.getHardware().getGraphicsCards();
}
- public static String getVersion() {
- return SystemUtils.OS_VERSION;
+ public long getGraphicsTotalVRAM(GraphicsCard graphicsCard) {
+ return graphicsCard.getVRam();
}
}
diff --git a/src/main/java/me/piitex/os/ProcessUtil.java b/src/main/java/me/piitex/os/ProcessUtil.java
index 96644c0..fd886c0 100644
--- a/src/main/java/me/piitex/os/ProcessUtil.java
+++ b/src/main/java/me/piitex/os/ProcessUtil.java
@@ -7,12 +7,15 @@
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
import java.util.Optional;
public class ProcessUtil {
private static final Logger logger = LoggerFactory.getLogger(ProcessUtil.class);
- private static final boolean VALIDATE_OS = OSUtil.getOS().toLowerCase().contains("window");
+ private static final boolean VALIDATE_OS = OSUtil.getOS().toLowerCase().contains("window") || OSUtil.getOS().toLowerCase().contains("linux");
static {
if (!VALIDATE_OS) {
@@ -26,22 +29,48 @@ public static boolean isProcessRunning(long pid) {
}
try {
- ProcessBuilder processBuilder = new ProcessBuilder(
- "tasklist",
- "/FI",
- "PID eq " + pid
- );
+ // Define the command based on the operating system
+ List command = new ArrayList<>();
+ if (OSUtil.getOS().contains("Windows")) {
+ // Command: tasklist /FI "PID eq [pid]"
+ command = Arrays.asList(
+ "tasklist",
+ "/FI",
+ "PID eq " + pid
+ );
+ } else if (OSUtil.getOS().contains("Linux")) {
+ command = Arrays.asList(
+ "ps",
+ "-p",
+ String.valueOf(pid)
+ );
+ }
+
+ ProcessBuilder processBuilder = new ProcessBuilder(command);
Process process = processBuilder.start();
- BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
- String line;
- while ((line = reader.readLine()) != null) {
- if (line.contains(" " + pid + " ")) { // Check for PID in the output
- return true;
+
+ if (OSUtil.getOS().contains("Windows")) {
+ BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
+ String line;
+ while ((line = reader.readLine()) != null) {
+ if (line.contains(" " + pid + " ") || line.startsWith(String.valueOf(pid))) {
+ return true;
+ }
+ }
+ return false;
+ } else if (OSUtil.getOS().contains("Linux")) {
+ BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
+ int lineCount = 0;
+ while (reader.readLine() != null) {
+ lineCount++;
}
+ return lineCount > 1;
+ } else {
+ return false;
}
- return false;
} catch (IOException e) {
- logger.error("Unable to check process condition!", e);
+ logger.error("Unable to check process condition for PID {}", pid, e);
+ Thread.currentThread().interrupt(); // Restore interrupted status
return false;
}
}
@@ -76,4 +105,8 @@ public static boolean killProcess(long pid) {
Optional processHandle = getRunningProcess(pid);
return processHandle.map(ProcessHandle::destroyForcibly).orElse(false);
}
+
+ public static boolean isValidOS() {
+ return VALIDATE_OS;
+ }
}
diff --git a/src/main/java/me/piitex/os/Version.java b/src/main/java/me/piitex/os/Version.java
new file mode 100644
index 0000000..e32f60e
--- /dev/null
+++ b/src/main/java/me/piitex/os/Version.java
@@ -0,0 +1,117 @@
+package me.piitex.os;
+
+import org.jetbrains.annotations.NotNull;
+
+public class Version implements Comparable {
+ private final String prefix;
+ private final double version;
+ private final String suffix;
+
+ /**
+ * Constructor for creating a version object. Used for comparing and updating third party software.
+ *
+ * Version Examples"
+ *
+ * 1.0
+ *
+ *
+ * v1.0
+ *
+ *
+ * 1.0-SNAPSHOT
+ *
+ *
+ * v1.0-SNAPSHOT
+ *
+ *
+ *
+ * The comparison of versions works by the order of the letters and numbers.
+ * Letters will be sorted ascended by alphabetical order. A is 0 and B is 1.
+ * The higher the number the greater the version. The version `0` will be the seen as the earliest possible version. The version `a0a` is the same as `0`.
+ *
+ * Versions schemes cannot change for the comparison. v1.0 /= 1.0-SNAPSHOT /= 1.0. Any changes made to versions schemes must be validated before parsing.
+ *
+ * @param prefix Optional prefix for the version. Example `a`
+ * @param version The version number as an Integer. Example `1234`
+ * @param suffix Optional suffix for the version. `Example b`
+ */
+ public Version(String prefix, int version, String suffix) {
+ this.prefix = prefix;
+ this.version = version;
+ this.suffix = suffix;
+ }
+
+ /**
+ * Constructor for creating a version object. Used for comparing and updating third party software.
+ *
+ * Version Examples"
+ *
+ * 1.0
+ *
+ *
+ * v1.0
+ *
+ *
+ * 1.0-SNAPSHOT
+ *
+ *
+ * v1.0-SNAPSHOT
+ *
+ *
+ *
+ * The comparison of versions works by the order of the letters and numbers.
+ * Letters will be sorted ascended by alphabetical order. A is 0 and B is 1.
+ * The higher the number the greater the version. The version `0` will be the seen as the earliest possible version. The version `a0a` is the same as `0`.
+ *
+ * Versions schemes cannot change for the comparison. v1.0 /= 1.0-SNAPSHOT /= 1.0. Any changes made to versions schemes must be validated before parsing.
+ *
+ * @param version The version number as an Integer. Example `1234`
+ */
+ public Version(int version) {
+ this.prefix = "";
+ this.version = version;
+ this.suffix = "";
+ }
+
+ public String getPrefix() {
+ return prefix;
+ }
+
+ public double getVersion() {
+ return version;
+ }
+
+ public String getSuffix() {
+ return suffix;
+ }
+
+ public int getCalculatedVersion() {
+ int total = 0;
+ for (char c : getPrefix().toCharArray()) {
+ total += getCharInt(c);
+ }
+ total += version;
+ for (char c : getSuffix().toCharArray()) {
+ total += getCharInt(c);
+ }
+ return total;
+ }
+
+ @Override
+ public int compareTo(@NotNull Version other) {
+ return getCalculatedVersion() - other.getCalculatedVersion();
+ }
+
+ private static int getCharInt(char c) {
+ char upperCaseLetter = Character.toUpperCase(c);
+
+ // Check if the character is an English alphabet letter
+ if (upperCaseLetter >= 'A' && upperCaseLetter <= 'Z') {
+ // Calculate the zero-based index: 'A' maps to 0
+ return upperCaseLetter - 'A';
+ } else {
+ // Handle non-alphabet characters (e.g., space, numbers)
+ return 0;
+ }
+ }
+}
diff --git a/src/main/java/me/piitex/os/VersionUtil.java b/src/main/java/me/piitex/os/VersionUtil.java
new file mode 100644
index 0000000..2233a16
--- /dev/null
+++ b/src/main/java/me/piitex/os/VersionUtil.java
@@ -0,0 +1,36 @@
+package me.piitex.os;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class VersionUtil {
+ private static final Pattern VERSION_PATTERN = Pattern.compile("^([a-zA-Z]*)(\\d+)([a-zA-Z]*)$");
+
+ public static Version parseVersion(String version) {
+ if (version.contains("-")) {
+ version = version.replace("-", ""); // Dashes are not valid characters for versions.
+ // Example: project-1.0-snapshot -> project1.0snapshot
+ // Prefix: project
+ // Version: 1.0
+ // Suffix: snapshot
+ }
+ version = version.replace(".", "");
+
+ Matcher matcher = VERSION_PATTERN.matcher(version);
+
+ if (matcher.matches()) {
+ // Group 1: Prefix (e.g., "v" from "v12a")
+ String prefix = matcher.group(1);
+
+ // Group 2: Numerical Value (e.g., "12" from "v12.1.2a")
+ // We use Integer.parseInt() on the string captured by this group.
+ int numericalValue = Integer.parseInt(matcher.group(2));
+
+ // Group 3: Suffix (e.g., "a" from "v12a")
+ String suffix = matcher.group(3);
+ return new Version(prefix, numericalValue, suffix);
+ } else {
+ return new Version("", Integer.parseInt(version), "");
+ }
+ }
+}
diff --git a/src/main/java/me/piitex/os/ZipUtil.java b/src/main/java/me/piitex/os/ZipUtil.java
new file mode 100644
index 0000000..fe5e8e5
--- /dev/null
+++ b/src/main/java/me/piitex/os/ZipUtil.java
@@ -0,0 +1,110 @@
+package me.piitex.os;
+
+import org.codehaus.plexus.archiver.tar.TarGZipUnArchiver;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.*;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import java.util.zip.ZipOutputStream;
+
+public class ZipUtil {
+ private static final Logger logger = LoggerFactory.getLogger(ZipUtil.class);
+
+ public static void unzipFile(File zip, File outDirectory) throws IOException {
+ if (!outDirectory.exists()) {
+ outDirectory.mkdir();
+ }
+ try (ZipInputStream zipIn = new ZipInputStream(new BufferedInputStream(new FileInputStream(zip)))) {
+ ZipEntry entry = zipIn.getNextEntry();
+ // iterates over entries in the zip file
+ while (entry != null) {
+ if (!entry.isDirectory()) {
+ // if the entry is a file, extract it
+ extractFile(entry.getName(), zipIn, outDirectory);
+ } else {
+ // if the entry is a directory, create the directory
+ File dir = new File(outDirectory, entry.getName());
+ dir.mkdir();
+ }
+ zipIn.closeEntry();
+ entry = zipIn.getNextEntry();
+ }
+ }
+ }
+
+ private static void extractFile(String name, ZipInputStream zipIn, File output) throws IOException {
+ File newFile = new File(output, name);
+ newFile.getParentFile().mkdirs();
+ try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(newFile))) {
+ byte[] bytesIn = new byte[4096];
+ int read;
+ while ((read = zipIn.read(bytesIn)) != -1) {
+ bos.write(bytesIn, 0, read);
+ }
+ }
+ }
+
+ public static void unzipTarGzFile(File tarGz, File outDirectory) {
+ if (!outDirectory.exists()) {
+ outDirectory.mkdir();
+ }
+
+ TarGZipUnArchiver unArchiver = new TarGZipUnArchiver();
+ unArchiver.setSourceFile(tarGz);
+ unArchiver.setDestDirectory(outDirectory);
+ unArchiver.extract();
+ }
+
+ public static void zipDirectory(File zipFile, File directory) {
+ if (!directory.isDirectory()) {
+ return;
+ }
+
+ if (zipFile.exists()) {
+ throw new RuntimeException("Zip file already exists! Cannot create.");
+ }
+
+ try {
+ if (zipFile.createNewFile()) {
+ logger.info("Created zip file '{}'", zipFile.getAbsolutePath());
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ try {
+ ZipOutputStream outputStream = new ZipOutputStream(new FileOutputStream(zipFile));
+
+ int zipped = 0;
+ for (File file : directory.listFiles()) {
+ if (!file.isFile()) {
+ continue;
+ }
+ zipFile(file, outputStream, zipped);
+ }
+
+ outputStream.close();
+ logger.info("Zipped '{}' files", zipped);
+ } catch (IOException e) {
+ logger.error("Could not create zip file!", e);
+ }
+
+ }
+
+ private static void zipFile(File file, OutputStream outputStream, int processed) {
+ byte[] buffer = new byte[1024];
+ int bytesRead;
+ try (FileInputStream fileInputStream = new FileInputStream(file)) {
+
+ // 3. Read the file content in chunks and write to the ZipOutputStream
+ while ((bytesRead = fileInputStream.read(buffer)) > 0) {
+ outputStream.write(buffer, 0, bytesRead);
+ processed++;
+ }
+ } catch (IOException e) {
+ logger.error("Could not zip '{}'", file.getAbsolutePath(), e);
+ }
+ }
+}
diff --git a/src/main/java/me/piitex/os/configurations/FileCrypter.java b/src/main/java/me/piitex/os/configurations/FileCrypter.java
index da2c3ea..6e56da7 100644
--- a/src/main/java/me/piitex/os/configurations/FileCrypter.java
+++ b/src/main/java/me/piitex/os/configurations/FileCrypter.java
@@ -1,6 +1,10 @@
package me.piitex.os.configurations;
+import org.jetbrains.annotations.Nullable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
import javax.crypto.*;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.PBEKeySpec;
@@ -33,10 +37,13 @@ public class FileCrypter {
private static final int GCM_IV_LENGTH_BYTES = 12;
private static final int GCM_TAG_LENGTH_BITS = 128;
- // RECOMMENDED TO CHANGE
- private static final String passKey = System.getProperty("user.name") + System.getProperty("user.home") + System.getProperty("os.name");
+ private static final Logger logger = LoggerFactory.getLogger(FileCrypter.class);
+
+ public static void encryptFile(File newFile, File outputFile) {
+ encryptFile(newFile, outputFile, null);
+ }
- public static void encryptFile(File file, File outputFile) {
+ public static void encryptFile(File newFile, File outputFile, @Nullable String key) {
SecureRandom secureRandom = new SecureRandom();
byte[] salt = new byte[SALT_LENGTH_BYTES];
secureRandom.nextBytes(salt); // Generate a unique salt for each encryption
@@ -44,9 +51,14 @@ public static void encryptFile(File file, File outputFile) {
byte[] iv = new byte[GCM_IV_LENGTH_BYTES]; // Generate a unique IV for each encryption
secureRandom.nextBytes(iv);
- char[] passKeyChars = MasterKeyManager.getPersistentPassKey();
+ char[] passKeyChars;
+ if (key == null) {
+ passKeyChars = MasterKeyManager.getPersistentPassKey();
+ } else {
+ passKeyChars = key.toCharArray();
+ }
- try (FileInputStream inputStream = new FileInputStream(file);
+ try (FileInputStream inputStream = new FileInputStream(newFile);
FileOutputStream outputStream = new FileOutputStream(outputFile)) {
SecretKeyFactory factory = SecretKeyFactory.getInstance(PBKDF_ALG);
@@ -78,12 +90,23 @@ public static void encryptFile(File file, File outputFile) {
} catch (NoSuchAlgorithmException | InvalidKeySpecException | NoSuchPaddingException |
InvalidKeyException | InvalidAlgorithmParameterException |
IllegalBlockSizeException | BadPaddingException | IOException e) {
- throw new RuntimeException("Error encrypting file: " + e.getMessage(), e);
+ logger.error("Error occurred while encrypting '{}'.", newFile.getAbsolutePath(), e);
}
}
public static void decryptFile(File file, File outputFile) throws IllegalBlockSizeException, IOException {
- char[] passKeyChars = MasterKeyManager.getPersistentPassKey();
+ decryptFile(file, outputFile, null);
+ }
+
+ public static void decryptFile(File file, File outputFile, @Nullable String key) throws IllegalBlockSizeException, IOException {
+
+ char[] passKeyChars;
+ if (key == null) {
+ passKeyChars = MasterKeyManager.getPersistentPassKey();
+ } else {
+ passKeyChars = key.toCharArray();
+ }
+
try (FileInputStream inputStream = new FileInputStream(file);
FileOutputStream outputStream = new FileOutputStream(outputFile)) {
@@ -124,7 +147,7 @@ public static void decryptFile(File file, File outputFile) throws IllegalBlockSi
outputStream.write(outputBytes);
}
} catch (NoSuchAlgorithmException | InvalidKeySpecException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException | BadPaddingException e) {
- throw new RuntimeException("Error decrypting file: " + e.getMessage(), e);
+ logger.error("Error occurred while decrypting '{}'.", file.getAbsolutePath(), e);
}
}
}
\ No newline at end of file
diff --git a/src/main/java/me/piitex/os/configurations/InfoFile.java b/src/main/java/me/piitex/os/configurations/InfoFile.java
index edab1c8..3afe4aa 100644
--- a/src/main/java/me/piitex/os/configurations/InfoFile.java
+++ b/src/main/java/me/piitex/os/configurations/InfoFile.java
@@ -32,7 +32,7 @@ public InfoFile() {
* Constructs a new {@code InfoFile} instance and associates it with a physical file.
*
* If the file does not exist, it will be created. If the file exists, its contents
- * will be loaded into memory. The file's contents will be decrypted if
+ * will be loaded into memory via a binary stream. The file's contents will be decrypted if
* encryption is enabled.
*
* @param file the file to read from and write to.
@@ -48,41 +48,45 @@ public InfoFile(File file, boolean encrypt) {
logger.warn("Unable to create file '{}'", file.getAbsolutePath());
}
} catch (IOException e) {
- throw new RuntimeException(e);
+ logger.error("IO exception occurred while creating info file!", e);
}
} else {
- // Load file
- Scanner scanner = null;
File output = null;
try {
output = Files.createTempFile(null, ".info").toFile();
+ File targetFile = file;
+
if (encrypt && !dev) {
- // Decrypt file
try {
FileCrypter.decryptFile(file, output);
- scanner = new Scanner(new FileInputStream(output));
+ targetFile = output;
} catch (IllegalBlockSizeException | IOException ignored) {
- scanner = new Scanner(new FileInputStream(file));
+ // Fallback or ignore if decryption fails
}
- } else {
- scanner = new Scanner(new FileInputStream(file));
}
- while (scanner.hasNextLine()) {
- String line = scanner.nextLine();
- if (line.split("=").length > 1) {
- entryMap.put(line.split("=")[0], line.split("=")[1].replace("!@!", "\n"));
+
+ // Safeguard: Only attempt to read if the file actually has data
+ if (targetFile.length() > 0) {
+ try (DataInputStream dis = new DataInputStream(new FileInputStream(targetFile))) {
+ int mapSize = dis.readInt();
+ for (int i = 0; i < mapSize; i++) {
+ String key = dis.readUTF();
+ String value = dis.readUTF();
+ entryMap.put(key, value);
+ }
+ } catch (EOFException e) {
+ logger.warn("Reached EOF while reading info file '{}'", file.getName());
+ } catch (UTFDataFormatException e) {
+ logger.error("Data format error in info file '{}'. The file may be an old text version. Please delete it.", file.getName(), e);
}
}
-
} catch (IOException e) {
- logger.error("An occurred during the initial encryption process.", e);
+ logger.error("An error occurred during the initial read process.", e);
} finally {
- if (scanner != null) {
- scanner.close();
- }
- if (output != null && !output.delete()) {
- logger.warn("Unable to delete unencrypted file during initialization '{}'", output.getAbsolutePath());
- forceDelete(output);
+ if (output != null && output.exists()) {
+ if (!output.delete()) {
+ forceDelete(output);
+ }
}
}
}
@@ -195,12 +199,10 @@ public Double getDoubleOrDefault(String key, Double defaultValue) {
public List getList(String key) {
String value = entryMap.get(key);
List toReturn = new ArrayList<>();
- if (!value.contains("!@!")) {
+ if (value == null || !value.contains("!@!")) {
return toReturn;
}
-
toReturn.addAll(Arrays.asList(value.split("!@!")));
-
return toReturn;
}
@@ -215,12 +217,10 @@ public List getList(String key) {
public LinkedList getLinkedList(String key) {
String value = entryMap.get(key);
LinkedList toReturn = new LinkedList<>();
- if (!value.contains("!@!")) {
+ if (value == null || !value.contains("!@!")) {
return toReturn;
}
-
toReturn.addAll(Arrays.asList(value.split("!@!")));
-
return toReturn;
}
@@ -234,21 +234,16 @@ public LinkedList getLinkedList(String key) {
*/
public Map getStringMap(String key) {
Map toReturn = new HashMap<>();
- // !@!key@!@value!@!key@!@value2!@!
-
String value = entryMap.get(key);
- if (!value.contains("!&'!")) {
+ if (value == null || !value.contains("!&'!")) {
return toReturn;
}
-
for (String s : value.split("!&'!")) {
- // s = key@!@value
String[] split = s.split("@!@");
if (split.length > 1) {
toReturn.put(split[0], split[1]);
}
}
-
return toReturn;
}
@@ -262,21 +257,16 @@ public Map getStringMap(String key) {
*/
public TreeMap getSortedStringMap(String key) {
TreeMap toReturn = new TreeMap<>();
- // !@!key@!@value!@!key@!@value2!@!
-
String value = entryMap.get(key);
- if (!value.contains("!&'!")) {
+ if (value == null || !value.contains("!&'!")) {
return toReturn;
}
-
for (String s : value.split("!&'!")) {
- // s = key@!@value
String[] split = s.split("@!@");
if (split.length > 1) {
toReturn.put(split[0], split[1]);
}
}
-
return toReturn;
}
@@ -293,13 +283,12 @@ public boolean hasKey(String key) {
/**
* Sets a key-value pair.
*
- * Newline characters in the value will be replaced with the "!@!" delimiter.
+ * Using binary serialization natively supports newline characters, meaning they no longer need to be delimited.
*
* @param key the key to set.
* @param value the string value to set.
*/
public void set(String key, String value) {
- value = value.replace("\n", "!@!");
entryMap.put(key, value);
update();
}
@@ -380,7 +369,7 @@ public void set(String key, Map map) {
* Writes the current entry map to the associated file.
*
* This method is called automatically after every {@link #set(String, String)} operation.
- * The file will be encrypted if encryption is enabled.
+ * The file will be written using a binary stream and encrypted if encryption is enabled.
*
* @throws RuntimeException if an {@link IOException} occurs during file writing.
*/
@@ -394,32 +383,29 @@ public void update() {
File output = null;
try {
- FileWriter writer;
output = Files.createTempFile(null, ".info").toFile();
- if (encrypt && !dev) {
- writer = new FileWriter(output);
- } else {
- writer = new FileWriter(file);
- }
- entryMap.forEach((s, s2) -> {
- s2 = s2.replace("\n", "!@!");
- try {
- writer.write(s + "=" + s2 + "\n");
- } catch (IOException e) {
- throw new RuntimeException(e);
+ File targetFile = (encrypt && !dev) ? output : file;
+
+ try (DataOutputStream dos = new DataOutputStream(new FileOutputStream(targetFile))) {
+ dos.writeInt(entryMap.size());
+ for (Map.Entry entry : entryMap.entrySet()) {
+ dos.writeUTF(entry.getKey());
+
+ // Safeguard against null values breaking the binary stream
+ String value = entry.getValue() == null ? "" : entry.getValue();
+ dos.writeUTF(value);
}
- });
- writer.close();
+ }
+
if (encrypt && !dev) {
- // Replace output into the file
FileCrypter.encryptFile(output, file);
}
} catch (IOException e) {
- logger.error("An error occurred during the encryption save process.", e);
+ logger.error("An error occurred during the save process.", e);
} finally {
if (output != null && output.exists()) {
if (!output.delete()) {
- logger.error("Unable to delete unencrypted file after writing. '{}'", file.getAbsolutePath());
+ logger.error("Unable to delete temp file after writing. '{}'", output.getAbsolutePath());
forceDelete(output);
}
}
@@ -433,4 +419,11 @@ public Map getEntryMap() {
public void setEntryMap(Map entryMap) {
this.entryMap = entryMap;
}
-}
+
+ public static InfoFile copy(InfoFile input) {
+ InfoFile infoFile = new InfoFile();
+ input.getEntryMap().forEach((s, s2) -> infoFile.getEntryMap().put(s, s2));
+ infoFile.update();
+ return infoFile;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/me/piitex/os/configurations/MasterKeyManager.java b/src/main/java/me/piitex/os/configurations/MasterKeyManager.java
index b595893..1532664 100644
--- a/src/main/java/me/piitex/os/configurations/MasterKeyManager.java
+++ b/src/main/java/me/piitex/os/configurations/MasterKeyManager.java
@@ -33,7 +33,7 @@ public static char[] getPersistentPassKey() {
}
private static void generateAndSaveMasterKey() {
- System.out.println("Generating new persistent master key...");
+ logger.info("Generating a new security key...");
SecureRandom random = new SecureRandom();
byte[] keyBytes = new byte[MASTER_KEY_LENGTH_BYTES];
random.nextBytes(keyBytes);