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.
This commit is contained in:
extio-de
2025-12-27 23:40:14 +01:00
parent bc627f3fe8
commit e87edd27a2
13 changed files with 758 additions and 26 deletions

View File

@@ -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.G2DThemeManager;
import de.extio.game_engine.renderer.g2d.theme.PatternRenderer; 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.SpacecraftThemeFactoryBean;
import de.extio.game_engine.renderer.g2d.theme.Theme; 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.renderer.g2d.theme.VintageThemeFactoryBean;
import de.extio.game_engine.resource.StaticResourceService; import de.extio.game_engine.resource.StaticResourceService;
@@ -50,6 +57,41 @@ public class G2DAutoConfiguration {
return new VintageThemeFactoryBean(); return new VintageThemeFactoryBean();
} }
@Bean
FactoryBean<Theme> modernTheme() {
return new ModernThemeFactoryBean();
}
@Bean
FactoryBean<Theme> contemporaryTheme() {
return new ContemporaryThemeFactoryBean();
}
@Bean
FactoryBean<Theme> urbanTheme() {
return new UrbanThemeFactoryBean();
}
@Bean
FactoryBean<Theme> dreamTheme() {
return new DreamThemeFactoryBean();
}
@Bean
FactoryBean<Theme> fantasyTheme() {
return new FantasyThemeFactoryBean();
}
@Bean
FactoryBean<Theme> noirTheme() {
return new NoirThemeFactoryBean();
}
@Bean
FactoryBean<Theme> neonTheme() {
return new NeonThemeFactoryBean();
}
@Bean @Bean
G2DThemeManager g2dThemeManager( G2DThemeManager g2dThemeManager(
final StaticResourceService staticResourceService, final StaticResourceService staticResourceService,

View File

@@ -0,0 +1,36 @@
package de.extio.game_engine.renderer.g2d.theme;
import org.springframework.beans.factory.FactoryBean;
public class ContemporaryThemeFactoryBean implements FactoryBean<Theme> {
@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;
}
}

View File

@@ -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");
}
}

View File

@@ -0,0 +1,36 @@
package de.extio.game_engine.renderer.g2d.theme;
import org.springframework.beans.factory.FactoryBean;
public class DreamThemeFactoryBean implements FactoryBean<Theme> {
@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;
}
}

View File

@@ -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);
}
}

View File

@@ -0,0 +1,36 @@
package de.extio.game_engine.renderer.g2d.theme;
import org.springframework.beans.factory.FactoryBean;
public class FantasyThemeFactoryBean implements FactoryBean<Theme> {
@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;
}
}

View File

@@ -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");
}
}

View File

@@ -0,0 +1,36 @@
package de.extio.game_engine.renderer.g2d.theme;
import org.springframework.beans.factory.FactoryBean;
public class ModernThemeFactoryBean implements FactoryBean<Theme> {
@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;
}
}

View File

@@ -0,0 +1,36 @@
package de.extio.game_engine.renderer.g2d.theme;
import org.springframework.beans.factory.FactoryBean;
public class NeonThemeFactoryBean implements FactoryBean<Theme> {
@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;
}
}

View File

@@ -0,0 +1,36 @@
package de.extio.game_engine.renderer.g2d.theme;
import org.springframework.beans.factory.FactoryBean;
public class NoirThemeFactoryBean implements FactoryBean<Theme> {
@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;
}
}

View File

@@ -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);
}
}

View File

@@ -0,0 +1,36 @@
package de.extio.game_engine.renderer.g2d.theme;
import org.springframework.beans.factory.FactoryBean;
public class UrbanThemeFactoryBean implements FactoryBean<Theme> {
@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;
}
}

View File

@@ -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.model.event.UiControlEvent;
import de.extio.game_engine.renderer.work.RenderingBoPool; import de.extio.game_engine.renderer.work.RenderingBoPool;
import de.extio.game_engine.resource.StaticResource; import de.extio.game_engine.resource.StaticResource;
import de.extio.game_engine.spatial2.model.ImmutableCoordI2;
public class DemoModule extends AbstractClientModule { public class DemoModule extends AbstractClientModule {
@@ -56,19 +57,19 @@ public class DemoModule extends AbstractClientModule {
@Override @Override
public void onLoad() { public void onLoad() {
this.mainWindow = this.applicationContext.getBean(Window.class); 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.setNormalizedDimension(RendererControl.REFERENCE_RESOLUTION.divide(9).multiply(7));
this.mainWindow.setNormalizedPosition(centeredPosition(this.mainWindow.getNormalizedDimension()));
this.mainWindow.setDraggable(true); this.mainWindow.setDraggable(true);
this.secondaryWindow = this.applicationContext.getBean(Window.class); 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.setNormalizedDimension(RendererControl.REFERENCE_RESOLUTION.divide(7).multiply(2));
this.secondaryWindow.setNormalizedPosition(centeredPosition(this.secondaryWindow.getNormalizedDimension()));
this.secondaryWindow.setDraggable(true); this.secondaryWindow.setDraggable(true);
this.secondaryWindow.setCloseButton(true); this.secondaryWindow.setCloseButton(true);
this.themeSelectionWindow = this.applicationContext.getBean(Window.class); 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.setNormalizedDimension(RendererControl.REFERENCE_RESOLUTION.divide(7).multiply(2));
this.themeSelectionWindow.setNormalizedPosition(centeredPosition(this.themeSelectionWindow.getNormalizedDimension()));
this.themeSelectionWindow.setDraggable(true); this.themeSelectionWindow.setDraggable(true);
this.themeSelectionWindow.setCloseButton(true); this.themeSelectionWindow.setCloseButton(true);
@@ -210,37 +211,49 @@ public class DemoModule extends AbstractClientModule {
? "Theme" ? "Theme"
: "Select Theme"; : "Select Theme";
var bo = this.renderingBoPool.acquire("DemoModule_ThemeSelect_Label_Title", ControlRenderingBo.class) final var paddingX = 20;
.setCaption(title) final var paddingBottom = 24;
.setFontSize(42) final var columns = 2;
.setType(LabelControl.class) final var columnGap = 24;
.setVisible(true) final var buttonWidth = 340;
.setEnabled(true) final var buttonHeight = 54;
.withDimensionAbsolute(RendererControl.REFERENCE_RESOLUTION.divide(7).multiply(2).substract(20).getX(), 60) final var headerHeight = 60;
.withPositionRelative(10, 30); 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_Text_Title", DrawFontRenderingBo.class)
.setText(title)
.setSize(42)
.setAlignment(HorizontalAlignment.CENTER)
.withDimensionAbsolute(desiredWidth - (paddingX * 2), headerHeight)
.withPositionRelative(paddingX, 30);
this.themeSelectionWindow.putRenderingBo(bo); this.themeSelectionWindow.putRenderingBo(bo);
final var currentTheme = this.themeManager.getCurrentTheme(); final var currentTheme = this.themeManager.getCurrentTheme();
final var currentThemeName = currentTheme != null ? currentTheme.getName() : ""; final var currentThemeName = currentTheme != null ? currentTheme.getName() : "";
bo = this.renderingBoPool.acquire("DemoModule_ThemeSelect_Label_Current", ControlRenderingBo.class) bo = this.renderingBoPool.acquire("DemoModule_ThemeSelect_Text_Current", DrawFontRenderingBo.class)
.setCaption(currentThemeName.isBlank() ? "" : "Current: " + currentThemeName) .setText(currentThemeName.isBlank() ? "" : "Current: " + currentThemeName)
.setFontSize(24) .setSize(24)
.setType(LabelControl.class) .setAlignment(HorizontalAlignment.CENTER)
.setVisible(true) .withDimensionAbsolute(desiredWidth - (paddingX * 2), currentHeight)
.setEnabled(true) .withPositionRelative(paddingX, 95);
.withDimensionAbsolute(RendererControl.REFERENCE_RESOLUTION.divide(7).multiply(2).substract(20).getX(), 40)
.withPositionRelative(10, 95);
this.themeSelectionWindow.putRenderingBo(bo); 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++) { for (var i = 0; i < themes.size(); i++) {
final var themeName = themes.get(i); final var themeName = themes.get(i);
final var controlId = "DemoModule_ThemeSelect_Button_" + sanitizeThemeId(themeName); final var controlId = "DemoModule_ThemeSelect_Button_" + sanitizeThemeId(themeName);
this.themeSelectionByControlId.put(controlId, 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) bo = this.renderingBoPool.acquire(controlId, ControlRenderingBo.class)
.setCaption(themeName) .setCaption(themeName)
@@ -249,13 +262,20 @@ public class DemoModule extends AbstractClientModule {
.setVisible(true) .setVisible(true)
.setEnabled(true) .setEnabled(true)
.withDimensionAbsolute(buttonWidth, buttonHeight) .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.putRenderingBo(bo);
} }
this.themeSelectionWindow.draw(); 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) { private static String sanitizeThemeId(final String themeName) {
if (themeName == null || themeName.isBlank()) { if (themeName == null || themeName.isBlank()) {
return "Unknown"; return "Unknown";