From e87edd27a28ea4f3169e8cba8cd7f3ab21e2822a Mon Sep 17 00:00:00 2001 From: extio-de Date: Sat, 27 Dec 2025 23:40:14 +0100 Subject: [PATCH] feat: Add multiple new themes and their corresponding pattern renderers - Introduced new themes: Contemporary, Dream, Fantasy, Modern, Neon, Noir, and Urban. - Implemented FactoryBeans for each theme to facilitate their creation. - Developed pattern renderers for each theme to define their visual styles. - Updated G2DAutoConfiguration to include new theme beans. - Enhanced DemoModule to support dynamic theme selection with improved layout and positioning. --- .../renderer/g2d/G2DAutoConfiguration.java | 42 +++++++ .../theme/ContemporaryThemeFactoryBean.java | 36 ++++++ .../g2d/theme/DreamPatternRenderer.java | 99 +++++++++++++++ .../g2d/theme/DreamThemeFactoryBean.java | 36 ++++++ .../g2d/theme/FantasyPatternRenderer.java | 107 +++++++++++++++++ .../g2d/theme/FantasyThemeFactoryBean.java | 36 ++++++ .../g2d/theme/ModernPatternRenderer.java | 99 +++++++++++++++ .../g2d/theme/ModernThemeFactoryBean.java | 36 ++++++ .../g2d/theme/NeonThemeFactoryBean.java | 36 ++++++ .../g2d/theme/NoirThemeFactoryBean.java | 36 ++++++ .../g2d/theme/UrbanPatternRenderer.java | 113 ++++++++++++++++++ .../g2d/theme/UrbanThemeFactoryBean.java | 36 ++++++ .../de/extio/game_engine/demo/DemoModule.java | 72 +++++++---- 13 files changed, 758 insertions(+), 26 deletions(-) create mode 100644 src/main/java/de/extio/game_engine/renderer/g2d/theme/ContemporaryThemeFactoryBean.java create mode 100644 src/main/java/de/extio/game_engine/renderer/g2d/theme/DreamPatternRenderer.java create mode 100644 src/main/java/de/extio/game_engine/renderer/g2d/theme/DreamThemeFactoryBean.java create mode 100644 src/main/java/de/extio/game_engine/renderer/g2d/theme/FantasyPatternRenderer.java create mode 100644 src/main/java/de/extio/game_engine/renderer/g2d/theme/FantasyThemeFactoryBean.java create mode 100644 src/main/java/de/extio/game_engine/renderer/g2d/theme/ModernPatternRenderer.java create mode 100644 src/main/java/de/extio/game_engine/renderer/g2d/theme/ModernThemeFactoryBean.java create mode 100644 src/main/java/de/extio/game_engine/renderer/g2d/theme/NeonThemeFactoryBean.java create mode 100644 src/main/java/de/extio/game_engine/renderer/g2d/theme/NoirThemeFactoryBean.java create mode 100644 src/main/java/de/extio/game_engine/renderer/g2d/theme/UrbanPatternRenderer.java create mode 100644 src/main/java/de/extio/game_engine/renderer/g2d/theme/UrbanThemeFactoryBean.java diff --git a/src/main/java/de/extio/game_engine/renderer/g2d/G2DAutoConfiguration.java b/src/main/java/de/extio/game_engine/renderer/g2d/G2DAutoConfiguration.java index 2fb8998..f1535e5 100644 --- a/src/main/java/de/extio/game_engine/renderer/g2d/G2DAutoConfiguration.java +++ b/src/main/java/de/extio/game_engine/renderer/g2d/G2DAutoConfiguration.java @@ -12,8 +12,15 @@ import org.springframework.context.annotation.Bean; import de.extio.game_engine.renderer.g2d.theme.G2DThemeManager; import de.extio.game_engine.renderer.g2d.theme.PatternRenderer; +import de.extio.game_engine.renderer.g2d.theme.ContemporaryThemeFactoryBean; +import de.extio.game_engine.renderer.g2d.theme.DreamThemeFactoryBean; +import de.extio.game_engine.renderer.g2d.theme.FantasyThemeFactoryBean; +import de.extio.game_engine.renderer.g2d.theme.ModernThemeFactoryBean; +import de.extio.game_engine.renderer.g2d.theme.NeonThemeFactoryBean; +import de.extio.game_engine.renderer.g2d.theme.NoirThemeFactoryBean; import de.extio.game_engine.renderer.g2d.theme.SpacecraftThemeFactoryBean; import de.extio.game_engine.renderer.g2d.theme.Theme; +import de.extio.game_engine.renderer.g2d.theme.UrbanThemeFactoryBean; import de.extio.game_engine.renderer.g2d.theme.VintageThemeFactoryBean; import de.extio.game_engine.resource.StaticResourceService; @@ -50,6 +57,41 @@ public class G2DAutoConfiguration { return new VintageThemeFactoryBean(); } + @Bean + FactoryBean modernTheme() { + return new ModernThemeFactoryBean(); + } + + @Bean + FactoryBean contemporaryTheme() { + return new ContemporaryThemeFactoryBean(); + } + + @Bean + FactoryBean urbanTheme() { + return new UrbanThemeFactoryBean(); + } + + @Bean + FactoryBean dreamTheme() { + return new DreamThemeFactoryBean(); + } + + @Bean + FactoryBean fantasyTheme() { + return new FantasyThemeFactoryBean(); + } + + @Bean + FactoryBean noirTheme() { + return new NoirThemeFactoryBean(); + } + + @Bean + FactoryBean neonTheme() { + return new NeonThemeFactoryBean(); + } + @Bean G2DThemeManager g2dThemeManager( final StaticResourceService staticResourceService, diff --git a/src/main/java/de/extio/game_engine/renderer/g2d/theme/ContemporaryThemeFactoryBean.java b/src/main/java/de/extio/game_engine/renderer/g2d/theme/ContemporaryThemeFactoryBean.java new file mode 100644 index 0000000..fb7c7f1 --- /dev/null +++ b/src/main/java/de/extio/game_engine/renderer/g2d/theme/ContemporaryThemeFactoryBean.java @@ -0,0 +1,36 @@ +package de.extio.game_engine.renderer.g2d.theme; + +import org.springframework.beans.factory.FactoryBean; + +public class ContemporaryThemeFactoryBean implements FactoryBean { + + @Override + public Theme getObject() { + return Theme.builder() + .name("Contemporary") + .patternRendererName("modernPatternRenderer") + .borderOuter(new HSBColor(0.88f, 0.12f, 0.75f)) + .borderInner(new HSBColor(0.88f, 0.10f, 0.92f)) + .borderInnerDisabled(new HSBColor(0.88f, 0.06f, 0.42f)) + .backgroundNormal(new HSBColor(0.92f, 0.55f, 0.22f)) + .backgroundSelected(new HSBColor(0.33f, 0.75f, 0.70f)) + .textNormal(new HSBColor(0.08f, 0.04f, 0.94f)) + .textDisabled(new HSBColor(0.08f, 0.02f, 0.62f)) + .selectionPrimary(new HSBColor(0.33f, 0.75f, 0.70f)) + .selectionSecondary(new HSBColor(0.58f, 0.25f, 0.95f)) + .windowBackground(new HSBColor(0.92f, 0.55f, 0.12f)) + .hoverBrightnessAdjustment(0.22f) + .pressedBrightnessAdjustment(0.38f) + .build(); + } + + @Override + public Class getObjectType() { + return Theme.class; + } + + @Override + public boolean isSingleton() { + return true; + } +} diff --git a/src/main/java/de/extio/game_engine/renderer/g2d/theme/DreamPatternRenderer.java b/src/main/java/de/extio/game_engine/renderer/g2d/theme/DreamPatternRenderer.java new file mode 100644 index 0000000..c01778f --- /dev/null +++ b/src/main/java/de/extio/game_engine/renderer/g2d/theme/DreamPatternRenderer.java @@ -0,0 +1,99 @@ +package de.extio.game_engine.renderer.g2d.theme; + +import java.awt.Color; +import java.awt.Graphics2D; + +import org.springframework.context.annotation.Conditional; +import org.springframework.stereotype.Component; + +import de.extio.game_engine.renderer.g2d.G2DRendererCondition; +import de.extio.game_engine.renderer.g2d.bo.rendering.G2DDrawFont; + +@Conditional(G2DRendererCondition.class) +@Component +public class DreamPatternRenderer implements PatternRenderer { + + @Override + public void drawDecorativeBorder(final Graphics2D g2d, final int x, final int y, final int width, final int height, final int strength, final Color color) { + final var s = Math.max(1, strength); + final var arc = Math.max(6, s * 6); + + g2d.setColor(color); + g2d.drawRoundRect(x, y, width - 1, height - 1, arc, arc); + + final var dot = Math.max(2, s * 2); + g2d.fillOval(x + s, y + s, dot, dot); + g2d.fillOval(x + width - s - dot, y + s, dot, dot); + g2d.fillOval(x + s, y + height - s - dot, dot, dot); + g2d.fillOval(x + width - s - dot, y + height - s - dot, dot, dot); + } + + @Override + public void drawDecorativeBorderFilled(final Graphics2D g2d, final int x, final int y, final int width, final int height, final int strength, final Color borderColor, final Color backgroundColor) { + final var s = Math.max(1, strength); + final var arc = Math.max(6, s * 6); + + g2d.setColor(backgroundColor); + g2d.fillRoundRect(x + s, y + s, Math.max(0, width - s * 2), Math.max(0, height - s * 2), arc, arc); + + this.drawDecorativeBorder(g2d, x, y, width, height, s, borderColor); + } + + @Override + public void drawWindowPanel(final Graphics2D g2d, final int x, final int y, final int width, final int height, final boolean thickBorder, final Color innerBorderColor, final Color outerBorderColor, final Color backgroundColor, final double scaleFactor) { + final var s = Math.max((thickBorder ? 4 : 2), (int) ((thickBorder ? 6 : 3) * scaleFactor)); + final var s2 = s * 2; + + final var bg = new Color(backgroundColor.getRed(), backgroundColor.getGreen(), backgroundColor.getBlue(), 160); + this.drawDecorativeBorderFilled(g2d, x + s, y + s, width - s2, height - s2, s, innerBorderColor, bg); + this.drawDecorativeBorder(g2d, x, y, width, height, s, outerBorderColor); + } + + @Override + public void drawCloseButton(final Graphics2D g2d, final int width, final int height, final boolean enabled, final int state, final Color backgroundColor, final double scaleFactor, final Theme theme) { + final var STATE_HOVERED = 4; + final var STATE_TOGGLED = 1; + final var STATE_PRESSED = 2; + + final var highlight = (state & (STATE_HOVERED | STATE_TOGGLED)) != 0; + final var pressed = (state & STATE_PRESSED) != 0; + + Color bg = backgroundColor; + if (bg == null) { + bg = theme.getWindowBackground().toColor(); + } + if (highlight) { + bg = theme.getSelectionSecondary().toColor(); + } + if (pressed) { + bg = theme.getSelectionPrimary().toColor(); + } + + final var bg2 = new Color(bg.getRed(), bg.getGreen(), bg.getBlue(), 210); + g2d.setColor(bg2); + g2d.fillRoundRect(0, 0, width, height, Math.max(6, (int) (10 * scaleFactor)), Math.max(6, (int) (10 * scaleFactor))); + + final var s = Math.max(1, (int) (2 * scaleFactor)); + this.drawDecorativeBorder(g2d, 0, 0, width, height, s, theme.getBorderOuter().toColor()); + + if (!enabled) { + return; + } + + final Color fg; + if (pressed) { + fg = theme.getTextNormal().toColor(); + } + else if (highlight) { + fg = Color.BLACK; + } + else { + fg = theme.getTextDisabled().toColor(); + } + g2d.setColor(fg); + + final var fontSize = (int) (15 * scaleFactor); + final var textDim = G2DDrawFont.getTextDimensions("X", g2d, 15, scaleFactor); + G2DDrawFont.renderText(g2d, textDim, 1.0, ((width - textDim.getX()) / 2), ((height - textDim.getY()) / 2), fontSize, "X"); + } +} diff --git a/src/main/java/de/extio/game_engine/renderer/g2d/theme/DreamThemeFactoryBean.java b/src/main/java/de/extio/game_engine/renderer/g2d/theme/DreamThemeFactoryBean.java new file mode 100644 index 0000000..482b008 --- /dev/null +++ b/src/main/java/de/extio/game_engine/renderer/g2d/theme/DreamThemeFactoryBean.java @@ -0,0 +1,36 @@ +package de.extio.game_engine.renderer.g2d.theme; + +import org.springframework.beans.factory.FactoryBean; + +public class DreamThemeFactoryBean implements FactoryBean { + + @Override + public Theme getObject() { + return Theme.builder() + .name("Dream") + .patternRendererName("dreamPatternRenderer") + .borderOuter(new HSBColor(0.74f, 0.22f, 0.80f)) + .borderInner(new HSBColor(0.83f, 0.18f, 0.95f)) + .borderInnerDisabled(new HSBColor(0.74f, 0.10f, 0.35f)) + .backgroundNormal(new HSBColor(0.74f, 0.55f, 0.16f)) + .backgroundSelected(new HSBColor(0.88f, 0.55f, 0.85f)) + .textNormal(new HSBColor(0.12f, 0.03f, 0.96f)) + .textDisabled(new HSBColor(0.12f, 0.02f, 0.70f)) + .selectionPrimary(new HSBColor(0.88f, 0.55f, 0.85f)) + .selectionSecondary(new HSBColor(0.55f, 0.30f, 0.95f)) + .windowBackground(new HSBColor(0.74f, 0.70f, 0.07f)) + .hoverBrightnessAdjustment(0.28f) + .pressedBrightnessAdjustment(0.45f) + .build(); + } + + @Override + public Class getObjectType() { + return Theme.class; + } + + @Override + public boolean isSingleton() { + return true; + } +} diff --git a/src/main/java/de/extio/game_engine/renderer/g2d/theme/FantasyPatternRenderer.java b/src/main/java/de/extio/game_engine/renderer/g2d/theme/FantasyPatternRenderer.java new file mode 100644 index 0000000..6a8dfcf --- /dev/null +++ b/src/main/java/de/extio/game_engine/renderer/g2d/theme/FantasyPatternRenderer.java @@ -0,0 +1,107 @@ +package de.extio.game_engine.renderer.g2d.theme; + +import java.awt.Color; +import java.awt.Graphics2D; + +import org.springframework.context.annotation.Conditional; +import org.springframework.stereotype.Component; + +import de.extio.game_engine.renderer.g2d.G2DRendererCondition; + +@Conditional(G2DRendererCondition.class) +@Component +public class FantasyPatternRenderer implements PatternRenderer { + + @Override + public void drawDecorativeBorder(final Graphics2D g2d, final int x, final int y, final int width, final int height, final int strength, final Color color) { + final var s = Math.max(1, strength); + final var s2 = s * 2; + final var s3 = s * 3; + + g2d.setColor(color); + + g2d.fillRect(x, y, width, s); + g2d.fillRect(x, y + height - s, width, s); + g2d.fillRect(x, y, s, height); + g2d.fillRect(x + width - s, y, s, height); + + g2d.fillRect(x + s2, y + s2, width - s3 * 2, s); + g2d.fillRect(x + s2, y + height - s2 - s, width - s3 * 2, s); + g2d.fillRect(x + s2, y + s2, s, height - s3 * 2); + g2d.fillRect(x + width - s2 - s, y + s2, s, height - s3 * 2); + + final var tri = Math.max(6, s * 4); + g2d.fillPolygon(new int[] { x + s, x + tri, x + s }, new int[] { y + s, y + s, y + tri }, 3); + g2d.fillPolygon(new int[] { x + width - s, x + width - tri, x + width - s }, new int[] { y + s, y + s, y + tri }, 3); + g2d.fillPolygon(new int[] { x + s, x + tri, x + s }, new int[] { y + height - s, y + height - s, y + height - tri }, 3); + g2d.fillPolygon(new int[] { x + width - s, x + width - tri, x + width - s }, new int[] { y + height - s, y + height - s, y + height - tri }, 3); + } + + @Override + public void drawDecorativeBorderFilled(final Graphics2D g2d, final int x, final int y, final int width, final int height, final int strength, final Color borderColor, final Color backgroundColor) { + final var s = Math.max(1, strength); + g2d.setColor(backgroundColor); + g2d.fillRect(x + s, y + s, Math.max(0, width - s * 2), Math.max(0, height - s * 2)); + this.drawDecorativeBorder(g2d, x, y, width, height, s, borderColor); + } + + @Override + public void drawWindowPanel(final Graphics2D g2d, final int x, final int y, final int width, final int height, final boolean thickBorder, final Color innerBorderColor, final Color outerBorderColor, final Color backgroundColor, final double scaleFactor) { + final var s = Math.max((thickBorder ? 5 : 3), (int) ((thickBorder ? 7 : 4) * scaleFactor)); + final var s2 = s * 2; + + final var bg = new Color(backgroundColor.getRed(), backgroundColor.getGreen(), backgroundColor.getBlue(), 175); + this.drawDecorativeBorderFilled(g2d, x + s, y + s, width - s2, height - s2, s, innerBorderColor, bg); + this.drawDecorativeBorder(g2d, x, y, width, height, s, outerBorderColor); + } + + @Override + public void drawCloseButton(final Graphics2D g2d, final int width, final int height, final boolean enabled, final int state, final Color backgroundColor, final double scaleFactor, final Theme theme) { + final var STATE_HOVERED = 4; + final var STATE_TOGGLED = 1; + final var STATE_PRESSED = 2; + + final var highlight = (state & (STATE_HOVERED | STATE_TOGGLED)) != 0; + final var pressed = (state & STATE_PRESSED) != 0; + + Color bg = backgroundColor; + if (bg == null) { + bg = theme.getBorderInner().toColor(); + } + if (highlight) { + bg = theme.getSelectionSecondary().toColor(); + } + if (pressed) { + bg = theme.getSelectionPrimary().toColor(); + } + + g2d.setColor(bg); + g2d.fillRect(0, 0, width, height); + + final var s = Math.max(1, (int) (2 * scaleFactor)); + this.drawDecorativeBorder(g2d, 0, 0, width, height, s, theme.getBorderOuter().toColor()); + + if (!enabled) { + return; + } + + final Color fg; + if (pressed) { + fg = theme.getTextNormal().toColor(); + } + else if (highlight) { + fg = Color.BLACK; + } + else { + fg = theme.getTextDisabled().toColor(); + } + g2d.setColor(fg); + + final var crossSize = Math.min(width, height) / 2; + final var centerX = width / 2; + final var centerY = height / 2; + final var t = Math.max(1, (int) (2 * scaleFactor)); + g2d.fillRect(centerX - crossSize / 2, centerY - t / 2, crossSize, t); + g2d.fillRect(centerX - t / 2, centerY - crossSize / 2, t, crossSize); + } +} diff --git a/src/main/java/de/extio/game_engine/renderer/g2d/theme/FantasyThemeFactoryBean.java b/src/main/java/de/extio/game_engine/renderer/g2d/theme/FantasyThemeFactoryBean.java new file mode 100644 index 0000000..6f5ef2c --- /dev/null +++ b/src/main/java/de/extio/game_engine/renderer/g2d/theme/FantasyThemeFactoryBean.java @@ -0,0 +1,36 @@ +package de.extio.game_engine.renderer.g2d.theme; + +import org.springframework.beans.factory.FactoryBean; + +public class FantasyThemeFactoryBean implements FactoryBean { + + @Override + public Theme getObject() { + return Theme.builder() + .name("Fantasy") + .patternRendererName("fantasyPatternRenderer") + .borderOuter(new HSBColor(0.12f, 0.55f, 0.65f)) + .borderInner(new HSBColor(0.14f, 0.35f, 0.92f)) + .borderInnerDisabled(new HSBColor(0.12f, 0.25f, 0.25f)) + .backgroundNormal(new HSBColor(0.32f, 0.70f, 0.12f)) + .backgroundSelected(new HSBColor(0.13f, 0.80f, 0.75f)) + .textNormal(new HSBColor(0.12f, 0.10f, 0.96f)) + .textDisabled(new HSBColor(0.12f, 0.05f, 0.65f)) + .selectionPrimary(new HSBColor(0.13f, 0.80f, 0.75f)) + .selectionSecondary(new HSBColor(0.58f, 0.40f, 0.90f)) + .windowBackground(new HSBColor(0.32f, 0.75f, 0.06f)) + .hoverBrightnessAdjustment(0.18f) + .pressedBrightnessAdjustment(0.34f) + .build(); + } + + @Override + public Class getObjectType() { + return Theme.class; + } + + @Override + public boolean isSingleton() { + return true; + } +} diff --git a/src/main/java/de/extio/game_engine/renderer/g2d/theme/ModernPatternRenderer.java b/src/main/java/de/extio/game_engine/renderer/g2d/theme/ModernPatternRenderer.java new file mode 100644 index 0000000..f9d0444 --- /dev/null +++ b/src/main/java/de/extio/game_engine/renderer/g2d/theme/ModernPatternRenderer.java @@ -0,0 +1,99 @@ +package de.extio.game_engine.renderer.g2d.theme; + +import java.awt.Color; +import java.awt.Graphics2D; + +import org.springframework.context.annotation.Conditional; +import org.springframework.stereotype.Component; + +import de.extio.game_engine.renderer.g2d.G2DRendererCondition; +import de.extio.game_engine.renderer.g2d.bo.rendering.G2DDrawFont; + +@Conditional(G2DRendererCondition.class) +@Component +public class ModernPatternRenderer implements PatternRenderer { + + @Override + public void drawDecorativeBorder(final Graphics2D g2d, final int x, final int y, final int width, final int height, final int strength, final Color color) { + final var s = Math.max(1, strength); + final var s2 = s * 2; + + g2d.setColor(color); + + g2d.fillRect(x, y, width, s); + g2d.fillRect(x, y + height - s, width, s); + g2d.fillRect(x, y, s, height); + g2d.fillRect(x + width - s, y, s, height); + + final var notch = Math.max(1, s2); + g2d.fillRect(x + s, y + s, notch, s); + g2d.fillRect(x + width - s - notch, y + s, notch, s); + g2d.fillRect(x + s, y + height - s - s, notch, s); + g2d.fillRect(x + width - s - notch, y + height - s - s, notch, s); + } + + @Override + public void drawDecorativeBorderFilled(final Graphics2D g2d, final int x, final int y, final int width, final int height, final int strength, final Color borderColor, final Color backgroundColor) { + final var s = Math.max(1, strength); + g2d.setColor(backgroundColor); + g2d.fillRect(x + s, y + s, Math.max(0, width - s * 2), Math.max(0, height - s * 2)); + this.drawDecorativeBorder(g2d, x, y, width, height, s, borderColor); + } + + @Override + public void drawWindowPanel(final Graphics2D g2d, final int x, final int y, final int width, final int height, final boolean thickBorder, final Color innerBorderColor, final Color outerBorderColor, final Color backgroundColor, final double scaleFactor) { + final var s = Math.max((thickBorder ? 3 : 2), (int) ((thickBorder ? 5 : 3) * scaleFactor)); + final var s2 = s * 2; + + final var bg = new Color(backgroundColor.getRed(), backgroundColor.getGreen(), backgroundColor.getBlue(), 210); + this.drawDecorativeBorderFilled(g2d, x + s, y + s, width - s2, height - s2, s, innerBorderColor, bg); + this.drawDecorativeBorder(g2d, x, y, width, height, s, outerBorderColor); + } + + @Override + public void drawCloseButton(final Graphics2D g2d, final int width, final int height, final boolean enabled, final int state, final Color backgroundColor, final double scaleFactor, final Theme theme) { + final var STATE_HOVERED = 4; + final var STATE_TOGGLED = 1; + final var STATE_PRESSED = 2; + + final var highlight = (state & (STATE_HOVERED | STATE_TOGGLED)) != 0; + final var pressed = (state & STATE_PRESSED) != 0; + + Color bgColor = backgroundColor; + if (bgColor == null) { + bgColor = theme.getBorderInner().toColor(); + } + if (highlight) { + bgColor = theme.getBackgroundNormal().toColor(); + } + if (pressed) { + bgColor = theme.getSelectionPrimary().toColor(); + } + + g2d.setColor(bgColor); + g2d.fillRect(0, 0, width, height); + + final var s = Math.max(1, (int) (2 * scaleFactor)); + this.drawDecorativeBorder(g2d, 0, 0, width, height, s, theme.getBorderOuter().toColor()); + + if (!enabled) { + return; + } + + final Color textColor; + if (pressed) { + textColor = theme.getTextNormal().toColor(); + } + else if (highlight) { + textColor = theme.getSelectionSecondary().toColor(); + } + else { + textColor = Color.BLACK; + } + g2d.setColor(textColor); + + final var fontSize = (int) (14 * scaleFactor); + final var textDim = G2DDrawFont.getTextDimensions("X", g2d, 14, scaleFactor); + G2DDrawFont.renderText(g2d, textDim, 1.0, ((width - textDim.getX()) / 2), ((height - textDim.getY()) / 2), fontSize, "X"); + } +} diff --git a/src/main/java/de/extio/game_engine/renderer/g2d/theme/ModernThemeFactoryBean.java b/src/main/java/de/extio/game_engine/renderer/g2d/theme/ModernThemeFactoryBean.java new file mode 100644 index 0000000..2211bd2 --- /dev/null +++ b/src/main/java/de/extio/game_engine/renderer/g2d/theme/ModernThemeFactoryBean.java @@ -0,0 +1,36 @@ +package de.extio.game_engine.renderer.g2d.theme; + +import org.springframework.beans.factory.FactoryBean; + +public class ModernThemeFactoryBean implements FactoryBean { + + @Override + public Theme getObject() { + return Theme.builder() + .name("Modern") + .patternRendererName("modernPatternRenderer") + .borderOuter(new HSBColor(0.58f, 0.08f, 0.82f)) + .borderInner(new HSBColor(0.58f, 0.10f, 0.92f)) + .borderInnerDisabled(new HSBColor(0.58f, 0.05f, 0.45f)) + .backgroundNormal(new HSBColor(0.60f, 0.55f, 0.20f)) + .backgroundSelected(new HSBColor(0.14f, 0.75f, 0.78f)) + .textNormal(new HSBColor(0.00f, 0.00f, 0.92f)) + .textDisabled(new HSBColor(0.00f, 0.00f, 0.65f)) + .selectionPrimary(new HSBColor(0.14f, 0.75f, 0.78f)) + .selectionSecondary(new HSBColor(0.50f, 0.20f, 0.98f)) + .windowBackground(new HSBColor(0.60f, 0.45f, 0.10f)) + .hoverBrightnessAdjustment(0.20f) + .pressedBrightnessAdjustment(0.35f) + .build(); + } + + @Override + public Class getObjectType() { + return Theme.class; + } + + @Override + public boolean isSingleton() { + return true; + } +} diff --git a/src/main/java/de/extio/game_engine/renderer/g2d/theme/NeonThemeFactoryBean.java b/src/main/java/de/extio/game_engine/renderer/g2d/theme/NeonThemeFactoryBean.java new file mode 100644 index 0000000..f37cbe6 --- /dev/null +++ b/src/main/java/de/extio/game_engine/renderer/g2d/theme/NeonThemeFactoryBean.java @@ -0,0 +1,36 @@ +package de.extio.game_engine.renderer.g2d.theme; + +import org.springframework.beans.factory.FactoryBean; + +public class NeonThemeFactoryBean implements FactoryBean { + + @Override + public Theme getObject() { + return Theme.builder() + .name("Neon") + .patternRendererName("dreamPatternRenderer") + .borderOuter(new HSBColor(0.83f, 0.80f, 0.95f)) + .borderInner(new HSBColor(0.52f, 0.85f, 0.92f)) + .borderInnerDisabled(new HSBColor(0.83f, 0.20f, 0.25f)) + .backgroundNormal(new HSBColor(0.74f, 0.80f, 0.09f)) + .backgroundSelected(new HSBColor(0.52f, 0.85f, 0.92f)) + .textNormal(new HSBColor(0.00f, 0.00f, 0.98f)) + .textDisabled(new HSBColor(0.00f, 0.00f, 0.70f)) + .selectionPrimary(new HSBColor(0.52f, 0.85f, 0.92f)) + .selectionSecondary(new HSBColor(0.16f, 0.85f, 0.95f)) + .windowBackground(new HSBColor(0.74f, 0.90f, 0.05f)) + .hoverBrightnessAdjustment(0.30f) + .pressedBrightnessAdjustment(0.55f) + .build(); + } + + @Override + public Class getObjectType() { + return Theme.class; + } + + @Override + public boolean isSingleton() { + return true; + } +} diff --git a/src/main/java/de/extio/game_engine/renderer/g2d/theme/NoirThemeFactoryBean.java b/src/main/java/de/extio/game_engine/renderer/g2d/theme/NoirThemeFactoryBean.java new file mode 100644 index 0000000..f1698df --- /dev/null +++ b/src/main/java/de/extio/game_engine/renderer/g2d/theme/NoirThemeFactoryBean.java @@ -0,0 +1,36 @@ +package de.extio.game_engine.renderer.g2d.theme; + +import org.springframework.beans.factory.FactoryBean; + +public class NoirThemeFactoryBean implements FactoryBean { + + @Override + public Theme getObject() { + return Theme.builder() + .name("Noir") + .patternRendererName("urbanPatternRenderer") + .borderOuter(new HSBColor(0.00f, 0.00f, 0.18f)) + .borderInner(new HSBColor(0.00f, 0.00f, 0.45f)) + .borderInnerDisabled(new HSBColor(0.00f, 0.00f, 0.12f)) + .backgroundNormal(new HSBColor(0.62f, 0.20f, 0.08f)) + .backgroundSelected(new HSBColor(0.12f, 0.15f, 0.85f)) + .textNormal(new HSBColor(0.00f, 0.00f, 0.92f)) + .textDisabled(new HSBColor(0.00f, 0.00f, 0.55f)) + .selectionPrimary(new HSBColor(0.12f, 0.15f, 0.85f)) + .selectionSecondary(new HSBColor(0.58f, 0.12f, 0.75f)) + .windowBackground(new HSBColor(0.62f, 0.30f, 0.05f)) + .hoverBrightnessAdjustment(0.12f) + .pressedBrightnessAdjustment(0.26f) + .build(); + } + + @Override + public Class getObjectType() { + return Theme.class; + } + + @Override + public boolean isSingleton() { + return true; + } +} diff --git a/src/main/java/de/extio/game_engine/renderer/g2d/theme/UrbanPatternRenderer.java b/src/main/java/de/extio/game_engine/renderer/g2d/theme/UrbanPatternRenderer.java new file mode 100644 index 0000000..7fe498c --- /dev/null +++ b/src/main/java/de/extio/game_engine/renderer/g2d/theme/UrbanPatternRenderer.java @@ -0,0 +1,113 @@ +package de.extio.game_engine.renderer.g2d.theme; + +import java.awt.Color; +import java.awt.Graphics2D; + +import org.springframework.context.annotation.Conditional; +import org.springframework.stereotype.Component; + +import de.extio.game_engine.renderer.g2d.G2DRendererCondition; + +@Conditional(G2DRendererCondition.class) +@Component +public class UrbanPatternRenderer implements PatternRenderer { + + @Override + public void drawDecorativeBorder(final Graphics2D g2d, final int x, final int y, final int width, final int height, final int strength, final Color color) { + final var s = Math.max(1, strength); + final var seg = Math.max(s * 3, 6); + final var gap = Math.max(s, 2); + + g2d.setColor(color); + + for (int xx = x; xx < x + width; xx += seg + gap) { + final var w = Math.min(seg, x + width - xx); + g2d.fillRect(xx, y, w, s); + g2d.fillRect(xx, y + height - s, w, s); + } + + for (int yy = y; yy < y + height; yy += seg + gap) { + final var h = Math.min(seg, y + height - yy); + g2d.fillRect(x, yy, s, h); + g2d.fillRect(x + width - s, yy, s, h); + } + + final var pin = Math.max(2, s * 2); + g2d.fillRect(x + s, y + s, pin, pin); + g2d.fillRect(x + width - s - pin, y + s, pin, pin); + g2d.fillRect(x + s, y + height - s - pin, pin, pin); + g2d.fillRect(x + width - s - pin, y + height - s - pin, pin, pin); + } + + @Override + public void drawDecorativeBorderFilled(final Graphics2D g2d, final int x, final int y, final int width, final int height, final int strength, final Color borderColor, final Color backgroundColor) { + final var s = Math.max(1, strength); + g2d.setColor(backgroundColor); + g2d.fillRect(x + s, y + s, Math.max(0, width - s * 2), Math.max(0, height - s * 2)); + this.drawDecorativeBorder(g2d, x, y, width, height, s, borderColor); + } + + @Override + public void drawWindowPanel(final Graphics2D g2d, final int x, final int y, final int width, final int height, final boolean thickBorder, final Color innerBorderColor, final Color outerBorderColor, final Color backgroundColor, final double scaleFactor) { + final var s = Math.max((thickBorder ? 4 : 2), (int) ((thickBorder ? 6 : 3) * scaleFactor)); + final var s2 = s * 2; + + final var bg = new Color(backgroundColor.getRed(), backgroundColor.getGreen(), backgroundColor.getBlue(), 190); + this.drawDecorativeBorderFilled(g2d, x + s, y + s, width - s2, height - s2, s, innerBorderColor, bg); + this.drawDecorativeBorder(g2d, x, y, width, height, s, outerBorderColor); + } + + @Override + public void drawCloseButton(final Graphics2D g2d, final int width, final int height, final boolean enabled, final int state, final Color backgroundColor, final double scaleFactor, final Theme theme) { + final var STATE_HOVERED = 4; + final var STATE_TOGGLED = 1; + final var STATE_PRESSED = 2; + + final var highlight = (state & (STATE_HOVERED | STATE_TOGGLED)) != 0; + final var pressed = (state & STATE_PRESSED) != 0; + + final var base = (backgroundColor != null) ? backgroundColor : theme.getBorderInner().toColor(); + final var hsb = Color.RGBtoHSB(base.getRed(), base.getGreen(), base.getBlue(), null); + + final Color bg; + if (pressed) { + bg = theme.getSelectionPrimary().toColor(); + } + else if (highlight) { + bg = theme.getSelectionSecondary().toColor(); + } + else { + bg = Color.getHSBColor(hsb[0], Math.min(1.0f, hsb[1] * 0.6f), Math.min(1.0f, hsb[2] * 0.35f)); + } + + g2d.setColor(bg); + g2d.fillRect(0, 0, width, height); + + final var s = Math.max(1, (int) (2 * scaleFactor)); + this.drawDecorativeBorder(g2d, 0, 0, width, height, s, theme.getBorderOuter().toColor()); + + if (!enabled) { + return; + } + + final var crossSize = Math.min(width, height) / 2; + final var centerX = width / 2; + final var centerY = height / 2; + final var t = Math.max(1, (int) (2 * scaleFactor)); + + final Color fg; + if (pressed) { + fg = theme.getTextNormal().toColor(); + } + else if (highlight) { + fg = Color.BLACK; + } + else { + fg = theme.getTextDisabled().toColor(); + } + g2d.setColor(fg); + + g2d.fillRect(centerX - crossSize / 2, centerY - t / 2, crossSize, t); + g2d.fillRect(centerX - t / 2, centerY - crossSize / 2, t, crossSize); + } +} diff --git a/src/main/java/de/extio/game_engine/renderer/g2d/theme/UrbanThemeFactoryBean.java b/src/main/java/de/extio/game_engine/renderer/g2d/theme/UrbanThemeFactoryBean.java new file mode 100644 index 0000000..d918d7d --- /dev/null +++ b/src/main/java/de/extio/game_engine/renderer/g2d/theme/UrbanThemeFactoryBean.java @@ -0,0 +1,36 @@ +package de.extio.game_engine.renderer.g2d.theme; + +import org.springframework.beans.factory.FactoryBean; + +public class UrbanThemeFactoryBean implements FactoryBean { + + @Override + public Theme getObject() { + return Theme.builder() + .name("Urban") + .patternRendererName("urbanPatternRenderer") + .borderOuter(new HSBColor(0.06f, 0.05f, 0.35f)) + .borderInner(new HSBColor(0.06f, 0.08f, 0.70f)) + .borderInnerDisabled(new HSBColor(0.06f, 0.04f, 0.22f)) + .backgroundNormal(new HSBColor(0.58f, 0.25f, 0.18f)) + .backgroundSelected(new HSBColor(0.02f, 0.75f, 0.70f)) + .textNormal(new HSBColor(0.12f, 0.05f, 0.92f)) + .textDisabled(new HSBColor(0.12f, 0.02f, 0.60f)) + .selectionPrimary(new HSBColor(0.02f, 0.75f, 0.70f)) + .selectionSecondary(new HSBColor(0.52f, 0.18f, 0.92f)) + .windowBackground(new HSBColor(0.58f, 0.30f, 0.10f)) + .hoverBrightnessAdjustment(0.18f) + .pressedBrightnessAdjustment(0.32f) + .build(); + } + + @Override + public Class getObjectType() { + return Theme.class; + } + + @Override + public boolean isSingleton() { + return true; + } +} diff --git a/src/test/java/de/extio/game_engine/demo/DemoModule.java b/src/test/java/de/extio/game_engine/demo/DemoModule.java index bfb07cf..b48500d 100644 --- a/src/test/java/de/extio/game_engine/demo/DemoModule.java +++ b/src/test/java/de/extio/game_engine/demo/DemoModule.java @@ -24,6 +24,7 @@ import de.extio.game_engine.renderer.model.bo.HorizontalAlignment; import de.extio.game_engine.renderer.model.event.UiControlEvent; import de.extio.game_engine.renderer.work.RenderingBoPool; import de.extio.game_engine.resource.StaticResource; +import de.extio.game_engine.spatial2.model.ImmutableCoordI2; public class DemoModule extends AbstractClientModule { @@ -48,7 +49,7 @@ public class DemoModule extends AbstractClientModule { private Window mainWindow; private Window secondaryWindow; - + private Window themeSelectionWindow; private final Map themeSelectionByControlId = new HashMap<>(); @@ -56,19 +57,19 @@ public class DemoModule extends AbstractClientModule { @Override public void onLoad() { this.mainWindow = this.applicationContext.getBean(Window.class); - this.mainWindow.setNormalizedPosition(RendererControl.REFERENCE_RESOLUTION.divide(9)); this.mainWindow.setNormalizedDimension(RendererControl.REFERENCE_RESOLUTION.divide(9).multiply(7)); + this.mainWindow.setNormalizedPosition(centeredPosition(this.mainWindow.getNormalizedDimension())); this.mainWindow.setDraggable(true); this.secondaryWindow = this.applicationContext.getBean(Window.class); - this.secondaryWindow.setNormalizedPosition(RendererControl.REFERENCE_RESOLUTION.divide(7).multiply(3)); this.secondaryWindow.setNormalizedDimension(RendererControl.REFERENCE_RESOLUTION.divide(7).multiply(2)); + this.secondaryWindow.setNormalizedPosition(centeredPosition(this.secondaryWindow.getNormalizedDimension())); this.secondaryWindow.setDraggable(true); this.secondaryWindow.setCloseButton(true); this.themeSelectionWindow = this.applicationContext.getBean(Window.class); - this.themeSelectionWindow.setNormalizedPosition(RendererControl.REFERENCE_RESOLUTION.divide(7).multiply(3)); this.themeSelectionWindow.setNormalizedDimension(RendererControl.REFERENCE_RESOLUTION.divide(7).multiply(2)); + this.themeSelectionWindow.setNormalizedPosition(centeredPosition(this.themeSelectionWindow.getNormalizedDimension())); this.themeSelectionWindow.setDraggable(true); this.themeSelectionWindow.setCloseButton(true); @@ -209,38 +210,50 @@ public class DemoModule extends AbstractClientModule { final var title = themes.size() <= 1 ? "Theme" : "Select Theme"; + + final var paddingX = 20; + final var paddingBottom = 24; + final var columns = 2; + final var columnGap = 24; + final var buttonWidth = 340; + final var buttonHeight = 54; + final var headerHeight = 60; + final var currentHeight = 40; + final var startY = 150; + final var gapY = 14; + final var rows = (themes.size() + columns - 1) / columns; + + final var desiredWidth = (paddingX * 2) + (columns * buttonWidth) + ((columns - 1) * columnGap); + final var desiredHeight = startY + (rows * buttonHeight) + (Math.max(0, rows - 1) * gapY) + paddingBottom; + this.themeSelectionWindow.setNormalizedDimension(ImmutableCoordI2.create(desiredWidth, desiredHeight)); + this.themeSelectionWindow.setNormalizedPosition(centeredPosition(ImmutableCoordI2.create(desiredWidth, desiredHeight))); - var bo = this.renderingBoPool.acquire("DemoModule_ThemeSelect_Label_Title", ControlRenderingBo.class) - .setCaption(title) - .setFontSize(42) - .setType(LabelControl.class) - .setVisible(true) - .setEnabled(true) - .withDimensionAbsolute(RendererControl.REFERENCE_RESOLUTION.divide(7).multiply(2).substract(20).getX(), 60) - .withPositionRelative(10, 30); + var bo = this.renderingBoPool.acquire("DemoModule_ThemeSelect_Text_Title", DrawFontRenderingBo.class) + .setText(title) + .setSize(42) + .setAlignment(HorizontalAlignment.CENTER) + .withDimensionAbsolute(desiredWidth - (paddingX * 2), headerHeight) + .withPositionRelative(paddingX, 30); this.themeSelectionWindow.putRenderingBo(bo); final var currentTheme = this.themeManager.getCurrentTheme(); final var currentThemeName = currentTheme != null ? currentTheme.getName() : ""; - bo = this.renderingBoPool.acquire("DemoModule_ThemeSelect_Label_Current", ControlRenderingBo.class) - .setCaption(currentThemeName.isBlank() ? "" : "Current: " + currentThemeName) - .setFontSize(24) - .setType(LabelControl.class) - .setVisible(true) - .setEnabled(true) - .withDimensionAbsolute(RendererControl.REFERENCE_RESOLUTION.divide(7).multiply(2).substract(20).getX(), 40) - .withPositionRelative(10, 95); + bo = this.renderingBoPool.acquire("DemoModule_ThemeSelect_Text_Current", DrawFontRenderingBo.class) + .setText(currentThemeName.isBlank() ? "" : "Current: " + currentThemeName) + .setSize(24) + .setAlignment(HorizontalAlignment.CENTER) + .withDimensionAbsolute(desiredWidth - (paddingX * 2), currentHeight) + .withPositionRelative(paddingX, 95); this.themeSelectionWindow.putRenderingBo(bo); - - final var buttonWidth = 340; - final var buttonHeight = 54; - final var startY = 150; - final var gap = 14; for (var i = 0; i < themes.size(); i++) { final var themeName = themes.get(i); final var controlId = "DemoModule_ThemeSelect_Button_" + sanitizeThemeId(themeName); this.themeSelectionByControlId.put(controlId, themeName); + final var column = i % columns; + final var row = i / columns; + final var x = paddingX + (column * (buttonWidth + columnGap)); + final var y = startY + (row * (buttonHeight + gapY)); bo = this.renderingBoPool.acquire(controlId, ControlRenderingBo.class) .setCaption(themeName) @@ -249,13 +262,20 @@ public class DemoModule extends AbstractClientModule { .setVisible(true) .setEnabled(true) .withDimensionAbsolute(buttonWidth, buttonHeight) - .withPositionRelative((RendererControl.REFERENCE_RESOLUTION.divide(7).multiply(2).getX() - buttonWidth) / 2, startY + i * (buttonHeight + gap)); + .withPositionRelative(x, y); this.themeSelectionWindow.putRenderingBo(bo); } this.themeSelectionWindow.draw(); } + private static ImmutableCoordI2 centeredPosition(final de.extio.game_engine.spatial2.model.CoordI2 dimension) { + final var ref = RendererControl.REFERENCE_RESOLUTION; + final var x = Math.max(0, (ref.getX() - dimension.getX()) / 2); + final var y = Math.max(0, (ref.getY() - dimension.getY()) / 2); + return ImmutableCoordI2.create(x, y); + } + private static String sanitizeThemeId(final String themeName) { if (themeName == null || themeName.isBlank()) { return "Unknown";