feat(theme): Introduce theming system with pattern renderers

- Added `ComponentState` enum to represent UI component states.
- Implemented `G2DThemeManager` to manage themes and pattern renderers.
- Created `HSBColor` class for HSB color representation and manipulation.
- Defined `PatternRenderer` interface for rendering UI patterns.
- Developed `SpacecraftPatternRenderer` and `VintagePatternRenderer` for default theming styles.
- Introduced `Theme` class to encapsulate theme properties and builder pattern.
- Added `SpacecraftThemeFactoryBean` and `VintageThemeFactoryBean` for theme instantiation.
- Updated `G2DWindowPanelControlImpl` to utilize theme manager.
- Removed obsolete `DrawWindowRenderingBo` interface.
- Enhanced `DemoModule` to support theme selection and application.
This commit is contained in:
extio-de
2025-12-27 23:25:02 +01:00
parent 6eb2ba271f
commit bc627f3fe8
36 changed files with 1276 additions and 1033 deletions

View File

@@ -0,0 +1,17 @@
package de.extio.game_engine.renderer;
import java.util.List;
import de.extio.game_engine.renderer.g2d.theme.Theme;
public interface ThemeManager {
List<String> getAvailableThemeNames();
Theme getCurrentTheme();
void setCurrentTheme(Theme theme);
void setCurrentTheme(String themeName);
}

View File

@@ -1,141 +0,0 @@
package de.extio.game_engine.renderer.container.legacy;
import java.util.function.Function;
import de.extio.game_engine.renderer.RendererControl;
import de.extio.game_engine.renderer.work.RenderingBoPool;
import de.extio.game_engine.spatial2.model.Area2;
/**
* Helper to position, draw and manage virtual windows in the UI.
* EnhancedWindow holds a state to switch between different representations and also can persist the window position.
*/
public class EnhancedWindow<T> extends Window {
public final static String PERSISTENT_STATE_PREFIX = "ENHANCEDWINDOW_POSITION_";
private final boolean persistentPosition;
private T state;
private final T initialState;
private final Function<T, Area2> areaFunction;
public static void resetWindows() {
// boolean removed = false;
// final Iterator<String> it = EngineFacade.instance().getPersistentClientState().state.keySet().iterator();
// while (it.hasNext()) {
// final String key = it.next();
// if (key.startsWith(EnhancedWindow.PERSISTENT_STATE_PREFIX)) {
// it.remove();
// removed = true;
// }
// }
// if (removed) {
// EngineFacade.instance().storePersistentClientState();
// }
synchronized (WINDOWS) {
for (final Window window : WINDOWS) {
if (window instanceof final EnhancedWindow<?> enhancedWindow) {
enhancedWindow.reset();
}
}
}
}
public EnhancedWindow(final String name, final boolean draggable, final boolean persistentPosition, final T initialState, final RenderingBoPool renderingBoPool, final RendererControl rendererControl, final Function<T, Area2> areaFunction) {
super(name, draggable, renderingBoPool, rendererControl);
this.initialState = initialState;
this.areaFunction = areaFunction;
this.persistentPosition = persistentPosition;
this.reset();
}
public String getName() {
return this.name;
}
public T getState() {
return this.state;
}
public void setState(final T state) {
if (state == null) {
this.reset();
return;
}
else {
this.state = state;
}
if (this.areaFunction != null) {
this.updateArea();
this.savePosition();
}
}
@Override
public void updateArea() {
if (this.areaFunction != null) {
final var area = this.areaFunction.apply(this.state);
this.setDimension(area.getDimension());
// this.resizeCentered(area.getDimension());
if (!this.persistentPosition) {
this.setPosition(area.getPosition());
}
this.updateRelativeScreenPosition();
}
super.updateArea();
}
@Override
public void reset() {
super.reset();
this.state = this.initialState;
this.updateArea();
this.loadPosition();
}
@Override
public void releaseDragging() {
if (this.isDragging()) {
this.savePosition();
}
super.releaseDragging();
}
private void loadPosition() {
if (this.persistentPosition && this.name != null) {
// final CoordD2 existingPosition = (CoordD2) EngineFacade.instance().getPersistentClientState().state.get(this.getPersistentStateKeyName());
// if (existingPosition != null) {
// LOGGER.debug(() -> "Position loaded: " + this.name + " " + existingPosition);
// this.relativeScreenPosition = MutableCoordD2.create(existingPosition);
// this.applyRelativeScreenPosition();
// }
/*else*/ if (this.areaFunction != null) {
final var area = this.areaFunction.apply(this.state);
this.setPosition(area.getPosition());
}
}
}
private void savePosition() {
if (!this.persistentPosition) {
}
// final CoordD2 existingPosition = (CoordD2) EngineFacade.instance().getPersistentClientState().state.get(this.getPersistentStateKeyName());
// if (existingPosition == null || !this.relativeScreenPosition.equals(existingPosition)) {
// EngineFacade.instance().getPersistentClientState().state.put(this.getPersistentStateKeyName(), MutableCoordD2.create(this.relativeScreenPosition));
// EngineFacade.instance().storePersistentClientState();
// LOGGER.debug(() -> "Position stored: " + this.name + " " + this.getPosition());
// }
}
// private String getPersistentStateKeyName() {
// return PERSISTENT_STATE_PREFIX.concat(this.name);
// }
}

View File

@@ -1,358 +0,0 @@
package de.extio.game_engine.renderer.container.legacy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.BiConsumer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import de.extio.game_engine.renderer.RendererControl;
import de.extio.game_engine.renderer.model.RenderingBo;
import de.extio.game_engine.renderer.model.RenderingBoLayer;
import de.extio.game_engine.renderer.model.bo.ControlRenderingBo;
import de.extio.game_engine.renderer.model.bo.ControlRenderingBo.WindowCloseButtonControl;
import de.extio.game_engine.renderer.model.color.RgbaColor;
import de.extio.game_engine.renderer.work.RenderingBoPool;
import de.extio.game_engine.renderer.model.bo.DrawWindowRenderingBo;
import de.extio.game_engine.spatial2.SpatialUtils2;
import de.extio.game_engine.spatial2.model.Area2;
import de.extio.game_engine.spatial2.model.CoordD2;
import de.extio.game_engine.spatial2.model.CoordI2;
import de.extio.game_engine.spatial2.model.HasPositionAndDimension2;
import de.extio.game_engine.spatial2.model.ImmutableCoordI2;
import de.extio.game_engine.spatial2.model.MutableCoordD2;
import de.extio.game_engine.spatial2.model.MutableCoordI2;
/**
* Helper to position, draw and manage virtual windows in the UI.
*/
public class Window implements HasPositionAndDimension2, AutoCloseable {
protected final static Logger LOGGER = LoggerFactory.getLogger(Window.class);
protected final static List<Window> WINDOWS = Collections.synchronizedList(new ArrayList<>());
protected CoordD2 relativeScreenPosition = MutableCoordD2.create();
protected final String name;
private final RenderingBoPool renderingBoPool;
private final RendererControl rendererControl;
private Area2 area;
private CoordI2 dragCoordPrev;
private boolean dragging;
private boolean draggable;
private boolean drawCloseButton;
private short renderingBoLayer;
private CoordI2 lastEffectiveViewportDimension;
private RgbaColor color;
private long lastFrameDisplayed;
public Window(final String name, final boolean draggable, final RenderingBoPool renderingBoPool, final RendererControl rendererControl) {
this.name = name;
this.draggable = draggable;
this.renderingBoPool = renderingBoPool;
this.rendererControl = rendererControl;
WINDOWS.add(this);
this.reset();
}
public static boolean intersectsAny(final HasPositionAndDimension2 area) {
// final long frame = EngineFacade.instance().getFrame();
// synchronized (WINDOWS) {
// for (final Window window : WINDOWS) {
// if (window.lastFrameDisplayed >= frame - 1 && WorldUtils.intersects(window, area)) {
// return true;
// }
// }
// }
// return false;
synchronized (WINDOWS) {
for (final Window window : WINDOWS) {
if (SpatialUtils2.intersects(window, area)) {
return true;
}
}
return false;
}
}
public void reset() {
this.area = new Area2();
this.area.setPosition(MutableCoordI2.create());
this.area.setDimension(MutableCoordI2.create());
this.releaseDragging();
this.updateRelativeScreenPosition();
}
public void releaseDragging() {
this.dragCoordPrev = null;
this.dragging = false;
}
public boolean drag(final CoordI2 coord) {
if (!this.draggable) {
return false;
}
try {
if (this.dragCoordPrev != null) {
if (!this.dragging && this.intersects(coord)) {
synchronized (WINDOWS) {
for (final Window window : WINDOWS) {
if (window.isDragging()) {
return false;
}
}
}
this.dragging = true;
}
if (this.dragging) {
this.move(MutableCoordI2.create(coord).substract(this.dragCoordPrev));
return true;
}
}
}
finally {
this.dragCoordPrev = coord;
}
return this.intersects(coord);
}
public void move(final CoordI2 delta) {
this.area.getPosition().add(delta);
this.ensureBounds();
this.updateRelativeScreenPosition();
}
public void updateArea() {
this.applyRelativeScreenPosition();
this.ensureBounds();
}
public void resizeCentered(final CoordI2 dimension) {
if (ImmutableCoordI2.zero().equals(this.area.getDimension())) {
this.area.getDimension().setXY(dimension);
this.updateRelativeScreenPosition();
return;
}
final CoordI2 delta = MutableCoordI2.create(this.area.getDimension()).substract(dimension);
if (ImmutableCoordI2.zero().equals(delta)) {
return;
}
this.area.getDimension().setXY(dimension);
this.move(MutableCoordI2.create(delta.getX() / 2, delta.getY() / 2));
this.updateRelativeScreenPosition();
}
public boolean intersects(final CoordI2 coord) {
return SpatialUtils2.intersects(this.area.getPosition(), this.area.getDimension(), coord, ImmutableCoordI2.one());
}
public void render(final List<RenderingBo> renderingBo, final BiConsumer<List<RenderingBo>, RenderingBo> consumer) {
final var currentEffectiveViewportDimension = this.rendererControl.getEffectiveViewportDimension();
if (!currentEffectiveViewportDimension.equals(this.lastEffectiveViewportDimension)) {
this.lastEffectiveViewportDimension = currentEffectiveViewportDimension;
this.updateArea();
}
var bo = this.renderingBoPool.acquire(this.name + "_Window", DrawWindowRenderingBo.class)
.setThickBorder(this.draggable)
.withDimensionAbsolute(this.area.getDimension())
.withPositionAbsoluteAnchorTopLeft(this.area.getPosition())
.setColor(this.color != null ? this.color : null);
if (this.renderingBoLayer > 0) {
bo.setLayer(this.renderingBoLayer);
}
consumer.accept(renderingBo, bo);
if (this.drawCloseButton) {
bo = this.renderingBoPool.acquire(this.name + "_Close", ControlRenderingBo.class)
.setType(WindowCloseButtonControl.class)
.setCustomData(this.draggable)
.setEnabled(true)
.setVisible(true)
.withDimensionAbsolute(this.area.getDimension())
.withPositionAbsoluteAnchorTopLeft(this.area.getPosition())
.setColor(this.color != null ? this.color : null);
if (this.renderingBoLayer > 0) {
bo.setLayer((short) Math.min(this.renderingBoLayer + 20, RenderingBoLayer.TOP));
}
else {
bo.setLayer(RenderingBoLayer.UI1);
}
consumer.accept(renderingBo, bo);
}
this.lastFrameDisplayed = this.rendererControl.getFrame();
}
@Override
public void close() {
this.releaseDragging();
WINDOWS.remove(this);
}
protected void ensureBounds() {
final var effectiveDim = this.rendererControl.getEffectiveViewportDimension();
var updated = false;
if (this.area.getPosition().getX() < 0) {
this.area.getPosition().setX(0);
updated = true;
}
if (this.area.getPosition().getY() < 0) {
this.area.getPosition().setY(0);
updated = true;
}
if (this.area.getDimension().getX() > effectiveDim.getX()) {
this.area.getDimension().setX(effectiveDim.getX());
updated = true;
}
if (this.area.getDimension().getY() > effectiveDim.getY()) {
this.area.getDimension().setY(effectiveDim.getY());
updated = true;
}
if (this.area.getPosition().getX() + this.area.getDimension().getX() > effectiveDim.getX()) {
this.area.getPosition().setX(effectiveDim.getX() - this.area.getDimension().getX());
updated = true;
}
if (this.area.getPosition().getY() + this.area.getDimension().getY() > effectiveDim.getY()) {
this.area.getPosition().setY(effectiveDim.getY() - this.area.getDimension().getY());
updated = true;
}
if (updated) {
this.updateRelativeScreenPosition();
}
}
protected void updateRelativeScreenPosition() {
final var currentEffectiveViewportDimension = this.rendererControl.getEffectiveViewportDimension();
this.relativeScreenPosition.setX((double) this.area.getPosition().getX() / (double) currentEffectiveViewportDimension.getX());
this.relativeScreenPosition.setY((double) this.area.getPosition().getY() / (double) currentEffectiveViewportDimension.getY());
}
protected void applyRelativeScreenPosition() {
final var currentEffectiveViewportDimension = this.rendererControl.getEffectiveViewportDimension();
this.area.getPosition().setX((int) (this.relativeScreenPosition.getX() * (double) currentEffectiveViewportDimension.getX()));
this.area.getPosition().setY((int) (this.relativeScreenPosition.getY() * (double) currentEffectiveViewportDimension.getY()));
this.ensureBounds();
}
public int getX() {
return this.area.getPosition().getX();
}
public void setX(final int x) {
this.area.getPosition().setX(x);
this.ensureBounds();
this.updateRelativeScreenPosition();
}
public int getY() {
return this.area.getPosition().getY();
}
public void setY(final int y) {
this.area.getPosition().setY(y);
this.ensureBounds();
this.updateRelativeScreenPosition();
}
public int getWidth() {
return this.area.getDimension().getX();
}
public void setWidth(final int width) {
this.area.getDimension().setX(width);
this.ensureBounds();
}
public int getHeight() {
return this.area.getDimension().getY();
}
public void setHeight(final int height) {
this.area.getDimension().setY(height);
this.ensureBounds();
}
@Override
public CoordI2 getPosition() {
return this.area.getPosition();
}
public void setPosition(final CoordI2 position) {
this.area.getPosition().setXY(position);
this.ensureBounds();
this.updateRelativeScreenPosition();
}
@Override
public CoordI2 getDimension() {
return ImmutableCoordI2.create(this.area.getDimension());
}
public void setDimension(final CoordI2 dimension) {
this.area.getDimension().setXY(dimension);
this.ensureBounds();
}
public Area2 getArea() {
return new Area2(ImmutableCoordI2.create(this.area.getPosition()), ImmutableCoordI2.create(this.area.getDimension()));
}
public boolean isDragging() {
return this.dragging;
}
public boolean isDraggable() {
return this.draggable;
}
public void setDraggable(final boolean draggable) {
this.draggable = draggable;
}
public short getRenderingBoLayer() {
return this.renderingBoLayer;
}
public void setRenderingBoLayer(final short renderingBoLayer) {
this.renderingBoLayer = renderingBoLayer;
}
public RgbaColor getColor() {
return this.color;
}
public void setColor(final RgbaColor color) {
this.color = color;
}
public boolean isDrawCloseButton() {
return this.drawCloseButton;
}
public void setDrawCloseButton(final boolean drawCloseButton) {
this.drawCloseButton = drawCloseButton;
}
}

View File

@@ -1,25 +1,62 @@
package de.extio.game_engine.renderer.g2d; package de.extio.game_engine.renderer.g2d;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean; 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.SpacecraftThemeFactoryBean;
import de.extio.game_engine.renderer.g2d.theme.Theme;
import de.extio.game_engine.renderer.g2d.theme.VintageThemeFactoryBean;
import de.extio.game_engine.resource.StaticResourceService;
@AutoConfiguration @AutoConfiguration
@ConditionalOnProperty(name = "game-engine.renderer.enabled", havingValue = "true", matchIfMissing = true) @ConditionalOnProperty(name = "game-engine.renderer.enabled", havingValue = "true", matchIfMissing = true)
@ConditionalOnProperty(name = "game-engine.renderer.strategy", havingValue = "g2d", matchIfMissing = true) @ConditionalOnProperty(name = "game-engine.renderer.strategy", havingValue = "g2d", matchIfMissing = true)
public class G2DAutoConfiguration { public class G2DAutoConfiguration {
@Bean @Bean
public G2DRenderer g2dRenderer(@Value("${game-engine.renderer.title:}") final String title) { G2DRenderer g2dRenderer(
@Value("${game-engine.renderer.title:}") final String title,
final G2DThemeManager themeManager,
final StaticResourceService staticResourceService) {
final var renderer = new G2DRenderer(); final var renderer = new G2DRenderer();
renderer.setTitle(title); renderer.setTitle(title);
renderer.setThemeManager(themeManager);
renderer.setStaticResourceService(staticResourceService);
themeManager.setG2dRenderer(renderer);
return renderer; return renderer;
} }
@Bean @Bean
public G2DRendererControl g2dRendererControl(final G2DRenderer renderer) { G2DRendererControl g2dRendererControl(final G2DRenderer renderer) {
return new G2DRendererControl(renderer); return new G2DRendererControl(renderer);
} }
@Bean
FactoryBean<Theme> spacecraftTheme() {
return new SpacecraftThemeFactoryBean();
}
@Bean
FactoryBean<Theme> vintageTheme() {
return new VintageThemeFactoryBean();
}
@Bean
G2DThemeManager g2dThemeManager(
final StaticResourceService staticResourceService,
final List<PatternRenderer> patternRendererList,
final Map<String, Theme> themes,
@Qualifier("spacecraftTheme") final Theme defaultTheme) {
return new G2DThemeManager(staticResourceService, patternRendererList, themes, defaultTheme);
}
} }

View File

@@ -30,12 +30,14 @@ import de.extio.game_engine.renderer.g2d.bo.rendering.G2DDrawFont;
import de.extio.game_engine.renderer.g2d.bo.rendering.G2DDrawFpsHistory; import de.extio.game_engine.renderer.g2d.bo.rendering.G2DDrawFpsHistory;
import de.extio.game_engine.renderer.g2d.control.G2DDrawControl; import de.extio.game_engine.renderer.g2d.control.G2DDrawControl;
import de.extio.game_engine.renderer.g2d.control.G2DDrawControlTooltip; import de.extio.game_engine.renderer.g2d.control.G2DDrawControlTooltip;
import de.extio.game_engine.renderer.g2d.theme.G2DThemeManager;
import de.extio.game_engine.renderer.model.RenderingBo; import de.extio.game_engine.renderer.model.RenderingBo;
import de.extio.game_engine.renderer.model.RenderingBoLayer; import de.extio.game_engine.renderer.model.RenderingBoLayer;
import de.extio.game_engine.renderer.model.bo.DrawFontRenderingBo; import de.extio.game_engine.renderer.model.bo.DrawFontRenderingBo;
import de.extio.game_engine.renderer.model.color.RgbaColor; import de.extio.game_engine.renderer.model.color.RgbaColor;
import de.extio.game_engine.renderer.model.event.TakeScreenshotEvent; import de.extio.game_engine.renderer.model.event.TakeScreenshotEvent;
import de.extio.game_engine.resource.StaticResource; import de.extio.game_engine.resource.StaticResource;
import de.extio.game_engine.resource.StaticResourceService;
import de.extio.game_engine.util.RingBuffer; import de.extio.game_engine.util.RingBuffer;
public class G2DRenderer implements Renderer { public class G2DRenderer implements Renderer {
@@ -78,6 +80,10 @@ public class G2DRenderer implements Renderer {
private StaticResource previousDefaultFont = null; private StaticResource previousDefaultFont = null;
private G2DThemeManager themeManager;
private StaticResourceService staticResourceService;
public G2DRenderer() { public G2DRenderer() {
LOGGER.info("ctor"); LOGGER.info("ctor");
@@ -367,6 +373,22 @@ public class G2DRenderer implements Renderer {
return this.title; return this.title;
} }
public G2DThemeManager getThemeManager() {
return this.themeManager;
}
public void setThemeManager(final G2DThemeManager themeManager) {
this.themeManager = themeManager;
}
public StaticResourceService getStaticResourceService() {
return this.staticResourceService;
}
public void setStaticResourceService(final StaticResourceService staticResourceService) {
this.staticResourceService = staticResourceService;
}
@Override @Override
public void setRendererData(final RendererData rendererData) { public void setRendererData(final RendererData rendererData) {
this.rendererData = rendererData; this.rendererData = rendererData;

View File

@@ -11,7 +11,7 @@ import org.springframework.stereotype.Component;
import de.extio.game_engine.renderer.g2d.G2DRenderer; import de.extio.game_engine.renderer.g2d.G2DRenderer;
import de.extio.game_engine.renderer.g2d.G2DRendererCondition; import de.extio.game_engine.renderer.g2d.G2DRendererCondition;
import de.extio.game_engine.renderer.g2d.control.components.ComponentRenderingSupport; import de.extio.game_engine.renderer.g2d.theme.PatternRenderer;
import de.extio.game_engine.renderer.model.RenderingBo; import de.extio.game_engine.renderer.model.RenderingBo;
import de.extio.game_engine.renderer.model.RenderingBoLayer; import de.extio.game_engine.renderer.model.RenderingBoLayer;
import de.extio.game_engine.renderer.model.bo.DrawEffectRenderingBo; import de.extio.game_engine.renderer.model.bo.DrawEffectRenderingBo;
@@ -216,12 +216,20 @@ public class G2DDrawEffect extends G2DAbstractRenderingBo implements DrawEffectR
} }
final var strength = this.customInt0 <= 0 ? 2 : this.customInt0; final var strength = this.customInt0 <= 0 ? 2 : this.customInt0;
final var effectColor = this.color == null ? Color.WHITE : this.color.toAwtColor();
// Use PatternRenderer from ThemeManager
final var renderer = this.rendererData.getRenderer();
final G2DRenderer g2dRenderer = (G2DRenderer) renderer;
final var themeManager = g2dRenderer.getThemeManager();
final var theme = themeManager.getCurrentTheme();
final PatternRenderer patternRenderer = themeManager.getPatternRenderer(theme.getPatternRendererName());
if (patternRenderer != null) {
if (this.effect == DrawEffectRenderingBoEffects.DECORATIVE_BORDER) { if (this.effect == DrawEffectRenderingBoEffects.DECORATIVE_BORDER) {
ComponentRenderingSupport.drawDecorativeBorder(graphics, area.getPosition().getX(), area.getPosition().getY(), area.getDimension().getX(), area.getDimension().getY(), strength, this.color == null ? Color.WHITE : this.color.toAwtColor()); patternRenderer.drawDecorativeBorder(graphics, area.getPosition().getX(), area.getPosition().getY(), area.getDimension().getX(), area.getDimension().getY(), strength, effectColor);
} else {
patternRenderer.drawDecorativeBorderFilled(graphics, area.getPosition().getX(), area.getPosition().getY(), area.getDimension().getX(), area.getDimension().getY(), strength, effectColor, effectColor);
} }
else {
ComponentRenderingSupport.drawDecorativeBorderFilled(graphics, area.getPosition().getX(), area.getPosition().getY(), area.getDimension().getX(), area.getDimension().getY(), strength, this.color == null ? Color.WHITE : this.color.toAwtColor());
} }
break; break;

View File

@@ -1,82 +0,0 @@
package de.extio.game_engine.renderer.g2d.bo.rendering;
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.control.components.ComponentRenderingSupport;
import de.extio.game_engine.renderer.model.RenderingBo;
import de.extio.game_engine.renderer.model.RenderingBoLayer;
import de.extio.game_engine.renderer.model.bo.DrawWindowRenderingBo;
import de.extio.game_engine.renderer.model.color.ImmutableRgbaColor;
import de.extio.game_engine.renderer.model.color.RgbaColor;
@Conditional(G2DRendererCondition.class)
@Component
public class G2DDrawWindow extends G2DAbstractRenderingBo implements DrawWindowRenderingBo {
private static final RgbaColor COLOR_WINDOW = new ImmutableRgbaColor(ComponentRenderingSupport.COLOR_COMPONENT_BORDER1.getRed(), ComponentRenderingSupport.COLOR_COMPONENT_BORDER1.getGreen(), ComponentRenderingSupport.COLOR_COMPONENT_BORDER1.getBlue());
private boolean thickBorder;
public G2DDrawWindow() {
super(RenderingBoLayer.UI_BGR);
this.color = COLOR_WINDOW;
}
@Override
public DrawWindowRenderingBo setThickBorder(final boolean thickBorder) {
this.thickBorder = thickBorder;
return this;
}
@Override
public void render(final Graphics2D graphics, final double scaleFactor, final boolean force) {
final var sx = (int) (this.x * scaleFactor);
final var sy = (int) (this.y * scaleFactor);
final var sw = (int) (this.width * scaleFactor);
final var sh = (int) (this.height * scaleFactor);
final var strength = Math.max(2, (int) (3 * scaleFactor));
if (this.color == null) {
this.color = COLOR_WINDOW;
}
final var cMain = this.color.toAwtColor();
final var rgbaDarker = this.color.toMutable();
rgbaDarker.setR(rgbaDarker.getR() / 2);
rgbaDarker.setG(rgbaDarker.getG() / 2);
rgbaDarker.setB(rgbaDarker.getB() / 2);
final var cDarker = rgbaDarker.toAwtColor();
if (this.thickBorder) {
final var borderStrength = strength * 2;
graphics.setColor(cMain);
graphics.fillRect(sx + strength * 2, sy + strength * 2, sw - strength * 4, borderStrength);
graphics.setColor(cDarker);
graphics.fillRect(sx + strength * 2, sy + strength * 2 + borderStrength, sw - strength * 4, strength);
}
ComponentRenderingSupport.drawDecorativeBorder(graphics, sx, sy, sw, sh, strength, cDarker);
ComponentRenderingSupport.drawDecorativeBorderFilled(graphics, sx + strength, sy + strength, sw - strength * 2, sh - strength * 2, strength, cMain);
}
@Override
public void apply(final RenderingBo other) {
super.apply(other);
if (other instanceof final G2DDrawWindow o) {
this.thickBorder = o.thickBorder;
}
}
@Override
public void close() throws Exception {
super.close();
this.color = COLOR_WINDOW;
this.thickBorder = false;
}
}

View File

@@ -1,194 +0,0 @@
package de.extio.game_engine.renderer.g2d.bo.rendering;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import java.util.HashMap;
import java.util.Map;
import org.springframework.context.annotation.Conditional;
import org.springframework.stereotype.Component;
import de.extio.game_engine.renderer.g2d.G2DRenderer;
import de.extio.game_engine.renderer.g2d.G2DRendererCondition;
import de.extio.game_engine.renderer.model.RenderingBo;
import de.extio.game_engine.renderer.model.RenderingBoLayer;
import de.extio.game_engine.renderer.model.bo.DrawWindowRenderingBo;
import de.extio.game_engine.renderer.model.color.ImmutableRgbaColor;
import de.extio.game_engine.renderer.model.color.RgbaColor;
// TODO: Unused for now, merge window implementations when implementing theme management
@Conditional(G2DRendererCondition.class)
// @Component
public class G2DDrawWindow2 extends G2DAbstractRenderingBo implements DrawWindowRenderingBo {
private static final Map<Long, G2DWindowCacheEntry> CACHE = new HashMap<>();
private static final RgbaColor COLOR_WINDOW_BGR = new ImmutableRgbaColor(119, 119, 189);
private static double LAST_SCALEFACTOR;
private static AlphaComposite alphaComposite;
private boolean thickBorder;
private Color awtColor;
public G2DDrawWindow2() {
super(RenderingBoLayer.UI_BGR);
this.color = COLOR_WINDOW_BGR;
}
@Override
public DrawWindowRenderingBo setThickBorder(final boolean thickBorder) {
this.thickBorder = thickBorder;
return this;
}
@Override
public void render(final Graphics2D graphics, final double scaleFactor, final boolean force) {
this.awtColor = this.color == null ? COLOR_WINDOW_BGR.toAwtColor() : this.color.toAwtColor();
if (scaleFactor != LAST_SCALEFACTOR) {
CACHE.clear();
LAST_SCALEFACTOR = scaleFactor;
}
final var key = Long.valueOf((long) this.width | ((long) this.height << 12) | ((this.thickBorder ? 1L : 0L) << 24) | ((long) this.awtColor.getRGB() << 32));
var entry = CACHE.get(key);
if (entry == null) {
final var img = ((G2DRenderer) this.rendererData.getRenderer()).getMainFrame().getGraphicsConfiguration().createCompatibleImage((int) (this.width * scaleFactor), (int) (this.height * scaleFactor), Transparency.TRANSLUCENT);
this.paintWindow(img, scaleFactor);
entry = new G2DWindowCacheEntry();
entry.setImage(img);
CACHE.put(key, entry);
}
entry.setUsed(true);
final var currentAlphaComposite = graphics.getComposite();
try {
if (alphaComposite == null) {
alphaComposite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.6F);
}
graphics.setComposite(alphaComposite);
graphics.drawImage(entry.getImage(), (int) (this.x * scaleFactor), (int) (this.y * scaleFactor), null);
}
finally {
graphics.setComposite(currentAlphaComposite);
}
}
@Override
public void apply(final RenderingBo other) {
super.apply(other);
if (other instanceof final G2DDrawWindow2 o) {
this.color = o.color;
this.thickBorder = o.thickBorder;
this.awtColor = o.awtColor;
}
}
@Override
public void close() throws Exception {
super.close();
this.color = COLOR_WINDOW_BGR;
this.thickBorder = false;
this.awtColor = null;
}
@Override
public void staticCleanupAfterFrame() {
final var it = CACHE.values().iterator();
while (it.hasNext()) {
final var entry = it.next();
if (!entry.isUsed()) {
entry.close();
it.remove();
}
else {
entry.setUsed(false);
}
}
}
private void paintWindow(final BufferedImage img, final double scaleFactor) {
final var imgGraphics = img.createGraphics();
try {
final var swidth = (int) (this.width * scaleFactor);
final var sheight = (int) (this.height * scaleFactor);
final var baseColor = new float[3];
this.awtColor.getRGBColorComponents(baseColor);
Color.RGBtoHSB((int) (baseColor[0] * 255), (int) (baseColor[1] * 255), (int) (baseColor[2] * 255), baseColor);
final var size = (int) (5 * scaleFactor);
imgGraphics.setColor(Color.BLACK);
imgGraphics.fillRect(0, size, swidth, sheight - size * 3);
imgGraphics.fillRect(size, sheight - size * 2, swidth - size * 2, size);
imgGraphics.setColor(Color.getHSBColor(baseColor[0], baseColor[1], baseColor[2]));
if (this.thickBorder) {
imgGraphics.fillRect(0, size * 2, size, sheight - size * 4);
imgGraphics.fillRect(swidth - size, size * 2, swidth, sheight - size * 4);
imgGraphics.fillRect(0, size * 2, swidth, size);
}
else {
imgGraphics.fillRect(0, 0, size, sheight - size * 2);
imgGraphics.fillRect(swidth - size, 0, swidth, sheight - size * 2);
imgGraphics.fillRect(0, 0, swidth, size);
}
imgGraphics.fillRect(size * 2, sheight - size, swidth - size * 4, size);
imgGraphics.fillPolygon(new int[] { 0, size * 2, size * 2, size }, new int[] { sheight - size * 2, sheight, sheight - size, sheight - size * 2 }, 4);
imgGraphics.fillPolygon(new int[] { swidth, swidth - size * 2, swidth - size * 2, swidth - size }, new int[] { sheight - size * 2, sheight, sheight - size, sheight - size * 2 }, 4);
if (this.thickBorder) {
for (var i = 0; i < size * 2; i++) {
imgGraphics.setColor(Color.getHSBColor(baseColor[0], baseColor[1], baseColor[2] * ((0.5F / (size * 2) * i) + 0.5F)));
imgGraphics.drawLine(0, i, swidth, i);
}
}
}
finally {
imgGraphics.dispose();
}
}
private static class G2DWindowCacheEntry implements AutoCloseable {
private BufferedImage image;
private boolean used;
public BufferedImage getImage() {
return this.image;
}
public void setImage(final BufferedImage image) {
this.image = image;
}
public boolean isUsed() {
return this.used;
}
public void setUsed(final boolean used) {
this.used = used;
}
@Override
public void close() {
if (this.image != null) {
this.image.flush();
}
}
}
}

View File

@@ -1,61 +0,0 @@
package de.extio.game_engine.renderer.g2d.control.components;
import java.awt.Color;
import java.awt.Graphics2D;
public class ComponentRenderingSupport {
public final static record HSB(float h, float s, float b) {
}
public final static HSB HSB_COMPONENT_BORDER0 = new HSB(0.71f, 0.12f, 0.50f);
public final static Color COLOR_COMPONENT_BORDER0 = Color.getHSBColor(HSB_COMPONENT_BORDER0.h(), HSB_COMPONENT_BORDER0.s(), HSB_COMPONENT_BORDER0.b());
public final static HSB HSB_COMPONENT_BORDER1 = new HSB(0.71f, 0.12f, 0.80f);
public final static Color COLOR_COMPONENT_BORDER1 = Color.getHSBColor(HSB_COMPONENT_BORDER1.h(), HSB_COMPONENT_BORDER1.s(), HSB_COMPONENT_BORDER1.b());
public final static HSB HSB_COMPONENT_BORDER1_DIS = new HSB(0.71f, 0.15f, 0.20f);
public final static Color COLOR_COMPONENT_BORDER1_DIS = Color.getHSBColor(HSB_COMPONENT_BORDER1_DIS.h(), HSB_COMPONENT_BORDER1_DIS.s(), HSB_COMPONENT_BORDER1_DIS.b());
public final static HSB HSB_COMPONENT_BGR = new HSB(0.71f, 0.92f, 0.30f);
public final static Color COLOR_COMPONENT_BGR = Color.getHSBColor(HSB_COMPONENT_BGR.h(), HSB_COMPONENT_BGR.s(), HSB_COMPONENT_BGR.b());
public final static HSB HSB_COMPONENT_SELECTED_0 = new HSB(0.199f, 0.83f, 0.50f);
public final static Color COLOR_COMPONENT_SELECTED_0 = Color.getHSBColor(HSB_COMPONENT_SELECTED_0.h(), HSB_COMPONENT_SELECTED_0.s(), HSB_COMPONENT_SELECTED_0.b());
public final static HSB HSB_COMPONENT_SELECTED_1 = new HSB(0.199f, 0.83f, 0.90f);
public final static Color COLOR_COMPONENT_SELECTED_1 = Color.getHSBColor(HSB_COMPONENT_SELECTED_1.h(), HSB_COMPONENT_SELECTED_1.s(), HSB_COMPONENT_SELECTED_1.b());
public final static Color COLOR_WINDOW_BGR = new Color(0, 0, 0, 180);
public static void drawDecorativeBorder(final Graphics2D g2d, final int x, final int y, final int w, final int h, final int strength, final Color color) {
final var strength2 = strength * 2;
final var strength4 = strength * 4;
g2d.setColor(color);
g2d.fillRect(x, y + strength2, strength, h - strength4);
g2d.fillRect(x + w - strength, y + strength2, strength, h - strength4);
g2d.fillRect(x + strength2, y, w - strength4, strength);
g2d.fillRect(x + strength2, y + h - strength, w - strength4, strength);
g2d.fillRect(x + strength, y + strength, strength2, strength2);
g2d.fillRect(x + w - strength - strength2, y + strength, strength2, strength2);
g2d.fillRect(x + strength, y + h - strength - strength2, strength2, strength2);
g2d.fillRect(x + w - strength - strength2, y + h - strength - strength2, strength2, strength2);
}
public static void drawDecorativeBorderFilled(final Graphics2D g2d, final int x, final int y, final int w, final int h, final int strength, final Color color) {
g2d.setColor(ComponentRenderingSupport.COLOR_WINDOW_BGR);
g2d.fillRect(x + strength * 2, y + strength * 2, w - strength * 4, h - strength * 4);
drawDecorativeBorder(g2d, x, y, w, h, strength, color);
}
}

View File

@@ -6,17 +6,27 @@ import java.awt.Graphics2D;
import java.awt.event.ActionListener; import java.awt.event.ActionListener;
import de.extio.game_engine.renderer.g2d.bo.rendering.G2DDrawFont; import de.extio.game_engine.renderer.g2d.bo.rendering.G2DDrawFont;
import de.extio.game_engine.renderer.g2d.theme.Theme;
import de.extio.game_engine.renderer.g2d.theme.G2DThemeManager;
@SuppressWarnings("serial") @SuppressWarnings("serial")
public class CustomButton extends CustomAbstractButton { public class CustomButton extends CustomAbstractButton {
public CustomButton(final boolean toggle, final ActionListener listener) { private final G2DThemeManager themeManager;
public CustomButton(final boolean toggle, final ActionListener listener, final G2DThemeManager themeManager) {
super(toggle, listener); super(toggle, listener);
this.themeManager = themeManager;
} }
@Override @Override
public void paint(final Graphics g) { public void paint(final Graphics g) {
if (this.themeManager == null) {
return;
}
final var g2d = (Graphics2D) g; final var g2d = (Graphics2D) g;
final Theme theme = this.themeManager.getCurrentTheme();
float h, s, b; float h, s, b;
@@ -24,24 +34,21 @@ public class CustomButton extends CustomAbstractButton {
final var bgrStrength = (this.getWidth() < 48 || this.getHeight() < 48) ? 0 : 2; final var bgrStrength = (this.getWidth() < 48 || this.getHeight() < 48) ? 0 : 2;
if (this.backgroundColor == null) { if (this.backgroundColor == null) {
if ((this.state & STATE_TOGGLED) == 0) { // Use theme colors
h = ComponentRenderingSupport.HSB_COMPONENT_BGR.h(); final var baseColor = (this.state & STATE_TOGGLED) == 0 ? theme.getBackgroundNormal() : theme.getBackgroundSelected();
s = ComponentRenderingSupport.HSB_COMPONENT_BGR.s(); h = baseColor.getHue();
b = ComponentRenderingSupport.HSB_COMPONENT_BGR.b(); s = baseColor.getSaturation();
} b = baseColor.getBrightness();
else {
h = ComponentRenderingSupport.HSB_COMPONENT_SELECTED_0.b();
s = ComponentRenderingSupport.HSB_COMPONENT_SELECTED_0.s();
b = ComponentRenderingSupport.HSB_COMPONENT_SELECTED_0.b();
}
if ((this.state & STATE_PRESSED) != 0) { if ((this.state & STATE_PRESSED) != 0) {
b += 0.40F; b += theme.getPressedBrightnessAdjustment();
} }
else if ((this.state & STATE_HOVERED) != 0) { else if ((this.state & STATE_HOVERED) != 0) {
b += 0.25F; b += theme.getHoverBrightnessAdjustment();
} }
} }
else { else {
// Use custom background color
final var hsb = new float[3]; final var hsb = new float[3];
Color.RGBtoHSB(this.backgroundColor.getRed(), this.backgroundColor.getGreen(), this.backgroundColor.getBlue(), hsb); Color.RGBtoHSB(this.backgroundColor.getRed(), this.backgroundColor.getGreen(), this.backgroundColor.getBlue(), hsb);
h = hsb[0]; h = hsb[0];
@@ -54,12 +61,14 @@ public class CustomButton extends CustomAbstractButton {
b = 0.50F; b = 0.50F;
} }
if ((this.state & STATE_PRESSED) != 0) { if ((this.state & STATE_PRESSED) != 0) {
b += 0.40F; b += theme.getPressedBrightnessAdjustment();
} }
else if ((this.state & STATE_HOVERED) != 0) { else if ((this.state & STATE_HOVERED) != 0) {
b += 0.25F; b += theme.getHoverBrightnessAdjustment();
} }
} }
b = Math.max(0.0f, Math.min(1.0f, b));
g2d.setColor(Color.getHSBColor(h, s, b)); g2d.setColor(Color.getHSBColor(h, s, b));
g2d.fillRect(2 + bgrStrength, 2 + bgrStrength, this.getWidth() - 5 - bgrStrength * 2, this.getHeight() - 5 - bgrStrength * 2); g2d.fillRect(2 + bgrStrength, 2 + bgrStrength, this.getWidth() - 5 - bgrStrength * 2, this.getHeight() - 5 - bgrStrength * 2);
@@ -70,14 +79,15 @@ public class CustomButton extends CustomAbstractButton {
g2d.drawImage(this.icon, 4 + bgrStrength, 4 + bgrStrength, this.getWidth() - 4 - bgrStrength, this.getHeight() - 4 - bgrStrength, 0, 0, this.icon.getWidth(), this.icon.getHeight(), null); g2d.drawImage(this.icon, 4 + bgrStrength, 4 + bgrStrength, this.getWidth() - 4 - bgrStrength, this.getHeight() - 4 - bgrStrength, 0, 0, this.icon.getWidth(), this.icon.getHeight(), null);
} }
else if (this.caption != null) { else if (this.caption != null) {
// Text color based on state
if ((this.state & (STATE_HOVERED | STATE_TOGGLED)) != 0) { if ((this.state & (STATE_HOVERED | STATE_TOGGLED)) != 0) {
g2d.setColor(Color.WHITE); g2d.setColor(Color.WHITE);
} }
else if (this.isEnabled()) { else if (this.isEnabled()) {
g2d.setColor(Color.LIGHT_GRAY); g2d.setColor(theme.getTextNormal().toColor());
} }
else { else {
g2d.setColor(Color.GRAY); g2d.setColor(theme.getTextDisabled().toColor());
} }
final var textDim = G2DDrawFont.getTextDimensions(this.caption, g2d, this.fontSize, this.scaleFactor); final var textDim = G2DDrawFont.getTextDimensions(this.caption, g2d, this.fontSize, this.scaleFactor);
G2DDrawFont.renderText(g2d, G2DDrawFont.renderText(g2d,
@@ -89,10 +99,17 @@ public class CustomButton extends CustomAbstractButton {
this.caption); this.caption);
} }
var color = (this.state & STATE_HOVERED) != 0 ? ComponentRenderingSupport.COLOR_COMPONENT_SELECTED_0 : ComponentRenderingSupport.COLOR_COMPONENT_BORDER0; // Draw borders using theme colors and pattern renderer
ComponentRenderingSupport.drawDecorativeBorder(g2d, 0, 0, this.getWidth() - 1, this.getHeight() - 1, borderStrength, color); var borderColor = (this.state & STATE_HOVERED) != 0 ? theme.getSelectionPrimary().toColor() : theme.getBorderOuter().toColor();
color = this.isEnabled() ? (this.state & STATE_HOVERED) != 0 ? ComponentRenderingSupport.COLOR_COMPONENT_SELECTED_1 : ComponentRenderingSupport.COLOR_COMPONENT_BORDER1 : ComponentRenderingSupport.COLOR_COMPONENT_BORDER1_DIS; final var patternRenderer = this.themeManager.getPatternRenderer(theme.getPatternRendererName());
ComponentRenderingSupport.drawDecorativeBorder(g2d, borderStrength, borderStrength, this.getWidth() - 1 - 2 * borderStrength, this.getHeight() - 1 - 2 * borderStrength, borderStrength, color); if (patternRenderer != null) {
patternRenderer.drawDecorativeBorder(g2d, 0, 0, this.getWidth() - 1, this.getHeight() - 1, borderStrength, borderColor);
borderColor = this.isEnabled() ?
(this.state & STATE_HOVERED) != 0 ? theme.getSelectionSecondary().toColor() : theme.getBorderInner().toColor() :
theme.getBorderInnerDisabled().toColor();
patternRenderer.drawDecorativeBorder(g2d, borderStrength, borderStrength, this.getWidth() - 1 - 2 * borderStrength, this.getHeight() - 1 - 2 * borderStrength, borderStrength, borderColor);
}
} }
} }

View File

@@ -10,7 +10,9 @@ import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener; import java.awt.event.MouseMotionListener;
import de.extio.game_engine.renderer.ThemeManager;
import de.extio.game_engine.renderer.g2d.bo.rendering.G2DDrawFont; import de.extio.game_engine.renderer.g2d.bo.rendering.G2DDrawFont;
import de.extio.game_engine.renderer.g2d.theme.Theme;
import de.extio.game_engine.renderer.model.bo.HorizontalAlignment; import de.extio.game_engine.renderer.model.bo.HorizontalAlignment;
import de.extio.game_engine.spatial2.model.CoordI2; import de.extio.game_engine.spatial2.model.CoordI2;
import de.extio.game_engine.spatial2.model.ImmutableCoordI2; import de.extio.game_engine.spatial2.model.ImmutableCoordI2;
@@ -44,6 +46,8 @@ public class CustomLabel extends Component {
private CoordI2 lastMousePosition; private CoordI2 lastMousePosition;
private final ThemeManager themeManager;
public void setCaption(final String caption) { public void setCaption(final String caption) {
this.caption = caption; this.caption = caption;
} }
@@ -92,7 +96,8 @@ public class CustomLabel extends Component {
return this.lastMousePosition; return this.lastMousePosition;
} }
public CustomLabel(final ActionListener listener) { public CustomLabel(final ActionListener listener, final ThemeManager themeManager) {
this.themeManager = themeManager;
this.setIgnoreRepaint(true); this.setIgnoreRepaint(true);
this.addMouseListener(new MouseAdapter() { this.addMouseListener(new MouseAdapter() {
@@ -146,7 +151,12 @@ public class CustomLabel extends Component {
@Override @Override
public void paint(final Graphics g) { public void paint(final Graphics g) {
if (this.themeManager == null) {
return;
}
final var g2d = (Graphics2D) g; final var g2d = (Graphics2D) g;
final Theme theme = this.themeManager.getCurrentTheme();
if (this.backgroundColor != null) { if (this.backgroundColor != null) {
g2d.setColor(this.backgroundColor); g2d.setColor(this.backgroundColor);
@@ -155,9 +165,9 @@ public class CustomLabel extends Component {
float h, s, b; float h, s, b;
if (this.foregroundColor == null) { if (this.foregroundColor == null) {
h = 0.0F; h = theme.getTextNormal().getHue();
s = 0.0F; s = theme.getTextNormal().getSaturation();
b = 0.80F; b = theme.getTextNormal().getBrightness();
} }
else { else {
final var hsb = Color.RGBtoHSB(this.foregroundColor.getRed(), this.foregroundColor.getGreen(), this.foregroundColor.getBlue(), null); final var hsb = Color.RGBtoHSB(this.foregroundColor.getRed(), this.foregroundColor.getGreen(), this.foregroundColor.getBlue(), null);
@@ -167,15 +177,17 @@ public class CustomLabel extends Component {
} }
if (!this.enabled) { if (!this.enabled) {
b -= 0.30F; h = theme.getTextDisabled().getHue();
s = theme.getTextDisabled().getSaturation();
b = theme.getTextDisabled().getBrightness();
} }
else if ((this.state & STATE_PRESSED) != 0) { else if ((this.state & STATE_PRESSED) != 0) {
h = ComponentRenderingSupport.HSB_COMPONENT_SELECTED_0.b(); h = theme.getSelectionPrimary().getHue();
s = ComponentRenderingSupport.HSB_COMPONENT_SELECTED_0.s(); s = theme.getSelectionPrimary().getSaturation();
b = ComponentRenderingSupport.HSB_COMPONENT_SELECTED_0.b() + 0.4f; b = theme.getSelectionPrimary().getBrightness() + theme.getPressedBrightnessAdjustment();
} }
else if ((this.state & STATE_HOVERED) != 0) { else if ((this.state & STATE_HOVERED) != 0) {
b += 0.15F; b += theme.getHoverBrightnessAdjustment();
} }
h = Math.max(0.0f, Math.min(1.0f, h)); h = Math.max(0.0f, Math.min(1.0f, h));

View File

@@ -10,6 +10,8 @@ import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener; import java.awt.event.MouseMotionListener;
import de.extio.game_engine.renderer.ThemeManager;
import de.extio.game_engine.renderer.g2d.theme.Theme;
import de.extio.game_engine.spatial2.model.CoordI2; import de.extio.game_engine.spatial2.model.CoordI2;
import de.extio.game_engine.spatial2.model.ImmutableCoordI2; import de.extio.game_engine.spatial2.model.ImmutableCoordI2;
@@ -44,6 +46,8 @@ public class CustomSlider extends Component {
private long lastActionEventReleased; private long lastActionEventReleased;
private final ThemeManager themeManager;
public void setScaleFactor(final double scaleFactor) { public void setScaleFactor(final double scaleFactor) {
this.scaleFactor = scaleFactor; this.scaleFactor = scaleFactor;
} }
@@ -92,9 +96,10 @@ public class CustomSlider extends Component {
return this.lastMousePosition; return this.lastMousePosition;
} }
public CustomSlider(final ActionListener listener) { public CustomSlider(final ActionListener listener, final ThemeManager themeManager) {
this.setIgnoreRepaint(true); this.setIgnoreRepaint(true);
this.actionListener = listener; this.actionListener = listener;
this.themeManager = themeManager;
this.addMouseListener(new MouseAdapter() { this.addMouseListener(new MouseAdapter() {
@@ -168,32 +173,39 @@ public class CustomSlider extends Component {
@Override @Override
public void paint(final Graphics g) { public void paint(final Graphics g) {
if (this.themeManager == null) {
return;
}
final var g2d = (Graphics2D) g; final var g2d = (Graphics2D) g;
final Theme theme = this.themeManager.getCurrentTheme();
float h, s, b; float h, s, b;
if (this.color == null) { if (this.color == null) {
h = 0.0F; if (!this.enabled) {
s = 0.0F; h = theme.getTextDisabled().getHue();
b = 0.80F; s = theme.getTextDisabled().getSaturation();
b = theme.getTextDisabled().getBrightness();
}
else {
h = theme.getTextNormal().getHue();
s = theme.getTextNormal().getSaturation();
b = theme.getTextNormal().getBrightness();
}
} }
else { else {
final var hsb = Color.RGBtoHSB(this.color.getRed(), this.color.getGreen(), this.color.getBlue(), null); final var hsb = Color.RGBtoHSB(this.color.getRed(), this.color.getGreen(), this.color.getBlue(), null);
h = hsb[0]; h = hsb[0];
s = hsb[1]; s = hsb[1];
b = hsb[2]; b = hsb[2];
}
if (!this.enabled) { if (!this.enabled) {
b -= 0.30F; b -= 0.30F;
} }
}
// Main line // Main line
if (this.horizontal) { if (this.horizontal) {
this.drawLine(g2d, h, s, b, 0, this.getHeight() / 2, this.getWidth(), this.getHeight() / 2, this.horizontal); this.drawLine(g2d, h, s, b, 0, this.getHeight() / 2, this.getWidth(), this.getHeight() / 2, this.horizontal);
}
else {
this.drawLine(g2d, h, s, b, this.getWidth() / 2, 0, this.getWidth() / 2, this.getHeight(), this.horizontal);
}
// Small lines // Small lines
b -= 0.20F; b -= 0.20F;
final var length = this.horizontal ? this.getWidth() : this.getHeight(); final var length = this.horizontal ? this.getWidth() : this.getHeight();
@@ -242,6 +254,7 @@ public class CustomSlider extends Component {
this.drawLine(g2d, h, s, b, 0, (int) (this.getHeight() * (1.0 - this.value)), this.getWidth(), (int) (this.getHeight() * (1.0 - this.value)), !this.horizontal); this.drawLine(g2d, h, s, b, 0, (int) (this.getHeight() * (1.0 - this.value)), this.getWidth(), (int) (this.getHeight() * (1.0 - this.value)), !this.horizontal);
} }
} }
}
private void applyColor(final Graphics2D g2d, float h, float s, float b) { private void applyColor(final Graphics2D g2d, float h, float s, float b) {
h = Math.max(0.0f, Math.min(1.0f, h)); h = Math.max(0.0f, Math.min(1.0f, h));

View File

@@ -6,77 +6,91 @@ import java.awt.Graphics2D;
import java.awt.event.ActionListener; import java.awt.event.ActionListener;
import de.extio.game_engine.renderer.g2d.bo.rendering.G2DDrawFont; import de.extio.game_engine.renderer.g2d.bo.rendering.G2DDrawFont;
import de.extio.game_engine.renderer.g2d.theme.Theme;
import de.extio.game_engine.renderer.g2d.theme.G2DThemeManager;
@SuppressWarnings("serial") @SuppressWarnings("serial")
public class CustomSwitch extends CustomAbstractButton { public class CustomSwitch extends CustomAbstractButton {
private boolean drawBorder; private boolean drawBorder;
private final G2DThemeManager themeManager;
public CustomSwitch(final boolean toggle, final ActionListener listener) { public CustomSwitch(final boolean toggle, final ActionListener listener, final G2DThemeManager themeManager) {
super(toggle, listener); super(toggle, listener);
this.themeManager = themeManager;
} }
@Override @Override
public void paint(final Graphics g) { public void paint(final Graphics g) {
if (this.themeManager == null) {
return;
}
final var g2d = (Graphics2D) g; final var g2d = (Graphics2D) g;
final Theme theme = this.themeManager.getCurrentTheme();
final var dimY = this.getHeight() - 1; final var dimY = this.getHeight() - 1;
final var dimX = (int) (dimY * 1.5); final var dimX = (int) (dimY * 1.5);
float h, s, b; float h, s, b;
if ((this.state & STATE_TOGGLED) == 0) { final var baseColor = (this.state & STATE_TOGGLED) == 0 ? theme.getBackgroundNormal() : theme.getBackgroundSelected();
h = ComponentRenderingSupport.HSB_COMPONENT_BGR.h(); h = baseColor.getHue();
s = ComponentRenderingSupport.HSB_COMPONENT_BGR.s(); s = baseColor.getSaturation();
b = ComponentRenderingSupport.HSB_COMPONENT_BGR.b(); b = baseColor.getBrightness();
}
else {
h = ComponentRenderingSupport.HSB_COMPONENT_SELECTED_0.b();
s = ComponentRenderingSupport.HSB_COMPONENT_SELECTED_0.s();
b = ComponentRenderingSupport.HSB_COMPONENT_SELECTED_0.b();
}
if ((this.state & STATE_PRESSED) != 0) { if ((this.state & STATE_PRESSED) != 0) {
b += 0.40F; b += theme.getPressedBrightnessAdjustment();
} }
else if ((this.state & STATE_HOVERED) != 0) { else if ((this.state & STATE_HOVERED) != 0) {
b += 0.25F; b += theme.getHoverBrightnessAdjustment();
} }
b = Math.max(0.0f, Math.min(1.0f, b));
final var bodyColor = Color.getHSBColor(h, s, b); final var bodyColor = Color.getHSBColor(h, s, b);
Color border0Color; Color border0Color;
Color border1Color; Color border1Color;
if (dimX < 45) { if (dimX < 45) {
border0Color = this.isEnabled() ? ComponentRenderingSupport.COLOR_COMPONENT_BORDER1 : ComponentRenderingSupport.COLOR_COMPONENT_BORDER1_DIS; border0Color = this.isEnabled() ? theme.getBorderInner().toColor() : theme.getBorderInnerDisabled().toColor();
border1Color = bodyColor; border1Color = bodyColor;
} }
else { else {
border0Color = ComponentRenderingSupport.COLOR_COMPONENT_BORDER0; border0Color = theme.getBorderOuter().toColor();
border1Color = this.isEnabled() ? ComponentRenderingSupport.COLOR_COMPONENT_BORDER1 : ComponentRenderingSupport.COLOR_COMPONENT_BORDER1_DIS; border1Color = this.isEnabled() ? theme.getBorderInner().toColor() : theme.getBorderInnerDisabled().toColor();
} }
if ((this.state & STATE_TOGGLED) == 0) { if ((this.state & STATE_TOGGLED) == 0) {
g2d.setColor(bodyColor); g2d.setColor(bodyColor);
g2d.fillRect(8, 8, (int) (dimX / 1.5) - 12, this.getHeight() - 15); g2d.fillRect(8, 8, (int) (dimX / 1.5) - 12, this.getHeight() - 15);
ComponentRenderingSupport.drawDecorativeBorder(g2d, 6, 4, (int) (dimX / 1.5) - 6, this.getHeight() - 9, 2, border0Color); final var patternRenderer = this.themeManager.getPatternRenderer(theme.getPatternRendererName());
ComponentRenderingSupport.drawDecorativeBorder(g2d, 8, 6, (int) (dimX / 1.5) - 10, this.getHeight() - 13, 2, border1Color); if (patternRenderer != null) {
patternRenderer.drawDecorativeBorder(g2d, 6, 4, (int) (dimX / 1.5) - 6, this.getHeight() - 9, 2, border0Color);
patternRenderer.drawDecorativeBorder(g2d, 8, 6, (int) (dimX / 1.5) - 10, this.getHeight() - 13, 2, border1Color);
}
} }
else { else {
g2d.setColor(bodyColor); g2d.setColor(bodyColor);
g2d.fillRect(dimX - (int) (dimX / 1.5), 8, (int) (dimX / 1.5) - 12, this.getHeight() - 15); g2d.fillRect(dimX - (int) (dimX / 1.5), 8, (int) (dimX / 1.5) - 12, this.getHeight() - 15);
ComponentRenderingSupport.drawDecorativeBorder(g2d, dimX - (int) (dimX / 1.5) - 3, 4, (int) (dimX / 1.5) - 6, this.getHeight() - 9, 2, border0Color); final var patternRenderer = this.themeManager.getPatternRenderer(theme.getPatternRendererName());
ComponentRenderingSupport.drawDecorativeBorder(g2d, dimX - (int) (dimX / 1.5) - 1, 6, (int) (dimX / 1.5) - 10, this.getHeight() - 13, 2, border1Color); if (patternRenderer != null) {
patternRenderer.drawDecorativeBorder(g2d, dimX - (int) (dimX / 1.5) - 3, 4, (int) (dimX / 1.5) - 6, this.getHeight() - 9, 2, border0Color);
patternRenderer.drawDecorativeBorder(g2d, dimX - (int) (dimX / 1.5) - 1, 6, (int) (dimX / 1.5) - 10, this.getHeight() - 13, 2, border1Color);
}
} }
// Text color based on state
if ((this.state & STATE_HOVERED) != 0) { if ((this.state & STATE_HOVERED) != 0) {
g2d.setColor(Color.WHITE); g2d.setColor(Color.WHITE);
} }
else if (this.isEnabled()) { else if (this.isEnabled()) {
g2d.setColor(Color.LIGHT_GRAY); g2d.setColor(theme.getTextNormal().toColor());
} }
else { else {
g2d.setColor(Color.GRAY); g2d.setColor(theme.getTextDisabled().toColor());
} }
final var textDim = G2DDrawFont.getTextDimensions(this.caption, g2d, this.fontSize, this.scaleFactor); final var textDim = G2DDrawFont.getTextDimensions(this.caption, g2d, this.fontSize, this.scaleFactor);
G2DDrawFont.renderText(g2d, G2DDrawFont.renderText(g2d,
textDim, textDim,
@@ -87,8 +101,16 @@ public class CustomSwitch extends CustomAbstractButton {
this.caption); this.caption);
if (this.drawBorder) { if (this.drawBorder) {
ComponentRenderingSupport.drawDecorativeBorder(g2d, 0, 0, this.getWidth() - 1, this.getHeight() - 1, 2, (this.state & STATE_HOVERED) != 0 ? ComponentRenderingSupport.COLOR_COMPONENT_SELECTED_0 : ComponentRenderingSupport.COLOR_COMPONENT_BORDER0); final var borderColor1 = (this.state & STATE_HOVERED) != 0 ? theme.getSelectionPrimary().toColor() : theme.getBorderOuter().toColor();
ComponentRenderingSupport.drawDecorativeBorder(g2d, 2, 2, this.getWidth() - 5, this.getHeight() - 5, 2, this.isEnabled() ? (this.state & STATE_HOVERED) != 0 ? ComponentRenderingSupport.COLOR_COMPONENT_SELECTED_1 : ComponentRenderingSupport.COLOR_COMPONENT_BORDER1 : ComponentRenderingSupport.COLOR_COMPONENT_BORDER1_DIS); final var patternRenderer = this.themeManager.getPatternRenderer(theme.getPatternRendererName());
if (patternRenderer != null) {
patternRenderer.drawDecorativeBorder(g2d, 0, 0, this.getWidth() - 1, this.getHeight() - 1, 2, borderColor1);
final var borderColor2 = this.isEnabled() ?
(this.state & STATE_HOVERED) != 0 ? theme.getSelectionSecondary().toColor() : theme.getBorderInner().toColor() :
theme.getBorderInnerDisabled().toColor();
patternRenderer.drawDecorativeBorder(g2d, 2, 2, this.getWidth() - 5, this.getHeight() - 5, 2, borderColor2);
}
} }
} }

View File

@@ -1,55 +1,33 @@
package de.extio.game_engine.renderer.g2d.control.components; package de.extio.game_engine.renderer.g2d.control.components;
import java.awt.Color;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.event.ActionListener; import java.awt.event.ActionListener;
import de.extio.game_engine.renderer.g2d.bo.rendering.G2DDrawFont; import de.extio.game_engine.renderer.g2d.theme.G2DThemeManager;
import de.extio.game_engine.renderer.model.color.RgbaColor;
@SuppressWarnings("serial") @SuppressWarnings("serial")
public class CustomWindowCloseButton extends CustomAbstractButton { public class CustomWindowCloseButton extends CustomAbstractButton {
public CustomWindowCloseButton(final boolean toggle, final ActionListener listener) { private final G2DThemeManager themeManager;
public CustomWindowCloseButton(final boolean toggle, final ActionListener listener, final G2DThemeManager themeManager) {
super(toggle, listener); super(toggle, listener);
this.themeManager = themeManager;
} }
@Override @Override
public void paint(final Graphics g) { public void paint(final Graphics g) {
if (this.themeManager == null) {
return;
}
final var g2d = (Graphics2D) g; final var g2d = (Graphics2D) g;
final var theme = this.themeManager.getCurrentTheme();
final var patternRenderer = this.themeManager.getPatternRenderer(theme.getPatternRendererName());
final var highlight = (this.state & (STATE_HOVERED | STATE_TOGGLED)) != 0; if (patternRenderer != null) {
final var pressed = (this.state & STATE_PRESSED) != 0; patternRenderer.drawCloseButton(g2d, this.getWidth(), this.getHeight(), this.isEnabled(), this.state, this.backgroundColor, this.scaleFactor, theme);
Color color = null;
if (pressed) {
color = ComponentRenderingSupport.COLOR_COMPONENT_SELECTED_0;
}
else if (highlight) {
color = ComponentRenderingSupport.COLOR_COMPONENT_BGR;
}
else if (this.backgroundColor == null) {
color = ComponentRenderingSupport.COLOR_COMPONENT_BORDER1;
}
else {
color = this.backgroundColor;
}
g2d.setColor(color);
g2d.fillRect(0, 0, this.getWidth(), this.getHeight());
if (this.isEnabled()) {
if (pressed) {
g2d.setColor(RgbaColor.YELLOW.toAwtColor());
}
else if (highlight) {
g2d.setColor(ComponentRenderingSupport.COLOR_COMPONENT_SELECTED_1);
}
else {
g2d.setColor(Color.BLACK);
}
final var textDim = G2DDrawFont.getTextDimensions("X", g2d, 16, this.scaleFactor);
G2DDrawFont.renderText(g2d, textDim, 1.0, ((this.getWidth() - textDim.getX()) / 2), ((this.getHeight() - textDim.getY()) / 2), (int) (16 * this.scaleFactor), "X");
} }
} }

View File

@@ -11,14 +11,12 @@ import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener; import java.awt.event.MouseWheelListener;
import de.extio.game_engine.renderer.g2d.G2DMainFrame; import de.extio.game_engine.renderer.g2d.G2DMainFrame;
import de.extio.game_engine.renderer.model.color.ImmutableRgbaColor; import de.extio.game_engine.renderer.g2d.theme.Theme;
import de.extio.game_engine.renderer.model.color.RgbaColor; import de.extio.game_engine.renderer.g2d.theme.G2DThemeManager;
import de.extio.game_engine.spatial2.model.ImmutableCoordI2; import de.extio.game_engine.spatial2.model.ImmutableCoordI2;
public class CustomWindowPanel extends Component { public class CustomWindowPanel extends Component {
private static final RgbaColor COLOR_WINDOW = new ImmutableRgbaColor(ComponentRenderingSupport.COLOR_COMPONENT_BORDER1.getRed(), ComponentRenderingSupport.COLOR_COMPONENT_BORDER1.getGreen(), ComponentRenderingSupport.COLOR_COMPONENT_BORDER1.getBlue());
protected boolean thickBorder; protected boolean thickBorder;
protected Color color; protected Color color;
@@ -27,6 +25,8 @@ public class CustomWindowPanel extends Component {
protected boolean dirty = true; protected boolean dirty = true;
private final G2DThemeManager themeManager;
public void setThickBorder(final boolean thickBorder) { public void setThickBorder(final boolean thickBorder) {
this.thickBorder = thickBorder; this.thickBorder = thickBorder;
} }
@@ -47,7 +47,8 @@ public class CustomWindowPanel extends Component {
this.dirty = dirty; this.dirty = dirty;
} }
public CustomWindowPanel() { public CustomWindowPanel(final G2DThemeManager themeManager) {
this.themeManager = themeManager;
this.setIgnoreRepaint(true); this.setIgnoreRepaint(true);
this.addMouseListener(new MouseAdapter() { this.addMouseListener(new MouseAdapter() {
@@ -103,24 +104,26 @@ public class CustomWindowPanel extends Component {
@Override @Override
public void paint(final Graphics g) { public void paint(final Graphics g) {
final var g2d = (Graphics2D) g; if (this.themeManager == null) {
return;
final var strength = Math.max(2, (int) (3 * this.scaleFactor));
final var effectiveColor = this.color != null ? this.color : COLOR_WINDOW.toAwtColor();
final var cMain = effectiveColor;
final var rgbaDarker = new Color(effectiveColor.getRed() / 2, effectiveColor.getGreen() / 2, effectiveColor.getBlue() / 2);
if (this.thickBorder) {
final var borderStrength = strength * 2;
g2d.setColor(cMain);
g2d.fillRect(strength * 2, strength * 2, this.getWidth() - strength * 4, borderStrength);
g2d.setColor(rgbaDarker);
g2d.fillRect(strength * 2, strength * 2 + borderStrength, this.getWidth() - strength * 4, strength);
} }
ComponentRenderingSupport.drawDecorativeBorder(g2d, 0, 0, this.getWidth(), this.getHeight(), strength, rgbaDarker); final var g2d = (Graphics2D) g;
ComponentRenderingSupport.drawDecorativeBorderFilled(g2d, strength, strength, this.getWidth() - strength * 2, this.getHeight() - strength * 2, strength, cMain); final Theme theme = this.themeManager.getCurrentTheme();
final var patternRenderer = this.themeManager.getPatternRenderer(theme.getPatternRendererName());
if (patternRenderer != null) {
patternRenderer.drawWindowPanel(g2d,
0,
0,
this.getWidth(),
this.getHeight(),
this.thickBorder,
theme.getBorderInner().toColor(),
theme.getBorderOuter().toColor(),
this.color != null ? this.color : theme.getWindowBackground().toColor(),
this.scaleFactor);
}
} }
} }

View File

@@ -71,7 +71,7 @@ public class G2DButtonControlImpl extends G2DBaseControlImpl implements ButtonCo
} }
protected void createControl() { protected void createControl() {
this.control = new CustomButton(false, event -> this.performAction()); this.control = new CustomButton(false, event -> this.performAction(), ((G2DRenderer) this.rendererData.getRenderer()).getThemeManager());
this.control.setName(this.id); this.control.setName(this.id);
} }

View File

@@ -98,7 +98,7 @@ public class G2DLabelControlImpl extends G2DBaseControlImpl implements LabelCont
} }
protected void createControl() { protected void createControl() {
this.control = new CustomLabel(event -> this.performAction()); this.control = new CustomLabel(event -> this.performAction(), ((G2DRenderer) this.rendererData.getRenderer()).getThemeManager());
this.control.setName(this.id); this.control.setName(this.id);
} }

View File

@@ -136,7 +136,7 @@ public class G2DSliderControlImpl extends G2DBaseControlImpl implements SliderCo
if (this.enabled) { if (this.enabled) {
this.performAction(); this.performAction();
} }
}); }, ((G2DRenderer) this.rendererData.getRenderer()).getThemeManager());
this.control.setName(this.id); this.control.setName(this.id);
} }

View File

@@ -1,12 +1,13 @@
package de.extio.game_engine.renderer.g2d.control.impl; package de.extio.game_engine.renderer.g2d.control.impl;
import de.extio.game_engine.renderer.g2d.G2DRenderer;
import de.extio.game_engine.renderer.g2d.control.components.CustomSwitch; import de.extio.game_engine.renderer.g2d.control.components.CustomSwitch;
public class G2DSwitchControlImpl extends G2DAbstractToggleButtonControlImpl { public class G2DSwitchControlImpl extends G2DAbstractToggleButtonControlImpl {
@Override @Override
protected void createControl() { protected void createControl() {
this.control = new CustomSwitch(true, event -> this.performAction()); this.control = new CustomSwitch(true, event -> this.performAction(), ((G2DRenderer) this.rendererData.getRenderer()).getThemeManager());
this.control.setName(this.id); this.control.setName(this.id);
} }

View File

@@ -18,8 +18,8 @@ import javax.swing.table.DefaultTableCellRenderer;
import de.extio.game_engine.renderer.RendererData; import de.extio.game_engine.renderer.RendererData;
import de.extio.game_engine.renderer.g2d.G2DRenderer; import de.extio.game_engine.renderer.g2d.G2DRenderer;
import de.extio.game_engine.renderer.g2d.bo.rendering.G2DDrawFont; import de.extio.game_engine.renderer.g2d.bo.rendering.G2DDrawFont;
import de.extio.game_engine.renderer.g2d.control.components.ComponentRenderingSupport;
import de.extio.game_engine.renderer.g2d.control.components.CustomTable; import de.extio.game_engine.renderer.g2d.control.components.CustomTable;
import de.extio.game_engine.renderer.g2d.theme.G2DThemeManager;
import de.extio.game_engine.renderer.model.bo.ControlRenderingBo.BaseControl; import de.extio.game_engine.renderer.model.bo.ControlRenderingBo.BaseControl;
import de.extio.game_engine.renderer.model.bo.ControlRenderingBo.TableControl; import de.extio.game_engine.renderer.model.bo.ControlRenderingBo.TableControl;
import de.extio.game_engine.renderer.model.color.RgbaColor; import de.extio.game_engine.renderer.model.color.RgbaColor;
@@ -167,7 +167,8 @@ public class G2DTableControlImpl extends G2DBaseControlImpl implements TableCont
this.control.setFont(G2DDrawFont.getFont(this.scaleFactor, this.fontSize)); this.control.setFont(G2DDrawFont.getFont(this.scaleFactor, this.fontSize));
this.control.setRowHeight((int) (this.control.getFont().getSize2D() * 1.25)); this.control.setRowHeight((int) (this.control.getFont().getSize2D() * 1.25));
this.control.setBackground(ComponentRenderingSupport.COLOR_COMPONENT_BGR); final G2DThemeManager themeManager = ((G2DRenderer) this.rendererData.getRenderer()).getThemeManager();
this.control.setBackground(themeManager.getCurrentTheme().getBackgroundNormal().toColor());
this.control.setForeground(Color.WHITE); this.control.setForeground(Color.WHITE);
if (this.dataModified || this.scrollPositionModified) { if (this.dataModified || this.scrollPositionModified) {

View File

@@ -6,9 +6,9 @@ import java.awt.TextComponent;
import java.awt.TextField; import java.awt.TextField;
import java.util.Objects; import java.util.Objects;
import de.extio.game_engine.renderer.ThemeManager;
import de.extio.game_engine.renderer.g2d.G2DRenderer; import de.extio.game_engine.renderer.g2d.G2DRenderer;
import de.extio.game_engine.renderer.g2d.bo.rendering.G2DDrawFont; import de.extio.game_engine.renderer.g2d.bo.rendering.G2DDrawFont;
import de.extio.game_engine.renderer.g2d.control.components.ComponentRenderingSupport;
import de.extio.game_engine.renderer.g2d.control.components.CustomTextArea; import de.extio.game_engine.renderer.g2d.control.components.CustomTextArea;
import de.extio.game_engine.renderer.g2d.control.components.CustomTextField; import de.extio.game_engine.renderer.g2d.control.components.CustomTextField;
import de.extio.game_engine.renderer.model.bo.ControlRenderingBo.BaseControl; import de.extio.game_engine.renderer.model.bo.ControlRenderingBo.BaseControl;
@@ -119,7 +119,9 @@ public class G2DTextfieldControlImpl extends G2DBaseControlImpl implements Textf
} }
private void initTextField() { private void initTextField() {
this.safeInvoke(() -> this.textComponent.setBackground(ComponentRenderingSupport.COLOR_COMPONENT_BGR)); final ThemeManager themeManager = ((G2DRenderer) this.rendererData.getRenderer()).getThemeManager();
final Color bgColor = themeManager.getCurrentTheme().getBackgroundNormal().toColor();
this.safeInvoke(() -> this.textComponent.setBackground(bgColor));
this.safeInvoke(() -> this.textComponent.setForeground(Color.WHITE)); this.safeInvoke(() -> this.textComponent.setForeground(Color.WHITE));
this.safeInvoke(() -> this.textComponent.setLocation(this.x, this.y)); this.safeInvoke(() -> this.textComponent.setLocation(this.x, this.y));
this.safeInvoke(() -> this.textComponent.setSize(this.width, this.height)); this.safeInvoke(() -> this.textComponent.setSize(this.width, this.height));

View File

@@ -9,9 +9,9 @@ import javax.swing.JTextField;
import javax.swing.text.JTextComponent; import javax.swing.text.JTextComponent;
import de.extio.game_engine.renderer.RendererData; import de.extio.game_engine.renderer.RendererData;
import de.extio.game_engine.renderer.ThemeManager;
import de.extio.game_engine.renderer.g2d.G2DRenderer; import de.extio.game_engine.renderer.g2d.G2DRenderer;
import de.extio.game_engine.renderer.g2d.bo.rendering.G2DDrawFont; import de.extio.game_engine.renderer.g2d.bo.rendering.G2DDrawFont;
import de.extio.game_engine.renderer.g2d.control.components.ComponentRenderingSupport;
import de.extio.game_engine.renderer.g2d.control.components.CustomJTextArea; import de.extio.game_engine.renderer.g2d.control.components.CustomJTextArea;
import de.extio.game_engine.renderer.g2d.control.components.CustomJTextField; import de.extio.game_engine.renderer.g2d.control.components.CustomJTextField;
import de.extio.game_engine.renderer.model.bo.ControlRenderingBo.BaseControl; import de.extio.game_engine.renderer.model.bo.ControlRenderingBo.BaseControl;
@@ -227,7 +227,14 @@ public class G2DTextfieldControlImpl2 extends G2DBaseControlImpl implements Text
} }
private void initControl() { private void initControl() {
this.safeInvoke(() -> this.textComponent.setBackground(this.backgroundColor != null ? this.backgroundColor.toAwtColor() : ComponentRenderingSupport.COLOR_COMPONENT_BGR)); final ThemeManager themeManager = ((G2DRenderer) this.rendererData.getRenderer()).getThemeManager();
final Color bgColor;
if (this.backgroundColor != null) {
bgColor = this.backgroundColor.toAwtColor();
} else {
bgColor = themeManager.getCurrentTheme().getBackgroundNormal().toColor();
}
this.safeInvoke(() -> this.textComponent.setBackground(bgColor));
this.safeInvoke(() -> this.textComponent.setForeground(Color.WHITE)); this.safeInvoke(() -> this.textComponent.setForeground(Color.WHITE));
this.safeInvoke(() -> this.textComponent.setFont(G2DDrawFont.getFont(this.scaleFactor, this.fontSize))); this.safeInvoke(() -> this.textComponent.setFont(G2DDrawFont.getFont(this.scaleFactor, this.fontSize)));
if (this.multiLine) { if (this.multiLine) {

View File

@@ -1,12 +1,13 @@
package de.extio.game_engine.renderer.g2d.control.impl; package de.extio.game_engine.renderer.g2d.control.impl;
import de.extio.game_engine.renderer.g2d.G2DRenderer;
import de.extio.game_engine.renderer.g2d.control.components.CustomButton; import de.extio.game_engine.renderer.g2d.control.components.CustomButton;
public class G2DToggleButtonControlImpl extends G2DAbstractToggleButtonControlImpl { public class G2DToggleButtonControlImpl extends G2DAbstractToggleButtonControlImpl {
@Override @Override
protected void createControl() { protected void createControl() {
this.control = new CustomButton(true, event -> this.performAction()); this.control = new CustomButton(true, event -> this.performAction(), ((G2DRenderer) this.rendererData.getRenderer()).getThemeManager());
this.control.setName(this.id); this.control.setName(this.id);
} }

View File

@@ -1,5 +1,6 @@
package de.extio.game_engine.renderer.g2d.control.impl; package de.extio.game_engine.renderer.g2d.control.impl;
import de.extio.game_engine.renderer.g2d.G2DRenderer;
import de.extio.game_engine.renderer.g2d.control.components.CustomWindowCloseButton; import de.extio.game_engine.renderer.g2d.control.components.CustomWindowCloseButton;
import de.extio.game_engine.renderer.model.bo.ControlRenderingBo.WindowCloseButtonControl; import de.extio.game_engine.renderer.model.bo.ControlRenderingBo.WindowCloseButtonControl;
@@ -11,7 +12,7 @@ public class G2DWindowCloseButtonControlImpl extends G2DButtonControlImpl implem
@Override @Override
protected void createControl() { protected void createControl() {
this.control = new CustomWindowCloseButton(false, event -> this.performAction()); this.control = new CustomWindowCloseButton(false, event -> this.performAction(), ((G2DRenderer) this.rendererData.getRenderer()).getThemeManager());
this.control.setName(this.id); this.control.setName(this.id);
} }

View File

@@ -77,7 +77,7 @@ public class G2DWindowPanelControlImpl extends G2DBaseControlImpl implements Win
} }
protected void createControl() { protected void createControl() {
this.control = new CustomWindowPanel(); this.control = new CustomWindowPanel(((G2DRenderer) this.rendererData.getRenderer()).getThemeManager());
this.control.setName(this.id); this.control.setName(this.id);
} }

View File

@@ -0,0 +1,37 @@
package de.extio.game_engine.renderer.g2d.theme;
/**
* Represents the visual state of a UI component.
* Used by the theming system to determine which colors to apply.
*/
public enum ComponentState {
/**
* Normal, default state - enabled, not interacted with
*/
NORMAL,
/**
* Mouse cursor is hovering over the component
*/
HOVERED,
/**
* Component is currently being pressed/clicked
*/
PRESSED,
/**
* Component is in toggled/selected state (for toggle buttons, switches)
*/
TOGGLED,
/**
* Component is disabled and cannot be interacted with
*/
DISABLED,
/**
* Component has keyboard focus (for text fields)
*/
FOCUSED
}

View File

@@ -0,0 +1,111 @@
package de.extio.game_engine.renderer.g2d.theme;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import de.extio.game_engine.renderer.ThemeManager;
import de.extio.game_engine.renderer.g2d.G2DRenderer;
import de.extio.game_engine.resource.StaticResourceService;
public class G2DThemeManager implements ThemeManager {
private static final Logger LOGGER = LoggerFactory.getLogger(G2DThemeManager.class);
private G2DRenderer g2dRenderer;
private final StaticResourceService staticResourceService;
private final Map<String, PatternRenderer> patternRenderers;
private final Map<String, Theme> themes;
private final Theme defaultTheme;
private Theme currentTheme;
public G2DThemeManager(
final StaticResourceService staticResourceService,
final List<PatternRenderer> patternRendererList,
final Map<String, Theme> themes,
final Theme defaultTheme) {
this.staticResourceService = staticResourceService;
this.patternRenderers = patternRendererList.stream()
.collect(Collectors.toMap(
renderer -> renderer.getClass().getSimpleName().substring(0, 1).toLowerCase() + renderer.getClass().getSimpleName().substring(1),
Function.identity()));
this.themes = Objects.requireNonNull(themes, "themes cannot be null");
this.defaultTheme = Objects.requireNonNull(defaultTheme, "defaultTheme cannot be null");
LOGGER.info("G2DThemeManager initialized with {} pattern renderers: {}", this.patternRenderers.size(), this.patternRenderers.keySet());
LOGGER.info("G2DThemeManager initialized with {} themes: {}", this.themes.size(), this.themes.keySet());
this.currentTheme = this.defaultTheme;
}
@Override
public List<String> getAvailableThemeNames() {
return this.themes.values().stream()
.map(Theme::getName)
.distinct()
.sorted()
.toList();
}
@Override
public void setCurrentTheme(final String themeName) {
if (themeName == null || themeName.isBlank()) {
throw new IllegalArgumentException("themeName cannot be null/blank");
}
final var direct = this.themes.get(themeName);
if (direct != null) {
this.setCurrentTheme(direct);
return;
}
final var byDisplayName = this.themes.values().stream()
.filter(theme -> theme != null && theme.getName() != null && theme.getName().equalsIgnoreCase(themeName))
.findFirst()
.orElse(null);
if (byDisplayName != null) {
this.setCurrentTheme(byDisplayName);
return;
}
this.setCurrentTheme(this.loadTheme(themeName));
}
@Override
public Theme getCurrentTheme() {
return this.currentTheme;
}
public PatternRenderer getPatternRenderer(final String rendererName) {
return this.patternRenderers.get(rendererName);
}
@Override
public void setCurrentTheme(final Theme theme) {
if (theme == null) {
throw new IllegalArgumentException("Theme cannot be null");
}
this.currentTheme = theme;
LOGGER.info("Theme switched to: {}", theme.getName());
this.g2dRenderer.reset();
}
public Theme loadTheme(final String themeName) {
Objects.requireNonNull(this.staticResourceService, "staticResourceService");
LOGGER.warn("Theme loading from StaticResourceService not yet implemented. Requested theme: {}. Using default theme.", themeName);
return this.defaultTheme;
}
public void setG2dRenderer(final G2DRenderer g2dRenderer) {
this.g2dRenderer = g2dRenderer;
}
}

View File

@@ -0,0 +1,107 @@
package de.extio.game_engine.renderer.g2d.theme;
import java.awt.Color;
import java.util.Objects;
/**
* Represents a color in HSB (Hue, Saturation, Brightness) color space.
* This allows for easier runtime color manipulation (e.g., brightening on hover).
*/
public final class HSBColor {
private final float hue;
private final float saturation;
private final float brightness;
/**
* Creates an HSB color.
*
* @param hue Hue component (0.0 to 1.0)
* @param saturation Saturation component (0.0 to 1.0)
* @param brightness Brightness component (0.0 to 1.0)
*/
public HSBColor(final float hue, final float saturation, final float brightness) {
this.hue = clamp(hue);
this.saturation = clamp(saturation);
this.brightness = clamp(brightness);
}
/**
* Creates an HSB color from an AWT Color.
*
* @param color The AWT color to convert
*/
public static HSBColor fromColor(final Color color) {
final var hsb = Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), null);
return new HSBColor(hsb[0], hsb[1], hsb[2]);
}
/**
* Converts this HSB color to an AWT Color.
*
* @return The AWT Color
*/
public Color toColor() {
return Color.getHSBColor(this.hue, this.saturation, this.brightness);
}
/**
* Creates a new HSBColor with adjusted brightness.
*
* @param delta The amount to add to brightness (-1.0 to 1.0)
* @return A new HSBColor with adjusted brightness
*/
public HSBColor adjustBrightness(final float delta) {
return new HSBColor(this.hue, this.saturation, this.brightness + delta);
}
/**
* Creates a new HSBColor with adjusted saturation.
*
* @param delta The amount to add to saturation (-1.0 to 1.0)
* @return A new HSBColor with adjusted saturation
*/
public HSBColor adjustSaturation(final float delta) {
return new HSBColor(this.hue, this.saturation + delta, this.brightness);
}
public float getHue() {
return this.hue;
}
public float getSaturation() {
return this.saturation;
}
public float getBrightness() {
return this.brightness;
}
private static float clamp(final float value) {
return Math.max(0.0f, Math.min(1.0f, value));
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || this.getClass() != o.getClass()) {
return false;
}
final HSBColor hsbColor = (HSBColor) o;
return Float.compare(hsbColor.hue, this.hue) == 0 &&
Float.compare(hsbColor.saturation, this.saturation) == 0 &&
Float.compare(hsbColor.brightness, this.brightness) == 0;
}
@Override
public int hashCode() {
return Objects.hash(this.hue, this.saturation, this.brightness);
}
@Override
public String toString() {
return String.format("HSB(%.2f, %.2f, %.2f)", this.hue, this.saturation, this.brightness);
}
}

View File

@@ -0,0 +1,67 @@
package de.extio.game_engine.renderer.g2d.theme;
import java.awt.Color;
import java.awt.Graphics2D;
/**
* Interface for rendering common UI patterns like borders, backgrounds, shadows, etc.
* Different implementations can provide different visual styles.
*/
public interface PatternRenderer {
/**
* Draws a decorative border around a component.
*
* @param g2d The graphics context
* @param x X position
* @param y Y position
* @param width Width of the border area
* @param height Height of the border area
* @param strength Border thickness/strength
* @param color Border color
*/
void drawDecorativeBorder(Graphics2D g2d, int x, int y, int width, int height, int strength, Color color);
/**
* Draws a decorative border with a filled background.
*
* @param g2d The graphics context
* @param x X position
* @param y Y position
* @param width Width of the border area
* @param height Height of the border area
* @param strength Border thickness/strength
* @param borderColor Border color
* @param backgroundColor Background fill color
*/
void drawDecorativeBorderFilled(Graphics2D g2d, int x, int y, int width, int height, int strength, Color borderColor, Color backgroundColor);
/**
* Draws a window panel with border and background.
*
* @param g2d The graphics context
* @param x X position
* @param y Y position
* @param width Width
* @param height Height
* @param thickBorder Whether to use thick border style
* @param mainColor Main panel color
* @param darkerColor Darker shade for borders
* @param scaleFactor UI scale factor
*/
void drawWindowPanel(Graphics2D g2d, int x, int y, int width, int height, boolean thickBorder, Color innerBorderColor, Color outerBorderColor, Color backgroundColor, double scaleFactor);
/**
* Draws a window close button with appropriate styling based on state.
*
* @param g2d The graphics context
* @param width Width of the button
* @param height Height of the button
* @param enabled Whether the button is enabled
* @param state Button state flags (hovered, pressed, toggled)
* @param backgroundColor Optional background color override (null to use theme defaults)
* @param scaleFactor UI scale factor
* @param theme Theme to use for colors
*/
void drawCloseButton(Graphics2D g2d, int width, int height, boolean enabled, int state, Color backgroundColor, double scaleFactor, Theme theme);
}

View File

@@ -0,0 +1,102 @@
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;
import de.extio.game_engine.renderer.model.color.RgbaColor;
/**
* Default implementation of PatternRenderer that draws borders and panels in a decorative style.
* This implementation provides the default theming for UI components.
*/
@Conditional(G2DRendererCondition.class)
@Component
public class SpacecraftPatternRenderer 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 strength2 = strength * 2;
final var strength4 = strength * 4;
g2d.setColor(color);
// Vertical lines
g2d.fillRect(x, y + strength2, strength, height - strength4);
g2d.fillRect(x + width - strength, y + strength2, strength, height - strength4);
// Horizontal lines
g2d.fillRect(x + strength2, y, width - strength4, strength);
g2d.fillRect(x + strength2, y + height - strength, width - strength4, strength);
// Corner squares
g2d.fillRect(x + strength, y + strength, strength2, strength2);
g2d.fillRect(x + width - strength - strength2, y + strength, strength2, strength2);
g2d.fillRect(x + strength, y + height - strength - strength2, strength2, strength2);
g2d.fillRect(x + width - strength - strength2, y + height - strength - strength2, strength2, strength2);
}
@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) {
// Fill background
g2d.setColor(backgroundColor);
g2d.fillRect(x + strength, y + strength, width - strength, height - strength);
// Draw border on top
this.drawDecorativeBorder(g2d, x, y, width, height, strength, 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 strength = Math.max((thickBorder ? 4 : 2), (int) ((thickBorder ? 6 : 3) * scaleFactor));
final var strength2 = strength * 2;
final var backgroundTransparency = new Color(backgroundColor.getRed(), backgroundColor.getGreen(), backgroundColor.getBlue(), 180);
drawDecorativeBorderFilled(g2d, x + strength, y + strength, width - strength2, height - strength2, strength, innerBorderColor, backgroundTransparency);
drawDecorativeBorder(g2d, x, y, width, height, strength, 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 = null;
if (pressed) {
bgColor = theme.getSelectionPrimary().toColor();
}
else if (highlight) {
bgColor = theme.getBackgroundNormal().toColor();
}
else if (backgroundColor == null) {
bgColor = theme.getBorderInner().toColor();
}
else {
bgColor = backgroundColor;
}
g2d.setColor(bgColor);
g2d.fillRect(0, 0, width, height);
if (enabled) {
if (pressed) {
g2d.setColor(RgbaColor.YELLOW.toAwtColor());
}
else if (highlight) {
g2d.setColor(theme.getSelectionSecondary().toColor());
}
else {
g2d.setColor(Color.BLACK);
}
final var textDim = G2DDrawFont.getTextDimensions("X", g2d, 16, scaleFactor);
G2DDrawFont.renderText(g2d, textDim, 1.0, ((width - textDim.getX()) / 2), ((height - textDim.getY()) / 2), (int) (16 * scaleFactor), "X");
}
}
}

View File

@@ -0,0 +1,36 @@
package de.extio.game_engine.renderer.g2d.theme;
import org.springframework.beans.factory.FactoryBean;
public class SpacecraftThemeFactoryBean implements FactoryBean<Theme> {
@Override
public Theme getObject() {
return Theme.builder()
.name("Spacecraft")
.patternRendererName("spacecraftPatternRenderer")
.borderOuter(new HSBColor(0.71f, 0.12f, 0.50f))
.borderInner(new HSBColor(0.71f, 0.12f, 0.80f))
.borderInnerDisabled(new HSBColor(0.71f, 0.15f, 0.20f))
.backgroundNormal(new HSBColor(0.71f, 0.92f, 0.30f))
.backgroundSelected(new HSBColor(0.199f, 0.83f, 0.50f))
.textNormal(new HSBColor(0.0f, 0.0f, 0.80f))
.textDisabled(new HSBColor(0.0f, 0.0f, 0.50f))
.selectionPrimary(new HSBColor(0.199f, 0.83f, 0.50f))
.selectionSecondary(new HSBColor(0.199f, 0.83f, 0.90f))
.windowBackground(new HSBColor(0.71f, 0.6f, 0.05f))
.hoverBrightnessAdjustment(0.25f)
.pressedBrightnessAdjustment(0.40f)
.build();
}
@Override
public Class<?> getObjectType() {
return Theme.class;
}
@Override
public boolean isSingleton() {
return true;
}
}

View File

@@ -0,0 +1,203 @@
package de.extio.game_engine.renderer.g2d.theme;
/**
* Represents a complete UI theme with all color definitions and pattern renderer.
* Themes are element-based rather than component-based, allowing consistent styling across different components.
*/
public class Theme {
private final String name;
private final String patternRendererName;
// Element colors - Borders
private final HSBColor borderOuter;
private final HSBColor borderInner;
private final HSBColor borderInnerDisabled;
// Element colors - Backgrounds
private final HSBColor backgroundNormal;
private final HSBColor backgroundSelected;
// Element colors - Text
private final HSBColor textNormal;
private final HSBColor textDisabled;
// Element colors - Selection/Highlight
private final HSBColor selectionPrimary;
private final HSBColor selectionSecondary;
// Element colors - Window/Panel
private final HSBColor windowBackground;
// Brightness adjustments for states
private final float hoverBrightnessAdjustment;
private final float pressedBrightnessAdjustment;
private Theme(final Builder builder) {
this.name = builder.name;
this.patternRendererName = builder.patternRendererName;
this.borderOuter = builder.borderOuter;
this.borderInner = builder.borderInner;
this.borderInnerDisabled = builder.borderInnerDisabled;
this.backgroundNormal = builder.backgroundNormal;
this.backgroundSelected = builder.backgroundSelected;
this.textNormal = builder.textNormal;
this.textDisabled = builder.textDisabled;
this.selectionPrimary = builder.selectionPrimary;
this.selectionSecondary = builder.selectionSecondary;
this.windowBackground = builder.windowBackground;
this.hoverBrightnessAdjustment = builder.hoverBrightnessAdjustment;
this.pressedBrightnessAdjustment = builder.pressedBrightnessAdjustment;
}
public String getName() {
return this.name;
}
public String getPatternRendererName() {
return this.patternRendererName;
}
public HSBColor getBorderOuter() {
return this.borderOuter;
}
public HSBColor getBorderInner() {
return this.borderInner;
}
public HSBColor getBorderInnerDisabled() {
return this.borderInnerDisabled;
}
public HSBColor getBackgroundNormal() {
return this.backgroundNormal;
}
public HSBColor getBackgroundSelected() {
return this.backgroundSelected;
}
public HSBColor getTextNormal() {
return this.textNormal;
}
public HSBColor getTextDisabled() {
return this.textDisabled;
}
public HSBColor getSelectionPrimary() {
return this.selectionPrimary;
}
public HSBColor getSelectionSecondary() {
return this.selectionSecondary;
}
public HSBColor getWindowBackground() {
return this.windowBackground;
}
public float getHoverBrightnessAdjustment() {
return this.hoverBrightnessAdjustment;
}
public float getPressedBrightnessAdjustment() {
return this.pressedBrightnessAdjustment;
}
public static Builder builder() {
return new Builder();
}
public static class Builder {
private String name;
private String patternRendererName;
private HSBColor borderOuter;
private HSBColor borderInner;
private HSBColor borderInnerDisabled;
private HSBColor backgroundNormal;
private HSBColor backgroundSelected;
private HSBColor textNormal;
private HSBColor textDisabled;
private HSBColor selectionPrimary;
private HSBColor selectionSecondary;
private HSBColor windowBackground;
private float hoverBrightnessAdjustment = 0.25f;
private float pressedBrightnessAdjustment = 0.40f;
public Builder name(final String name) {
this.name = name;
return this;
}
public Builder patternRendererName(final String patternRendererName) {
this.patternRendererName = patternRendererName;
return this;
}
public Builder borderOuter(final HSBColor borderOuter) {
this.borderOuter = borderOuter;
return this;
}
public Builder borderInner(final HSBColor borderInner) {
this.borderInner = borderInner;
return this;
}
public Builder borderInnerDisabled(final HSBColor borderInnerDisabled) {
this.borderInnerDisabled = borderInnerDisabled;
return this;
}
public Builder backgroundNormal(final HSBColor backgroundNormal) {
this.backgroundNormal = backgroundNormal;
return this;
}
public Builder backgroundSelected(final HSBColor backgroundSelected) {
this.backgroundSelected = backgroundSelected;
return this;
}
public Builder textNormal(final HSBColor textNormal) {
this.textNormal = textNormal;
return this;
}
public Builder textDisabled(final HSBColor textDisabled) {
this.textDisabled = textDisabled;
return this;
}
public Builder selectionPrimary(final HSBColor selectionPrimary) {
this.selectionPrimary = selectionPrimary;
return this;
}
public Builder selectionSecondary(final HSBColor selectionSecondary) {
this.selectionSecondary = selectionSecondary;
return this;
}
public Builder windowBackground(final HSBColor windowBackground) {
this.windowBackground = windowBackground;
return this;
}
public Builder hoverBrightnessAdjustment(final float hoverBrightnessAdjustment) {
this.hoverBrightnessAdjustment = hoverBrightnessAdjustment;
return this;
}
public Builder pressedBrightnessAdjustment(final float pressedBrightnessAdjustment) {
this.pressedBrightnessAdjustment = pressedBrightnessAdjustment;
return this;
}
public Theme build() {
return new Theme(this);
}
}
}

View File

@@ -0,0 +1,149 @@
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 VintagePatternRenderer 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) {
g2d.setColor(color);
final var size = strength;
g2d.fillRect(x, y, width, size);
g2d.fillRect(x, y, size, height - size * 2);
g2d.fillRect(x + width - size, y, size, height - size * 2);
g2d.fillRect(x + size * 2, y + height - size, width - size * 4, size);
g2d.fillPolygon(
new int[] { x, x + size * 2, x + size * 2, x + size },
new int[] { y + height - size * 2, y + height, y + height - size, y + height - size * 2 },
4
);
g2d.fillPolygon(
new int[] { x + width, x + width - size * 2, x + width - size * 2, x + width - size },
new int[] { y + height - size * 2, y + height, y + height - size, y + height - size * 2 },
4
);
}
@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) {
g2d.setColor(backgroundColor);
g2d.fillRect(x, y + strength, width, height - strength * 3);
g2d.fillRect(x + strength, y + height - strength * 2, width - strength * 2, strength);
this.drawDecorativeBorder(g2d, x, y, width, height, strength, 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 size = Math.max(3, (int) (5 * scaleFactor));
final var baseColor = new float[3];
innerBorderColor.getRGBColorComponents(baseColor);
Color.RGBtoHSB((int) (baseColor[0] * 255), (int) (baseColor[1] * 255), (int) (baseColor[2] * 255), baseColor);
final var backgroundTransparency = new Color(backgroundColor.getRed(), backgroundColor.getGreen(), backgroundColor.getBlue(), 180);
g2d.setColor(backgroundTransparency);
g2d.fillRect(x, y + size, width, height - size * 3);
g2d.fillRect(x + size, y + height - size * 2, width - size * 2, size);
g2d.setColor(Color.getHSBColor(baseColor[0], baseColor[1], baseColor[2]));
if (thickBorder) {
g2d.fillRect(x, y + size * 2, size, height - size * 4);
g2d.fillRect(x + width - size, y + size * 2, size, height - size * 4);
g2d.fillRect(x, y + size * 2, width, size);
}
else {
g2d.fillRect(x, y, size, height - size * 2);
g2d.fillRect(x + width - size, y, size, height - size * 2);
g2d.fillRect(x, y, width, size);
}
g2d.fillRect(x + size * 2, y + height - size, width - size * 4, size);
g2d.fillPolygon(
new int[] { x, x + size * 2, x + size * 2, x + size },
new int[] { y + height - size * 2, y + height, y + height - size, y + height - size * 2 },
4
);
g2d.fillPolygon(
new int[] { x + width, x + width - size * 2, x + width - size * 2, x + width - size },
new int[] { y + height - size * 2, y + height, y + height - size, y + height - size * 2 },
4
);
if (thickBorder) {
for (var i = 0; i < size * 2; i++) {
g2d.setColor(Color.getHSBColor(baseColor[0], baseColor[1], baseColor[2] * ((0.5F / (size * 2) * i) + 0.5F)));
g2d.drawLine(x, y + i, x + width, y + i);
}
}
}
@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 size = Math.max(2, (int) (3 * scaleFactor));
Color baseColor = null;
if (pressed) {
baseColor = theme.getSelectionPrimary().toColor();
}
else if (highlight) {
baseColor = theme.getSelectionSecondary().toColor();
}
else if (backgroundColor == null) {
baseColor = theme.getBorderInner().toColor();
}
else {
baseColor = backgroundColor;
}
final var hsb = Color.RGBtoHSB(baseColor.getRed(), baseColor.getGreen(), baseColor.getBlue(), null);
g2d.setColor(Color.BLACK);
g2d.fillRect(0, 0, width, height);
g2d.setColor(Color.getHSBColor(hsb[0], hsb[1], hsb[2]));
g2d.fillRect(0, 0, width, size);
g2d.fillRect(0, 0, size, height);
g2d.fillRect(width - size, 0, size, height);
g2d.fillRect(0, height - size, width, size);
if (enabled) {
final Color textColor;
if (pressed) {
textColor = theme.getSelectionPrimary().toColor();
}
else if (highlight) {
textColor = theme.getTextNormal().toColor();
}
else {
textColor = theme.getTextDisabled().toColor();
}
g2d.setColor(textColor);
final var crossSize = Math.min(width, height) / 2;
final var centerX = width / 2;
final var centerY = height / 2;
final var crossThickness = Math.max(1, (int) (2 * scaleFactor));
g2d.fillRect(centerX - crossSize / 2, centerY - crossThickness / 2, crossSize, crossThickness);
g2d.fillRect(centerX - crossThickness / 2, centerY - crossSize / 2, crossThickness, crossSize);
}
}
}

View File

@@ -0,0 +1,36 @@
package de.extio.game_engine.renderer.g2d.theme;
import org.springframework.beans.factory.FactoryBean;
public class VintageThemeFactoryBean implements FactoryBean<Theme> {
@Override
public Theme getObject() {
return Theme.builder()
.name("Vintage")
.patternRendererName("vintagePatternRenderer")
.borderOuter(new HSBColor(0.08f, 0.55f, 0.22f))
.borderInner(new HSBColor(0.10f, 0.30f, 0.55f))
.borderInnerDisabled(new HSBColor(0.08f, 0.20f, 0.18f))
.backgroundNormal(new HSBColor(0.085f, 0.45f, 0.28f))
.backgroundSelected(new HSBColor(0.11f, 0.55f, 0.55f))
.textNormal(new HSBColor(0.12f, 0.10f, 0.92f))
.textDisabled(new HSBColor(0.12f, 0.05f, 0.60f))
.selectionPrimary(new HSBColor(0.98f, 0.55f, 0.60f))
.selectionSecondary(new HSBColor(0.11f, 0.70f, 0.80f))
.windowBackground(new HSBColor(0.08f, 0.60f, 0.12f))
.hoverBrightnessAdjustment(0.15f)
.pressedBrightnessAdjustment(0.30f)
.build();
}
@Override
public Class<?> getObjectType() {
return Theme.class;
}
@Override
public boolean isSingleton() {
return true;
}
}

View File

@@ -1,13 +0,0 @@
package de.extio.game_engine.renderer.model.bo;
import de.extio.game_engine.renderer.model.RenderingBoHasDimension;
/**
* Renders a window
*/
public interface DrawWindowRenderingBo extends RenderingBoHasDimension {
DrawWindowRenderingBo setThickBorder(boolean thickBorder);
}

View File

@@ -1,6 +1,8 @@
package de.extio.game_engine.demo; package de.extio.game_engine.demo;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
@@ -10,6 +12,7 @@ import de.extio.game_engine.event.EventService;
import de.extio.game_engine.i18n.LocalizationService; import de.extio.game_engine.i18n.LocalizationService;
import de.extio.game_engine.module.AbstractClientModule; import de.extio.game_engine.module.AbstractClientModule;
import de.extio.game_engine.renderer.RendererControl; import de.extio.game_engine.renderer.RendererControl;
import de.extio.game_engine.renderer.ThemeManager;
import de.extio.game_engine.renderer.container.Window; import de.extio.game_engine.renderer.container.Window;
import de.extio.game_engine.renderer.model.RenderingBoLayer; import de.extio.game_engine.renderer.model.RenderingBoLayer;
import de.extio.game_engine.renderer.model.bo.ControlRenderingBo; import de.extio.game_engine.renderer.model.bo.ControlRenderingBo;
@@ -39,10 +42,17 @@ public class DemoModule extends AbstractClientModule {
@Autowired @Autowired
private LocalizationService localizationService; private LocalizationService localizationService;
@Autowired
private ThemeManager themeManager;
private Window mainWindow; private Window mainWindow;
private Window secondaryWindow; private Window secondaryWindow;
private Window themeSelectionWindow;
private final Map<String, String> themeSelectionByControlId = new HashMap<>();
@Override @Override
public void onLoad() { public void onLoad() {
this.mainWindow = this.applicationContext.getBean(Window.class); this.mainWindow = this.applicationContext.getBean(Window.class);
@@ -56,6 +66,12 @@ public class DemoModule extends AbstractClientModule {
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.setNormalizedPosition(RendererControl.REFERENCE_RESOLUTION.divide(7).multiply(3));
this.themeSelectionWindow.setNormalizedDimension(RendererControl.REFERENCE_RESOLUTION.divide(7).multiply(2));
this.themeSelectionWindow.setDraggable(true);
this.themeSelectionWindow.setCloseButton(true);
this.getModuleService().changeActiveState(this.getId(), true); this.getModuleService().changeActiveState(this.getId(), true);
this.getModuleService().changeDisplayState(this.getId(), true); this.getModuleService().changeDisplayState(this.getId(), true);
} }
@@ -64,10 +80,16 @@ public class DemoModule extends AbstractClientModule {
public void onUnload() { public void onUnload() {
this.getModuleService().unloadModule(this.mainWindow.getId()); this.getModuleService().unloadModule(this.mainWindow.getId());
this.getModuleService().unloadModule(this.secondaryWindow.getId()); this.getModuleService().unloadModule(this.secondaryWindow.getId());
this.getModuleService().unloadModule(this.themeSelectionWindow.getId());
} }
@Override @Override
public void onActivate() { public void onActivate() {
final var audioOptions = this.audioController.getAudioOptions();
audioOptions.getMusicOptions().setVolume(0.0f);
audioOptions.getSfxOptions().setVolume(0.0f);
this.audioController.applyAudioOptions(audioOptions);
this.setupMainWindow(); this.setupMainWindow();
this.getModuleService().changeActiveState(this.mainWindow.getId(), true); this.getModuleService().changeActiveState(this.mainWindow.getId(), true);
this.eventService.register(UiControlEvent.class, this.getId(), this::onUiControlEvent); this.eventService.register(UiControlEvent.class, this.getId(), this::onUiControlEvent);
@@ -77,6 +99,7 @@ public class DemoModule extends AbstractClientModule {
public void onDeactivate() { public void onDeactivate() {
this.getModuleService().changeActiveState(this.mainWindow.getId(), false); this.getModuleService().changeActiveState(this.mainWindow.getId(), false);
this.getModuleService().changeActiveState(this.secondaryWindow.getId(), false); this.getModuleService().changeActiveState(this.secondaryWindow.getId(), false);
this.getModuleService().changeActiveState(this.themeSelectionWindow.getId(), false);
} }
@Override @Override
@@ -89,6 +112,7 @@ public class DemoModule extends AbstractClientModule {
public void onHide() { public void onHide() {
this.getModuleService().changeDisplayState(this.mainWindow.getId(), false); this.getModuleService().changeDisplayState(this.mainWindow.getId(), false);
this.getModuleService().changeActiveState(this.secondaryWindow.getId(), false); this.getModuleService().changeActiveState(this.secondaryWindow.getId(), false);
this.getModuleService().changeActiveState(this.themeSelectionWindow.getId(), false);
this.audioController.stopMusic(); this.audioController.stopMusic();
} }
@@ -96,19 +120,31 @@ public class DemoModule extends AbstractClientModule {
switch (event.getId()) { switch (event.getId()) {
case "DemoModule_MainWindow_Label_Welcome" -> { case "DemoModule_MainWindow_Label_Welcome" -> {
this.audioController.play(new StaticResource(List.of("audio"), "alert0.ogg")); this.audioController.play(new StaticResource(List.of("audio"), "alert0.ogg"));
}
case "DemoModule_MainWindow_Label_Start" -> {
this.audioController.play(new StaticResource(List.of("audio"), "alert2.ogg"));
this.setupSecondaryWindow(); this.setupSecondaryWindow();
this.getModuleService().changeActiveState(this.secondaryWindow.getId(), true); this.getModuleService().changeActiveState(this.secondaryWindow.getId(), true);
this.getModuleService().changeDisplayState(this.secondaryWindow.getId(), true); this.getModuleService().changeDisplayState(this.secondaryWindow.getId(), true);
} }
case "DemoModule_SecondaryWindow_Button_Ok" -> { case "DemoModule_MainWindow_Label_Start" -> {
this.audioController.play(new StaticResource(List.of("audio"), "alert0.ogg")); this.audioController.play(new StaticResource(List.of("audio"), "alert0.ogg"));
this.setupThemeSelectionWindow();
this.getModuleService().changeActiveState(this.themeSelectionWindow.getId(), true);
this.getModuleService().changeDisplayState(this.themeSelectionWindow.getId(), true);
}
case "DemoModule_SecondaryWindow_Button_Ok" -> {
this.audioController.play(new StaticResource(List.of("audio"), "alert2.ogg"));
this.getModuleService().changeActiveState(this.secondaryWindow.getId(), false); this.getModuleService().changeActiveState(this.secondaryWindow.getId(), false);
} }
default -> {
final var themeName = this.themeSelectionByControlId.get(event.getId());
if (themeName != null) {
this.audioController.play(new StaticResource(List.of("audio"), "alert2.ogg"));
this.themeManager.setCurrentTheme(themeName);
this.getModuleService().changeActiveState(this.themeSelectionWindow.getId(), false);
}
}
} }
} }
@@ -143,6 +179,9 @@ public class DemoModule extends AbstractClientModule {
} }
private void setupSecondaryWindow() { private void setupSecondaryWindow() {
this.secondaryWindow.clearRenderingBos();
this.themeSelectionByControlId.clear();
var bo = this.renderingBoPool.acquire("DemoModule_SecondaryWindow_Text", DrawFontRenderingBo.class) var bo = this.renderingBoPool.acquire("DemoModule_SecondaryWindow_Text", DrawFontRenderingBo.class)
.setText(this.localizationService.translate("test-5").replace("\\n", "\n")) .setText(this.localizationService.translate("test-5").replace("\\n", "\n"))
.setSize(32) .setSize(32)
@@ -159,5 +198,68 @@ public class DemoModule extends AbstractClientModule {
.withDimensionAbsolute(160, 60) .withDimensionAbsolute(160, 60)
.withPositionRelative(RendererControl.REFERENCE_RESOLUTION.divide(7).getX() - 80, 200); .withPositionRelative(RendererControl.REFERENCE_RESOLUTION.divide(7).getX() - 80, 200);
this.secondaryWindow.putRenderingBo(bo); this.secondaryWindow.putRenderingBo(bo);
this.secondaryWindow.draw();
}
private void setupThemeSelectionWindow() {
this.themeSelectionWindow.clearRenderingBos();
this.themeSelectionByControlId.clear();
final var themes = this.themeManager.getAvailableThemeNames();
final var title = themes.size() <= 1
? "Theme"
: "Select Theme";
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);
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);
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);
bo = this.renderingBoPool.acquire(controlId, ControlRenderingBo.class)
.setCaption(themeName)
.setType(ButtonControl.class)
.setFontSize(28)
.setVisible(true)
.setEnabled(true)
.withDimensionAbsolute(buttonWidth, buttonHeight)
.withPositionRelative((RendererControl.REFERENCE_RESOLUTION.divide(7).multiply(2).getX() - buttonWidth) / 2, startY + i * (buttonHeight + gap));
this.themeSelectionWindow.putRenderingBo(bo);
}
this.themeSelectionWindow.draw();
}
private static String sanitizeThemeId(final String themeName) {
if (themeName == null || themeName.isBlank()) {
return "Unknown";
}
return themeName.replaceAll("[^a-zA-Z0-9]+", "_");
} }
} }