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 7ff875c..d08a9a0 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 @@ -14,6 +14,7 @@ import de.extio.game_engine.renderer.g2d.theme.PatternRenderer; import de.extio.game_engine.renderer.g2d.theme.BevelDarkThemeFactoryBean; import de.extio.game_engine.renderer.g2d.theme.BevelLightThemeFactoryBean; import de.extio.game_engine.renderer.g2d.theme.BlueprintThemeFactoryBean; +import de.extio.game_engine.renderer.g2d.theme.ChronicleThemeFactoryBean; 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; @@ -21,6 +22,7 @@ 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.SteampunkThemeFactoryBean; 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; @@ -82,6 +84,11 @@ public class G2DAutoConfiguration { return new ContemporaryThemeFactoryBean(); } + @Bean + FactoryBean chronicleTheme() { + return new ChronicleThemeFactoryBean(); + } + @Bean FactoryBean urbanTheme() { return new UrbanThemeFactoryBean(); diff --git a/src/main/java/de/extio/game_engine/renderer/g2d/theme/ChroniclePatternRenderer.java b/src/main/java/de/extio/game_engine/renderer/g2d/theme/ChroniclePatternRenderer.java new file mode 100644 index 0000000..21acfbb --- /dev/null +++ b/src/main/java/de/extio/game_engine/renderer/g2d/theme/ChroniclePatternRenderer.java @@ -0,0 +1,311 @@ +package de.extio.game_engine.renderer.g2d.theme; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.GradientPaint; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.Stroke; +import java.awt.image.BufferedImage; + +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; +import de.extio.game_engine.spatial2.model.CoordI2; + +@Conditional(G2DRendererCondition.class) +@Component +public class ChroniclePatternRenderer 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) { + drawDecorativeBorder(g2d, x, y, width, height, strength, color, true); + } + + private void drawDecorativeBorder(final Graphics2D g2d, final int x, final int y, final int width, final int height, final int strength, final Color color, final boolean extraDecoration) { + final int s = Math.max(1, strength); + final int arc = Math.max(10, s * 8); + + final Object aaOld = g2d.getRenderingHint(RenderingHints.KEY_ANTIALIASING); + final Stroke strokeOld = g2d.getStroke(); + try { + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g2d.setColor(color); + g2d.setStroke(new BasicStroke(Math.max(1, s), BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); + g2d.drawRoundRect(x, y, Math.max(0, width - 1), Math.max(0, height - 1), arc, arc); + + if (extraDecoration) { + final int curl = Math.max(6, s * 6); + final int pad = Math.max(2, s * 2); + if (width > curl + pad * 2 && height > curl + pad * 2) { + g2d.drawArc(x + pad, y + pad, curl, curl, 180, 90); + g2d.drawArc(x + width - pad - curl, y + pad, curl, curl, 270, 90); + g2d.drawArc(x + pad, y + height - pad - curl, curl, curl, 90, 90); + g2d.drawArc(x + width - pad - curl, y + height - pad - curl, curl, curl, 0, 90); + } + } + } + finally { + g2d.setStroke(strokeOld); + if (aaOld != null) { + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, aaOld); + } + } + } + + @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 int s = Math.max(1, strength); + final int arc = Math.max(10, s * 8); + + final Object aaOld = g2d.getRenderingHint(RenderingHints.KEY_ANTIALIASING); + try { + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g2d.setColor(backgroundColor); + g2d.fillRoundRect(x + s, y + s, Math.max(0, width - s * 2), Math.max(0, height - s * 2), arc, arc); + } + finally { + if (aaOld != null) { + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, aaOld); + } + } + 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 int s = Math.max((thickBorder ? 4 : 2), (int) ((thickBorder ? 7 : 4) * scaleFactor)); + final int s2 = s * 2; + final int arc = Math.max(12, (int) (18 * scaleFactor)); + + final var bg = new Color(backgroundColor.getRed(), backgroundColor.getGreen(), backgroundColor.getBlue(), 210); + final Object aaOld = g2d.getRenderingHint(RenderingHints.KEY_ANTIALIASING); + try { + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g2d.setColor(bg); + g2d.fillRoundRect(x + s, y + s, width - s2, height - s2, arc, arc); + } + finally { + if (aaOld != null) { + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, aaOld); + } + } + + this.drawDecorativeBorder(g2d, x + s, y + s, width - s2, height - s2, s, outerBorderColor, false); + this.drawDecorativeBorder(g2d, x, y, width, height, s, innerBorderColor, false); + } + + @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 int STATE_HOVERED = 4; + final int STATE_TOGGLED = 1; + final int STATE_PRESSED = 2; + + final boolean hovered = (state & STATE_HOVERED) != 0; + final boolean pressed = (state & STATE_PRESSED) != 0; + final boolean toggled = (state & STATE_TOGGLED) != 0; + + Color base = backgroundColor; + if (base == null) { + base = theme.getWindowBackground().toColor(); + } + if (hovered || toggled) { + base = theme.getSelectionSecondary().toColor(); + } + if (pressed) { + base = theme.getSelectionPrimary().toColor(); + } + + final var hsb = Color.RGBtoHSB(base.getRed(), base.getGreen(), base.getBlue(), null); + final float topB = pressed ? Math.max(0.0f, hsb[2] - 0.08f) : Math.min(1.0f, hsb[2] + 0.10f); + final float bottomB = pressed ? Math.min(1.0f, hsb[2] + 0.04f) : Math.max(0.0f, hsb[2] - 0.12f); + final var top = new Color(Color.getHSBColor(hsb[0], Math.min(1.0f, hsb[1] * 0.9f), topB).getRed(), Color.getHSBColor(hsb[0], Math.min(1.0f, hsb[1] * 0.9f), topB).getGreen(), Color.getHSBColor(hsb[0], Math.min(1.0f, hsb[1] * 0.9f), topB).getBlue(), 220); + final var bottom = new Color(Color.getHSBColor(hsb[0], hsb[1], bottomB).getRed(), Color.getHSBColor(hsb[0], hsb[1], bottomB).getGreen(), Color.getHSBColor(hsb[0], hsb[1], bottomB).getBlue(), 220); + + final int arc = Math.max(10, (int) (14 * scaleFactor)); + final Object aaOld = g2d.getRenderingHint(RenderingHints.KEY_ANTIALIASING); + try { + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + final var oldPaint = g2d.getPaint(); + g2d.setPaint(new GradientPaint(0, 0, top, 0, height, bottom)); + g2d.fillRoundRect(0, 0, width, height, arc, arc); + g2d.setPaint(oldPaint); + } + finally { + if (aaOld != null) { + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, aaOld); + } + } + + final int s = Math.max(1, (int) (2 * scaleFactor)); + final var border = hovered ? theme.getSelectionPrimary().toColor() : theme.getBorderOuter().toColor(); + this.drawDecorativeBorder(g2d, 0, 0, width, height, s, border, false); + + if (!enabled) { + return; + } + + final Color fg; + if (pressed) { + fg = theme.getTextNormal().toColor(); + } + else if (hovered) { + fg = Color.BLACK; + } + else { + fg = theme.getTextNormal().toColor(); + } + g2d.setColor(fg); + + final int 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"); + } + + private BufferedImage patternCache; + + private BufferedImage getPatternCache() { + if (this.patternCache == null) { + final int cell = 90; + this.patternCache = new BufferedImage(cell, cell, BufferedImage.TYPE_INT_ARGB); + final Graphics2D g2d = this.patternCache.createGraphics(); + g2d.setBackground(new Color(0, 0, 0, 0)); + g2d.clearRect(0, 0, cell, cell); + + g2d.setColor(new Color(255, 255, 255, 18)); + g2d.fillOval(14, 18, 12, 12); + g2d.fillOval(60, 58, 9, 9); + g2d.fillOval(42, 26, 7, 7); + + g2d.setColor(new Color(255, 255, 255, 10)); + for (int i = 6; i < cell; i += 18) { + g2d.drawLine(0, i, cell, i); + } + + g2d.dispose(); + } + return this.patternCache; + } + + @Override + public void drawBackgroundPattern(final Graphics2D g2d, final CoordI2 offset, final CoordI2 viewPort) { + final var cache = this.getPatternCache(); + final int cell = cache.getWidth(); + + final int offX = Math.floorMod(offset.getX(), cell); + final int offY = Math.floorMod(offset.getY(), cell); + + for (int x = -cell; x < viewPort.getX() + cell; x += cell) { + for (int y = -cell; y < viewPort.getY() + cell; y += cell) { + g2d.drawImage(cache, x + offX, y + offY, null); + } + } + } + + @Override + public void drawButton(final Graphics2D g2d, final int x, final int y, final int width, final int height, final boolean enabled, final int state, final Color backgroundColor, final double scaleFactor, final Theme theme) { + final int STATE_TOGGLED = 1; + final int STATE_PRESSED = 2; + final int STATE_HOVERED = 4; + + final boolean toggled = (state & STATE_TOGGLED) != 0; + final boolean pressed = (state & STATE_PRESSED) != 0; + final boolean hovered = (state & STATE_HOVERED) != 0; + + float h, s, b; + + if (backgroundColor == null) { + final var baseColor = !toggled ? theme.getBackgroundNormal() : theme.getBackgroundSelected(); + h = baseColor.getHue(); + s = baseColor.getSaturation(); + b = baseColor.getBrightness(); + if (pressed) { + b += theme.getPressedBrightnessAdjustment(); + } + else if (hovered) { + b += theme.getHoverBrightnessAdjustment(); + } + } + else { + final var hsb = new float[3]; + Color.RGBtoHSB(backgroundColor.getRed(), backgroundColor.getGreen(), backgroundColor.getBlue(), hsb); + h = hsb[0]; + s = hsb[1]; + b = toggled ? 0.55F : 0.35F; + if (pressed) { + b += theme.getPressedBrightnessAdjustment(); + } + else if (hovered) { + b += theme.getHoverBrightnessAdjustment(); + } + } + + b = Math.max(0.0f, Math.min(1.0f, b)); + Color base = Color.getHSBColor(h, s, b); + if (!enabled) { + final var hsb = Color.RGBtoHSB(base.getRed(), base.getGreen(), base.getBlue(), null); + base = Color.getHSBColor(hsb[0], Math.min(1.0f, hsb[1] * 0.15f), Math.min(1.0f, hsb[2] * 0.65f)); + } + + final var baseHsb = Color.RGBtoHSB(base.getRed(), base.getGreen(), base.getBlue(), null); + final float topB = pressed ? Math.max(0.0f, baseHsb[2] - 0.10f) : Math.min(1.0f, baseHsb[2] + 0.12f); + final float bottomB = pressed ? Math.min(1.0f, baseHsb[2] + 0.04f) : Math.max(0.0f, baseHsb[2] - 0.12f); + final var top = Color.getHSBColor(baseHsb[0], Math.min(1.0f, baseHsb[1] * 0.95f), topB); + final var bottom = Color.getHSBColor(baseHsb[0], baseHsb[1], bottomB); + + final int arc = Math.max(10, (int) (14 * scaleFactor)); + final Object aaOld = g2d.getRenderingHint(RenderingHints.KEY_ANTIALIASING); + try { + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + final var oldPaint = g2d.getPaint(); + g2d.setPaint(new GradientPaint(x, y, top, x, y + height, bottom)); + g2d.fillRoundRect(x, y, width, height, arc, arc); + g2d.setPaint(oldPaint); + } + finally { + if (aaOld != null) { + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, aaOld); + } + } + + int shimmerAlpha = hovered ? 30 : 16; + if (pressed) { + shimmerAlpha = 10; + } + if (!enabled) { + shimmerAlpha = 8; + } + if (shimmerAlpha > 0 && width > 8 && height > 8) { + final Object aaOld2 = g2d.getRenderingHint(RenderingHints.KEY_ANTIALIASING); + try { + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g2d.setColor(new Color(255, 255, 255, shimmerAlpha)); + g2d.fillRoundRect(x + 2, y + 2, width - 4, Math.max(3, height / 3), arc, arc); + } + finally { + if (aaOld2 != null) { + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, aaOld2); + } + } + } + + final int borderStrength = Math.max(1, (int) (2 * scaleFactor)); + final var outerBorder = hovered ? theme.getSelectionPrimary().toColor() : theme.getBorderOuter().toColor(); + final var innerBorder = enabled ? theme.getBorderInner().toColor() : theme.getBorderInnerDisabled().toColor(); + this.drawDecorativeBorder(g2d, x, y, width, height, borderStrength, outerBorder); + final int inset = Math.max(1, borderStrength); + if (width > inset * 4 && height > inset * 4) { + this.drawDecorativeBorder(g2d, x + inset, y + inset, width - inset * 2, height - inset * 2, Math.max(1, borderStrength / 2), innerBorder); + } + + if (hovered || toggled) { + final var accentBase = pressed ? theme.getSelectionSecondary().toColor() : theme.getSelectionPrimary().toColor(); + final var accent = new Color(accentBase.getRed(), accentBase.getGreen(), accentBase.getBlue(), enabled ? (pressed ? 90 : 120) : 60); + final int underline = Math.max(2, (int) (3 * scaleFactor)); + g2d.setColor(accent); + g2d.fillRoundRect(x + inset, y + height - inset - underline, Math.max(0, width - inset * 2), underline, arc, arc); + } + } +} diff --git a/src/main/java/de/extio/game_engine/renderer/g2d/theme/ChronicleThemeFactoryBean.java b/src/main/java/de/extio/game_engine/renderer/g2d/theme/ChronicleThemeFactoryBean.java new file mode 100644 index 0000000..b5fbb81 --- /dev/null +++ b/src/main/java/de/extio/game_engine/renderer/g2d/theme/ChronicleThemeFactoryBean.java @@ -0,0 +1,37 @@ +package de.extio.game_engine.renderer.g2d.theme; + +import org.springframework.beans.factory.FactoryBean; + +public class ChronicleThemeFactoryBean implements FactoryBean { + + @Override + public Theme getObject() { + return Theme.builder() + .name("Chronicle") + .font("Lato-Regular.ttf") + .patternRendererName("chroniclePatternRenderer") + .borderOuter(new HSBColor(0.08f, 0.18f, 0.45f)) + .borderInner(new HSBColor(0.08f, 0.12f, 0.70f)) + .borderInnerDisabled(new HSBColor(0.08f, 0.06f, 0.40f)) + .backgroundNormal(new HSBColor(0.11f, 0.22f, 0.86f)) + .backgroundSelected(new HSBColor(0.06f, 0.30f, 0.70f)) + .textNormal(new HSBColor(0.07f, 0.10f, 0.18f)) + .textDisabled(new HSBColor(0.07f, 0.08f, 0.35f)) + .selectionPrimary(new HSBColor(0.00f, 0.30f, 0.62f)) + .selectionSecondary(new HSBColor(0.12f, 0.20f, 0.95f)) + .windowBackground(new HSBColor(0.11f, 0.18f, 0.78f)) + .hoverBrightnessAdjustment(0.12f) + .pressedBrightnessAdjustment(0.24f) + .build(); + } + + @Override + public Class getObjectType() { + return Theme.class; + } + + @Override + public boolean isSingleton() { + return true; + } +}