diff --git a/.gitignore b/.gitignore
index 78596d7..07cd66f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -38,6 +38,4 @@ bin/
### Mac OS ###
.DS_Store
/src/main/resources/config/
-/src/main/resources/engine.json
-/src/main/resources/log4j2.xml
/cache/
diff --git a/logs/lastlog.log b/logs/lastlog.log
index c349a7e..33ab7b5 100644
--- a/logs/lastlog.log
+++ b/logs/lastlog.log
@@ -1,5 +1,13 @@
-[03:30:18.666][INFO] ===== Engine Initialization =====
-[03:30:18.668][INFO]
+[07:39:48.308][INFO]
+╔════════════════════════════════════════╗
+║ ===== FoxEngine Initialization ===== ║
+║ Engine Version: 1.15.16 ║
+║ Operating System: windows ║
+║ Available Processors: 20 ║
+║ System Load Average: -1,00 ║
+║ Log Level: INFO ║
+╚════════════════════════════════════════╝
+
_____ _____ _____
/\ \ /\ \ /\ \
/::\ \ /::\ \ /::\ \
@@ -20,37 +28,39 @@
\:::\ \
\:::\____\
\::/ /
- \/____/
-[03:30:18.674][INFO] Engine Version: 1.15.15
-[03:30:18.674][INFO] Application Title: FoxesLauncher-1.24.1
-[03:30:18.674][INFO] Operating System: windows
-[03:30:18.674][INFO] Available Processors: 20
-[03:30:18.675][INFO] System Load Average: -1.0
-[03:30:18.675][INFO] Log Level: INFO
-[03:30:18.679][INFO] Log level set to INFO
-[03:30:18.762][INFO] Initializing ExecutorService with pool size: 10
-[03:30:18.763][WARN] executor-config.properties not found, using default settings.
-[03:30:18.763][INFO] Creating default ExecutorService with pool size: 10
-[03:30:18.884][INFO]forge-0 Sounds loaded successfully
-[03:30:19.087][INFO] FrameConstructor initialization
-[03:30:19.091][INFO] Building FrameConstructor...
-[03:30:19.210][INFO]forge-1 Initializing StyleProvider with path: assets/styles/
-[03:30:19.220][INFO]forge-1 Registering json adapter...
-[03:30:19.221][INFO]forge-1 Registering json5 adapter...
-[03:30:19.221][INFO]forge-1 Registered adapters: [json5, json]
-[03:30:19.233][INFO]forge-1 - Registered component: label
-[03:30:19.235][INFO]forge-1 - Registered component: progressBar
-[03:30:19.235][INFO]forge-1 - Registered component: button
-[03:30:19.235][INFO]forge-1 - Registered component: textArea
-[03:30:19.236][INFO]forge-1 - Registered component: checkBox
-[03:30:19.236][INFO]forge-1 - Registered component: textField
-[03:30:19.236][INFO]forge-1 - Registered component: spriteImage
-[03:30:19.237][INFO]forge-1 - Registered component: passField
-[03:30:19.237][INFO]forge-1 - Registered component: spinner
-[03:30:19.237][INFO]forge-1 - Registered component: multiButton
-[03:30:19.238][INFO]forge-1 - Registered component: dropBox
-[03:30:19.238][INFO]forge-1 - Registered component: slider
-[03:30:19.238][INFO]forge-1 - Registered component: compositeSlider
-[03:30:19.239][INFO]forge-1 - Registered component: fileSelector
-[03:30:19.239][INFO]forge-1 - Registered component: compositeComponent
-[03:30:19.345][INFO] Created font - mcfontBold
+ \/____/
+— TestApp-1.0.0
+[07:39:48.315][INFO] Log level set to INFO
+[07:39:48.402][INFO] Initializing ExecutorService with pool size: 10
+[07:39:48.403][WARN] executor-config.properties not found, using default settings.
+[07:39:48.403][INFO] Creating default ExecutorService with pool size: 10
+[07:39:48.524][INFO]forge-0 Sounds loaded successfully
+[07:39:48.702][INFO] FrameConstructor: initialization started
+[07:39:48.702][INFO] FrameConstructor: building AppFrame from 'assets/frames/frame.json'
+[07:39:48.846][INFO] FrameConstructor: frame is visible (title='ЛисийМир', size=850x500)
+[07:39:48.846][INFO] FrameConstructor: frame successfully built from 'assets/frames/frame.json'
+[07:39:48.847][INFO] FrameConstructor: initialization completed
+[07:39:48.849][INFO]forge-1 Initializing StyleProvider with path: assets/styles/
+[07:39:48.861][INFO]forge-1 Registering json adapter...
+[07:39:48.861][INFO]forge-1 Registering json5 adapter...
+[07:39:48.861][INFO]forge-1 Registered adapters: [json5, json]
+[07:39:48.874][INFO]forge-1 - Registered component: label
+[07:39:48.874][INFO]forge-1 - Registered component: progressBar
+[07:39:48.875][INFO]forge-1 - Registered component: button
+[07:39:48.875][INFO]forge-1 - Registered component: textArea
+[07:39:48.875][INFO]forge-1 - Registered component: checkBox
+[07:39:48.876][INFO]forge-1 - Registered component: textField
+[07:39:48.876][INFO]forge-1 - Registered component: spriteImage
+[07:39:48.876][INFO]forge-1 - Registered component: passField
+[07:39:48.877][INFO]forge-1 - Registered component: spinner
+[07:39:48.877][INFO]forge-1 - Registered component: multiButton
+[07:39:48.877][INFO]forge-1 - Registered component: dropBox
+[07:39:48.878][INFO]forge-1 - Registered component: slider
+[07:39:48.878][INFO]forge-1 - Registered component: compositeSlider
+[07:39:48.878][INFO]forge-1 - Registered component: fileSelector
+[07:39:48.879][INFO]forge-1 - Registered component: compositeComponent
+[07:39:48.884][INFO]forge-1 ActionHandler created with mainFrame panel and listens [class org.foxesworld.engine.gui.components.multiButton.MultiButton, class org.foxesworld.engine.gui.components.button.Button]
+[07:39:48.974][INFO] FrameConstructor: frame focus gained
+[07:39:48.975][INFO] FrameConstructor: frame focus gained
+[07:39:49.004][INFO] Created font - mcfontBold
+[07:39:49.093][INFO] Created font - mcfont
diff --git a/src/main/java/org/foxesworld/engine/Engine.java b/src/main/java/org/foxesworld/engine/Engine.java
index 66bde76..89a34e7 100644
--- a/src/main/java/org/foxesworld/engine/Engine.java
+++ b/src/main/java/org/foxesworld/engine/Engine.java
@@ -165,17 +165,17 @@ public Engine(int poolSize, String worker, Map
+ *
* Injection rules:
*
*
- *
+ * Responsible for creating and configuring the main application JFrame using {@link FrameAttributes} + * loaded from a template or provided directly. Provides focus tracking, shaped window support, + * seasonal background support (via {@link Panel}) and centralized logging for important lifecycle events. + *
+ */ public class FrameConstructor extends JFrame { private final Engine engine; private FocusStatusListener focusStatusListener; @@ -23,106 +33,248 @@ public class FrameConstructor extends JFrame { private final LanguageProvider LANG; private boolean hasFocus; + /** + * Creates and initializes the application frame. + * + *+ * This constructor immediately triggers the frame build process using the template path + * configured in the engine's file properties. + *
+ * + * @param engine engine instance used for resources, localization and configuration; must not be {@code null}. + */ public FrameConstructor(Engine engine) { - Engine.getLOGGER().info("FrameConstructor initialization"); + Engine.LOGGER.info("FrameConstructor: initialization started"); this.engine = engine; this.LANG = engine.getLANG(); this.hasFocus = false; this.focusStatusListener = engine; - buildFrame("assets/frames/frame.json"); + // build frame from engine-provided template (may log errors if template missing/invalid) + buildFrame(engine.getFileProperties().getFrameTpl()); + Engine.LOGGER.info("FrameConstructor: initialization completed"); } + /** + * Loads FrameAttributes from the given resource path and builds the frame. + * + *+ * This method logs the path being loaded and any errors encountered while reading or parsing the template. + *
+ * + * @param path resource path to the frame template (for example "assets/frame/frame.json") + */ private void buildFrame(String path) { + Engine.LOGGER.info("FrameConstructor: building AppFrame from '{}'", path); Gson gson = new Gson(); - FrameAttributes frameAttributes; - InputStreamReader reader = new InputStreamReader( - this.getClass().getClassLoader().getResourceAsStream(path), StandardCharsets.UTF_8); - frameAttributes = gson.fromJson(reader, FrameAttributes.class); - buildFrame(frameAttributes); + + try (InputStream in = this.getClass().getClassLoader().getResourceAsStream(path)) { + if (in == null) { + Engine.LOGGER.error("FrameConstructor: resource not found at path '{}'", path); + return; + } + try (InputStreamReader reader = new InputStreamReader(in, StandardCharsets.UTF_8)) { + FrameAttributes frameAttributes = gson.fromJson(reader, FrameAttributes.class); + if (frameAttributes == null) { + Engine.LOGGER.error("FrameConstructor: parsed FrameAttributes is null for path '{}'", path); + return; + } + Engine.LOGGER.debug("FrameConstructor: parsed FrameAttributes: width={}, height={}, resizable={}, undecorated={}, borderRadius={}", + frameAttributes.getWidth(), frameAttributes.getHeight(), + frameAttributes.isResizable(), frameAttributes.isUndecorated(), frameAttributes.getBorderRadius()); + buildFrame(frameAttributes); + Engine.LOGGER.info("FrameConstructor: frame successfully built from '{}'", path); + } + } catch (Exception ex) { + Engine.LOGGER.error("FrameConstructor: failed to build frame from '{}'", path, ex); + } } + /** + * Configures the Swing {@link JFrame} using provided {@link FrameAttributes}. + * + *+ * Sets icon, title, size, decoration, window shape (if border radius provided), centers on screen, + * and installs the {@link Panel} root panel. Also sets the frame visible. + *
+ * + * @param frameAttributes attributes describing appearance and behavior of the frame. + */ public void buildFrame(FrameAttributes frameAttributes) { - Engine.getLOGGER().info("Building FrameConstructor..."); - if (!frameAttributes.getAppIcon().endsWith(".svg")) { - setIconImage(this.engine.getImageUtils().getLocalImage(frameAttributes.getAppIcon())); - } else { - setIconImage(new FlatSVGIcon(frameAttributes.getAppIcon(), 1).getImage()); - } - setTitle(LANG.getString(frameAttributes.getAppTitle())); - setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - setSize(frameAttributes.getWidth(), frameAttributes.getHeight()); - setResizable(frameAttributes.isResizable()); - setUndecorated(frameAttributes.isUndecorated()); - if (frameAttributes.getBorderRadius() != 0) { - this.setShape(new RoundRectangle2D.Double( - 0, 0, getWidth(), getHeight(), - frameAttributes.getBorderRadius(), - frameAttributes.getBorderRadius() - )); - } + try { + Engine.LOGGER.debug("FrameConstructor: applying FrameAttributes to JFrame"); + + if (frameAttributes.getAppIcon() != null && !frameAttributes.getAppIcon().isEmpty()) { + if (!frameAttributes.getAppIcon().endsWith(".svg")) { + Engine.LOGGER.debug("FrameConstructor: loading raster icon '{}'", frameAttributes.getAppIcon()); + setIconImage(this.engine.getImageUtils().getLocalImage(frameAttributes.getAppIcon())); + } else { + Engine.LOGGER.debug("FrameConstructor: loading SVG icon '{}'", frameAttributes.getAppIcon()); + setIconImage(new FlatSVGIcon(frameAttributes.getAppIcon(), 1).getImage()); + } + } else { + Engine.LOGGER.warn("FrameConstructor: no app icon configured in FrameAttributes"); + } + + setTitle(LANG.getString(frameAttributes.getAppTitle())); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + setSize(frameAttributes.getWidth(), frameAttributes.getHeight()); + setResizable(frameAttributes.isResizable()); + setUndecorated(frameAttributes.isUndecorated()); + + if (frameAttributes.getBorderRadius() != 0) { + Engine.LOGGER.debug("FrameConstructor: applying window shape with border radius {}", frameAttributes.getBorderRadius()); + this.setShape(new RoundRectangle2D.Double( + 0, 0, getWidth(), getHeight(), + frameAttributes.getBorderRadius(), + frameAttributes.getBorderRadius() + )); + } + + screenSize = Toolkit.getDefaultToolkit().getScreenSize(); + int x = (screenSize.width - getWidth()) / 2; + int y = (screenSize.height - getHeight()) / 2; + setLocation(x, y); + Engine.LOGGER.debug("FrameConstructor: window positioned at x={}, y={}, screenWidth={}, screenHeight={}", x, y, screenSize.width, screenSize.height); - screenSize = Toolkit.getDefaultToolkit().getScreenSize(); - int x = (screenSize.width - getWidth()) / 2; - int y = (screenSize.height - getHeight()) / 2; - setLocation(x, y); - panel = new Panel(this); - this.rootPanel = panel.setRootPanel(frameAttributes); - this.rootPanel.setName("rootPanel"); - setContentPane(this.rootPanel); - setVisible(true); + panel = new Panel(this); + this.rootPanel = panel.setRootPanel(frameAttributes); + this.rootPanel.setName("rootPanel"); + setContentPane(this.rootPanel); + + // ensure focus listeners are set up before showing the frame + setupFocusListeners(); + + setVisible(true); + Engine.LOGGER.info("FrameConstructor: frame is visible (title='{}', size={}x{})", getTitle(), getWidth(), getHeight()); + } catch (Exception ex) { + Engine.LOGGER.error("FrameConstructor: error while applying FrameAttributes", ex); + } } + /** + * Installs a WindowFocusListener that updates the configured {@link FocusStatusListener}. + * + *+ * This method logs listener setup and will report focus gained/lost events to the listener. + *
+ */ private void setupFocusListeners() { + Engine.LOGGER.debug("FrameConstructor: setting up focus listeners"); addWindowFocusListener(new WindowFocusListener() { @Override public void windowGainedFocus(WindowEvent e) { + Engine.LOGGER.debug("FrameConstructor: window gained focus event"); onFrameFocusGained(); } @Override public void windowLostFocus(WindowEvent e) { + Engine.LOGGER.debug("FrameConstructor: window lost focus event"); onFrameFocusLost(); } }); } + /** + * Internal handler for frame gaining focus. + * Notifies the {@link FocusStatusListener} and logs the change. + */ private void onFrameFocusGained() { hasFocus = true; - focusStatusListener.updateFocus(hasFocus); + Engine.LOGGER.info("FrameConstructor: frame focus gained"); + if (focusStatusListener != null) { + try { + focusStatusListener.updateFocus(hasFocus); + } catch (Exception ex) { + Engine.LOGGER.error("FrameConstructor: error notifying focusStatusListener on focus gained", ex); + } + } else { + Engine.LOGGER.warn("FrameConstructor: focusStatusListener is null when focus gained"); + } } + /** + * Internal handler for frame losing focus. + * Notifies the {@link FocusStatusListener} and logs the change. + */ private void onFrameFocusLost() { hasFocus = false; - focusStatusListener.updateFocus(hasFocus); + Engine.LOGGER.info("FrameConstructor: frame focus lost"); + if (focusStatusListener != null) { + try { + focusStatusListener.updateFocus(hasFocus); + } catch (Exception ex) { + Engine.LOGGER.error("FrameConstructor: error notifying focusStatusListener on focus lost", ex); + } + } else { + Engine.LOGGER.warn("FrameConstructor: focusStatusListener is null when focus lost"); + } } + + /** + * Returns whether the frame currently has focus. + * + * @return {@code true} if frame has focus; {@code false} otherwise. + */ public boolean hasFocus() { return hasFocus; } + /** + * Returns the screen size where the frame was created. + * + * @return screen {@link Dimension}. + */ public Dimension getScreenSize() { return screenSize; } + /** + * Returns the root content panel that was installed on this frame. + * + * @return root {@link JPanel}. + */ public JPanel getRootPanel() { return this.rootPanel; } + /** + * Convenience accessor for the underlying {@link Engine} instance. + * + * @return engine. + */ public Engine getAppFrame() { return engine; } + /** + * Returns the internal {@link Panel} helper instance used by this frame. + * + * @return panel. + */ public Panel getPanel() { return panel; } + /** + * Changes frame size and logs the new dimensions. + * + * @param width new width in pixels. + * @param height new height in pixels. + */ public void setFrameSize(int width, int height) { + Engine.LOGGER.info("FrameConstructor: resizing frame to {}x{}", width, height); this.setSize(width, height); } + /** + * Sets the listener that will be updated on window focus changes. + * + * @param focusStatusListener listener to notify when frame focus changes. + */ public void setFocusStatusListener(FocusStatusListener focusStatusListener) { + Engine.LOGGER.debug("FrameConstructor: setting FocusStatusListener -> {}", focusStatusListener); this.focusStatusListener = focusStatusListener; setupFocusListeners(); } - - -} \ No newline at end of file +} diff --git a/src/main/java/org/foxesworld/engine/gui/components/panel/Panel.java b/src/main/java/org/foxesworld/engine/gui/components/panel/Panel.java index 8b51484..008fc7f 100644 --- a/src/main/java/org/foxesworld/engine/gui/components/panel/Panel.java +++ b/src/main/java/org/foxesworld/engine/gui/components/panel/Panel.java @@ -15,25 +15,64 @@ import static org.foxesworld.engine.utils.FontUtils.hexToColor; +/** + * A custom Swing {@link JPanel} implementation used by the GUI frame system. + * + *+ * Responsibilities: + *
+ * Painting operations apply the panel-wide {@code alpha} value so that children and backgrounds + * are rendered with the specified transparency. + *
+ */ public class Panel extends JPanel { private FrameAttributes frameAttributes; private JPanel groupPanel; private final FrameConstructor frameConstructor; private BufferedImage texture; - // Поле прозрачности: 1.0 - полностью непрозрачно, 0.0 - полностью прозрачно. + // Alpha transparency field: 1.0 = fully opaque, 0.0 = fully transparent. private float alpha = 1.0f; + /** + * Constructs a Panel backed by the provided {@link FrameConstructor}. + * + * @param frameConstructor frame constructor used to resolve resources (fonts, images, etc.). + */ public Panel(FrameConstructor frameConstructor) { this.frameConstructor = frameConstructor; - // Устанавливаем режим непрозрачности в зависимости от alpha + // Set opacity according to alpha this.setOpaque(alpha >= 1.0f); } - // Геттер прозрачности + /** + * Returns the current alpha (transparency) value. + * + * @return alpha in range [0.0, 1.0]. + */ public float getAlpha() { return alpha; } + + /** + * Sets the panel alpha (transparency). + * + *+ * Values outside the [0.0, 1.0] range are clamped. Changing the alpha updates the panel's + * opaque flag and triggers a repaint. + *
+ * + * @param alpha new alpha value (0.0 — fully transparent, 1.0 — fully opaque). + */ public void setAlpha(float alpha) { if (alpha < 0f) { alpha = 0f; @@ -45,16 +84,28 @@ public void setAlpha(float alpha) { repaint(); } + /** + * Creates and returns the root panel for the frame using the provided {@link FrameAttributes}. + * + *+ * The returned panel paints using the parent's {@link #alpha} composite. If a texture is set + * via {@link #setTexture(BufferedImage)} it will be drawn stretched to the panel size; + * otherwise a seasonally chosen background is loaded and darkened according to frame attributes. + *
+ * + * @param frameAttributes frame attributes describing background and seasonal images. + * @return a configured root {@link JPanel} instance (named "rootPanel"). + */ public JPanel setRootPanel(FrameAttributes frameAttributes) { this.frameAttributes = frameAttributes; - // Пример корневой панели, в которой используется прозрачность из поля alpha + // Example root panel that respects the alpha field JPanel rootPanel = new JPanel(null, true) { @Override protected void paintComponent(Graphics g) { Graphics2D g2d = (Graphics2D) g.create(); try { - // Применяем прозрачность, заданную в поле alpha + // Apply panel alpha g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, Panel.this.alpha)); super.paintComponent(g2d); if (texture != null) { @@ -74,15 +125,20 @@ protected void paintComponent(Graphics g) { } /** - * Устанавливает текстуру для панели. + * Sets a custom texture image for the panel. The texture will be used when painting the panel. * - * @param newTexture изображение текстуры. + * @param newTexture texture image to apply; may be {@code null} to revert to the seasonal background. */ public void setTexture(BufferedImage newTexture) { this.texture = newTexture; - repaint(); // Перерисовываем панель с новой текстурой + repaint(); // Repaint panel with the new texture } + /** + * Draws a darkened background based on the frame's seasonal background image. + * + * @param g graphics context to draw into. + */ private void drawDarkenedBackground(Graphics g) { BufferedImage backgroundImage = frameConstructor.getAppFrame() .getImageUtils() @@ -90,6 +146,11 @@ private void drawDarkenedBackground(Graphics g) { g.drawImage(applyDarkening(backgroundImage, hexToColor(frameAttributes.getBackgroundBlur())), 0, 0, null); } + /** + * Selects a seasonal background image path according to the current month. + * + * @return the configured seasonal image key from {@link FrameAttributes}. + */ private String getSeasonalBackground() { return switch (CurrentMonth.getCurrentMonth()) { case DECEMBER, JANUARY, FEBRUARY -> frameAttributes.getWinterImage(); @@ -99,6 +160,13 @@ private String getSeasonalBackground() { }; } + /** + * Returns a new image that is visually darkened by overlaying a semi-transparent color. + * + * @param image original source image. + * @param darkeningColor overlay color used to darken (alpha of overlay is set to 0.5f). + * @return a new {@link BufferedImage} with the darkening applied. + */ private BufferedImage applyDarkening(BufferedImage image, Color darkeningColor) { int width = image.getWidth(); int height = image.getHeight(); @@ -125,13 +193,32 @@ private BufferedImage applyDarkening(BufferedImage image, Color darkeningColor) return darkenedImage; } + /** + * Creates a configured group panel according to {@link PanelAttributes}. + * + *+ * The created panel supports: + *
+ * Expected format: "top,left,bottom,right,#RRGGBB" where the first four values are integer thicknesses + * and the last value is a hex color string. + *
+ * + * @param groupPanel target panel to apply the border to. + * @param border comma-separated border descriptor. + */ private void createBorder(JPanel groupPanel, String border) { String[] borderData = border.split(","); int top = Integer.parseInt(borderData[0]); @@ -221,4 +326,4 @@ private void createBorder(JPanel groupPanel, String border) { Color borderColor = hexToColor(borderData[4]); groupPanel.setBorder(new MatteBorder(top, left, bottom, right, borderColor)); } -} +} \ No newline at end of file diff --git a/src/main/resources/buildInfo.json b/src/main/resources/buildInfo.json index e65933e..ce6d4f8 100644 --- a/src/main/resources/buildInfo.json +++ b/src/main/resources/buildInfo.json @@ -1,2 +1,2 @@ -{"engineVersion": "1.15.15", +{"engineVersion": "1.15.16", "engineBrand": " \n _____ _____ _____ \n /\\ \\ /\\ \\ /\\ \\ \n /::\\ \\ /::\\ \\ /::\\ \\ \n \\:::\\ \\ /::::\\ \\ \\:::\\ \\ \n \\:::\\ \\ /::::::\\ \\ \\:::\\ \\ \n \\:::\\ \\ /:::/\\:::\\ \\ \\:::\\ \\ \n \\:::\\ \\ /:::/__\\:::\\ \\ \\:::\\ \\ \n /::::\\ \\ /::::\\ \\:::\\ \\ /::::\\ \\ \n /::::::\\ \\ /::::::\\ \\:::\\ \\ /::::::\\ \\ \n /:::/\\:::\\ \\ /:::/\\:::\\ \\:::\\ \\ /:::/\\:::\\ \\ \n /:::/ \\:::\\____\\/:::/__\\:::\\ \\:::\\____\\ /:::/ \\:::\\____\\\n /:::/ \\::/ /\\:::\\ \\:::\\ \\::/ / /:::/ \\::/ /\n /:::/ / \\/____/ \\:::\\ \\:::\\ \\/____/ /:::/ / \\/____/ \n /:::/ / \\:::\\ \\:::\\ \\ /:::/ / \n/:::/ / \\:::\\ \\:::\\____\\ /:::/ / \n\\::/ / \\:::\\ \\::/ / \\::/ / \n \\/____/ \\:::\\ \\/____/ \\/____/ \n \\:::\\ \\ \n \\:::\\____\\ \n \\::/ / \n \\/____/ "} \ No newline at end of file