From bc627f3fe8c97d4e00ab4c12294b9740f8689c65 Mon Sep 17 00:00:00 2001 From: extio-de Date: Sat, 27 Dec 2025 23:25:02 +0100 Subject: [PATCH] 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. --- .../game_engine/renderer/ThemeManager.java | 17 + .../container/legacy/EnhancedWindow.java | 141 ------- .../renderer/container/legacy/Window.java | 358 ------------------ .../renderer/g2d/G2DAutoConfiguration.java | 41 +- .../game_engine/renderer/g2d/G2DRenderer.java | 22 ++ .../g2d/bo/rendering/G2DDrawEffect.java | 20 +- .../g2d/bo/rendering/G2DDrawWindow.java | 82 ---- .../g2d/bo/rendering/G2DDrawWindow2.java | 194 ---------- .../components/ComponentRenderingSupport.java | 61 --- .../g2d/control/components/CustomButton.java | 59 ++- .../g2d/control/components/CustomLabel.java | 30 +- .../g2d/control/components/CustomSlider.java | 109 +++--- .../g2d/control/components/CustomSwitch.java | 70 ++-- .../components/CustomWindowCloseButton.java | 48 +-- .../control/components/CustomWindowPanel.java | 45 ++- .../control/impl/G2DButtonControlImpl.java | 2 +- .../g2d/control/impl/G2DLabelControlImpl.java | 2 +- .../control/impl/G2DSliderControlImpl.java | 2 +- .../control/impl/G2DSwitchControlImpl.java | 3 +- .../g2d/control/impl/G2DTableControlImpl.java | 5 +- .../control/impl/G2DTextfieldControlImpl.java | 6 +- .../impl/G2DTextfieldControlImpl2.java | 11 +- .../impl/G2DToggleButtonControlImpl.java | 3 +- .../impl/G2DWindowCloseButtonControlImpl.java | 3 +- .../impl/G2DWindowPanelControlImpl.java | 2 +- .../renderer/g2d/theme/ComponentState.java | 37 ++ .../renderer/g2d/theme/G2DThemeManager.java | 111 ++++++ .../renderer/g2d/theme/HSBColor.java | 107 ++++++ .../renderer/g2d/theme/PatternRenderer.java | 67 ++++ .../g2d/theme/SpacecraftPatternRenderer.java | 102 +++++ .../g2d/theme/SpacecraftThemeFactoryBean.java | 36 ++ .../game_engine/renderer/g2d/theme/Theme.java | 203 ++++++++++ .../g2d/theme/VintagePatternRenderer.java | 149 ++++++++ .../g2d/theme/VintageThemeFactoryBean.java | 36 ++ .../model/bo/DrawWindowRenderingBo.java | 13 - .../de/extio/game_engine/demo/DemoModule.java | 112 +++++- 36 files changed, 1276 insertions(+), 1033 deletions(-) create mode 100644 src/main/java/de/extio/game_engine/renderer/ThemeManager.java delete mode 100644 src/main/java/de/extio/game_engine/renderer/container/legacy/EnhancedWindow.java delete mode 100644 src/main/java/de/extio/game_engine/renderer/container/legacy/Window.java delete mode 100644 src/main/java/de/extio/game_engine/renderer/g2d/bo/rendering/G2DDrawWindow.java delete mode 100644 src/main/java/de/extio/game_engine/renderer/g2d/bo/rendering/G2DDrawWindow2.java delete mode 100644 src/main/java/de/extio/game_engine/renderer/g2d/control/components/ComponentRenderingSupport.java create mode 100644 src/main/java/de/extio/game_engine/renderer/g2d/theme/ComponentState.java create mode 100644 src/main/java/de/extio/game_engine/renderer/g2d/theme/G2DThemeManager.java create mode 100644 src/main/java/de/extio/game_engine/renderer/g2d/theme/HSBColor.java create mode 100644 src/main/java/de/extio/game_engine/renderer/g2d/theme/PatternRenderer.java create mode 100644 src/main/java/de/extio/game_engine/renderer/g2d/theme/SpacecraftPatternRenderer.java create mode 100644 src/main/java/de/extio/game_engine/renderer/g2d/theme/SpacecraftThemeFactoryBean.java create mode 100644 src/main/java/de/extio/game_engine/renderer/g2d/theme/Theme.java create mode 100644 src/main/java/de/extio/game_engine/renderer/g2d/theme/VintagePatternRenderer.java create mode 100644 src/main/java/de/extio/game_engine/renderer/g2d/theme/VintageThemeFactoryBean.java delete mode 100644 src/main/java/de/extio/game_engine/renderer/model/bo/DrawWindowRenderingBo.java diff --git a/src/main/java/de/extio/game_engine/renderer/ThemeManager.java b/src/main/java/de/extio/game_engine/renderer/ThemeManager.java new file mode 100644 index 0000000..0b7d419 --- /dev/null +++ b/src/main/java/de/extio/game_engine/renderer/ThemeManager.java @@ -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 getAvailableThemeNames(); + + Theme getCurrentTheme(); + + void setCurrentTheme(Theme theme); + + void setCurrentTheme(String themeName); + +} diff --git a/src/main/java/de/extio/game_engine/renderer/container/legacy/EnhancedWindow.java b/src/main/java/de/extio/game_engine/renderer/container/legacy/EnhancedWindow.java deleted file mode 100644 index b9f22cb..0000000 --- a/src/main/java/de/extio/game_engine/renderer/container/legacy/EnhancedWindow.java +++ /dev/null @@ -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 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 areaFunction; - - public static void resetWindows() { - // boolean removed = false; - // final Iterator 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 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); - // } -} diff --git a/src/main/java/de/extio/game_engine/renderer/container/legacy/Window.java b/src/main/java/de/extio/game_engine/renderer/container/legacy/Window.java deleted file mode 100644 index 1a2c48a..0000000 --- a/src/main/java/de/extio/game_engine/renderer/container/legacy/Window.java +++ /dev/null @@ -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 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, final BiConsumer, 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; - } -} diff --git a/src/main/java/de/extio/game_engine/renderer/g2d/G2DAutoConfiguration.java b/src/main/java/de/extio/game_engine/renderer/g2d/G2DAutoConfiguration.java index b7dbee5..2fb8998 100644 --- a/src/main/java/de/extio/game_engine/renderer/g2d/G2DAutoConfiguration.java +++ b/src/main/java/de/extio/game_engine/renderer/g2d/G2DAutoConfiguration.java @@ -1,25 +1,62 @@ 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.Qualifier; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 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 @ConditionalOnProperty(name = "game-engine.renderer.enabled", havingValue = "true", matchIfMissing = true) @ConditionalOnProperty(name = "game-engine.renderer.strategy", havingValue = "g2d", matchIfMissing = true) public class G2DAutoConfiguration { @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(); renderer.setTitle(title); + renderer.setThemeManager(themeManager); + renderer.setStaticResourceService(staticResourceService); + themeManager.setG2dRenderer(renderer); return renderer; } @Bean - public G2DRendererControl g2dRendererControl(final G2DRenderer renderer) { + G2DRendererControl g2dRendererControl(final G2DRenderer renderer) { return new G2DRendererControl(renderer); } + @Bean + FactoryBean spacecraftTheme() { + return new SpacecraftThemeFactoryBean(); + } + + @Bean + FactoryBean vintageTheme() { + return new VintageThemeFactoryBean(); + } + + @Bean + G2DThemeManager g2dThemeManager( + final StaticResourceService staticResourceService, + final List patternRendererList, + final Map themes, + @Qualifier("spacecraftTheme") final Theme defaultTheme) { + return new G2DThemeManager(staticResourceService, patternRendererList, themes, defaultTheme); + } + } diff --git a/src/main/java/de/extio/game_engine/renderer/g2d/G2DRenderer.java b/src/main/java/de/extio/game_engine/renderer/g2d/G2DRenderer.java index ee5be74..34fac29 100644 --- a/src/main/java/de/extio/game_engine/renderer/g2d/G2DRenderer.java +++ b/src/main/java/de/extio/game_engine/renderer/g2d/G2DRenderer.java @@ -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.control.G2DDrawControl; 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.RenderingBoLayer; 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.event.TakeScreenshotEvent; import de.extio.game_engine.resource.StaticResource; +import de.extio.game_engine.resource.StaticResourceService; import de.extio.game_engine.util.RingBuffer; public class G2DRenderer implements Renderer { @@ -78,6 +80,10 @@ public class G2DRenderer implements Renderer { private StaticResource previousDefaultFont = null; + private G2DThemeManager themeManager; + + private StaticResourceService staticResourceService; + public G2DRenderer() { LOGGER.info("ctor"); @@ -367,6 +373,22 @@ public class G2DRenderer implements Renderer { 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 public void setRendererData(final RendererData rendererData) { this.rendererData = rendererData; diff --git a/src/main/java/de/extio/game_engine/renderer/g2d/bo/rendering/G2DDrawEffect.java b/src/main/java/de/extio/game_engine/renderer/g2d/bo/rendering/G2DDrawEffect.java index 777a1b3..abd4077 100644 --- a/src/main/java/de/extio/game_engine/renderer/g2d/bo/rendering/G2DDrawEffect.java +++ b/src/main/java/de/extio/game_engine/renderer/g2d/bo/rendering/G2DDrawEffect.java @@ -11,7 +11,7 @@ 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.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.RenderingBoLayer; 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 effectColor = this.color == null ? Color.WHITE : this.color.toAwtColor(); - 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()); - } - 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()); + // 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) { + 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); + } } break; diff --git a/src/main/java/de/extio/game_engine/renderer/g2d/bo/rendering/G2DDrawWindow.java b/src/main/java/de/extio/game_engine/renderer/g2d/bo/rendering/G2DDrawWindow.java deleted file mode 100644 index 2a15980..0000000 --- a/src/main/java/de/extio/game_engine/renderer/g2d/bo/rendering/G2DDrawWindow.java +++ /dev/null @@ -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; - } - -} diff --git a/src/main/java/de/extio/game_engine/renderer/g2d/bo/rendering/G2DDrawWindow2.java b/src/main/java/de/extio/game_engine/renderer/g2d/bo/rendering/G2DDrawWindow2.java deleted file mode 100644 index 5ffed07..0000000 --- a/src/main/java/de/extio/game_engine/renderer/g2d/bo/rendering/G2DDrawWindow2.java +++ /dev/null @@ -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 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(); - } - } - - } - -} diff --git a/src/main/java/de/extio/game_engine/renderer/g2d/control/components/ComponentRenderingSupport.java b/src/main/java/de/extio/game_engine/renderer/g2d/control/components/ComponentRenderingSupport.java deleted file mode 100644 index 448c050..0000000 --- a/src/main/java/de/extio/game_engine/renderer/g2d/control/components/ComponentRenderingSupport.java +++ /dev/null @@ -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); - } - -} diff --git a/src/main/java/de/extio/game_engine/renderer/g2d/control/components/CustomButton.java b/src/main/java/de/extio/game_engine/renderer/g2d/control/components/CustomButton.java index 7c91ee9..5fc2c72 100644 --- a/src/main/java/de/extio/game_engine/renderer/g2d/control/components/CustomButton.java +++ b/src/main/java/de/extio/game_engine/renderer/g2d/control/components/CustomButton.java @@ -6,17 +6,27 @@ import java.awt.Graphics2D; import java.awt.event.ActionListener; 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") 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); + this.themeManager = themeManager; } @Override public void paint(final Graphics g) { + if (this.themeManager == null) { + return; + } + final var g2d = (Graphics2D) g; + final Theme theme = this.themeManager.getCurrentTheme(); float h, s, b; @@ -24,24 +34,21 @@ public class CustomButton extends CustomAbstractButton { final var bgrStrength = (this.getWidth() < 48 || this.getHeight() < 48) ? 0 : 2; if (this.backgroundColor == null) { - if ((this.state & STATE_TOGGLED) == 0) { - h = ComponentRenderingSupport.HSB_COMPONENT_BGR.h(); - s = ComponentRenderingSupport.HSB_COMPONENT_BGR.s(); - b = ComponentRenderingSupport.HSB_COMPONENT_BGR.b(); - } - else { - h = ComponentRenderingSupport.HSB_COMPONENT_SELECTED_0.b(); - s = ComponentRenderingSupport.HSB_COMPONENT_SELECTED_0.s(); - b = ComponentRenderingSupport.HSB_COMPONENT_SELECTED_0.b(); - } + // Use theme colors + final var baseColor = (this.state & STATE_TOGGLED) == 0 ? theme.getBackgroundNormal() : theme.getBackgroundSelected(); + h = baseColor.getHue(); + s = baseColor.getSaturation(); + b = baseColor.getBrightness(); + if ((this.state & STATE_PRESSED) != 0) { - b += 0.40F; + b += theme.getPressedBrightnessAdjustment(); } else if ((this.state & STATE_HOVERED) != 0) { - b += 0.25F; + b += theme.getHoverBrightnessAdjustment(); } } else { + // Use custom background color final var hsb = new float[3]; Color.RGBtoHSB(this.backgroundColor.getRed(), this.backgroundColor.getGreen(), this.backgroundColor.getBlue(), hsb); h = hsb[0]; @@ -54,12 +61,14 @@ public class CustomButton extends CustomAbstractButton { b = 0.50F; } if ((this.state & STATE_PRESSED) != 0) { - b += 0.40F; + b += theme.getPressedBrightnessAdjustment(); } 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.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); } else if (this.caption != null) { + // Text color based on state if ((this.state & (STATE_HOVERED | STATE_TOGGLED)) != 0) { g2d.setColor(Color.WHITE); } else if (this.isEnabled()) { - g2d.setColor(Color.LIGHT_GRAY); + g2d.setColor(theme.getTextNormal().toColor()); } else { - g2d.setColor(Color.GRAY); + g2d.setColor(theme.getTextDisabled().toColor()); } final var textDim = G2DDrawFont.getTextDimensions(this.caption, g2d, this.fontSize, this.scaleFactor); G2DDrawFont.renderText(g2d, @@ -89,10 +99,17 @@ public class CustomButton extends CustomAbstractButton { this.caption); } - var color = (this.state & STATE_HOVERED) != 0 ? ComponentRenderingSupport.COLOR_COMPONENT_SELECTED_0 : ComponentRenderingSupport.COLOR_COMPONENT_BORDER0; - ComponentRenderingSupport.drawDecorativeBorder(g2d, 0, 0, this.getWidth() - 1, this.getHeight() - 1, borderStrength, color); - color = this.isEnabled() ? (this.state & STATE_HOVERED) != 0 ? ComponentRenderingSupport.COLOR_COMPONENT_SELECTED_1 : ComponentRenderingSupport.COLOR_COMPONENT_BORDER1 : ComponentRenderingSupport.COLOR_COMPONENT_BORDER1_DIS; - ComponentRenderingSupport.drawDecorativeBorder(g2d, borderStrength, borderStrength, this.getWidth() - 1 - 2 * borderStrength, this.getHeight() - 1 - 2 * borderStrength, borderStrength, color); + // Draw borders using theme colors and pattern renderer + var borderColor = (this.state & STATE_HOVERED) != 0 ? theme.getSelectionPrimary().toColor() : theme.getBorderOuter().toColor(); + final var patternRenderer = this.themeManager.getPatternRenderer(theme.getPatternRendererName()); + 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); + } } } diff --git a/src/main/java/de/extio/game_engine/renderer/g2d/control/components/CustomLabel.java b/src/main/java/de/extio/game_engine/renderer/g2d/control/components/CustomLabel.java index f9a2749..4588d9d 100644 --- a/src/main/java/de/extio/game_engine/renderer/g2d/control/components/CustomLabel.java +++ b/src/main/java/de/extio/game_engine/renderer/g2d/control/components/CustomLabel.java @@ -10,7 +10,9 @@ import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; 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.theme.Theme; import de.extio.game_engine.renderer.model.bo.HorizontalAlignment; import de.extio.game_engine.spatial2.model.CoordI2; import de.extio.game_engine.spatial2.model.ImmutableCoordI2; @@ -44,6 +46,8 @@ public class CustomLabel extends Component { private CoordI2 lastMousePosition; + private final ThemeManager themeManager; + public void setCaption(final String caption) { this.caption = caption; } @@ -92,7 +96,8 @@ public class CustomLabel extends Component { return this.lastMousePosition; } - public CustomLabel(final ActionListener listener) { + public CustomLabel(final ActionListener listener, final ThemeManager themeManager) { + this.themeManager = themeManager; this.setIgnoreRepaint(true); this.addMouseListener(new MouseAdapter() { @@ -146,7 +151,12 @@ public class CustomLabel extends Component { @Override public void paint(final Graphics g) { + if (this.themeManager == null) { + return; + } + final var g2d = (Graphics2D) g; + final Theme theme = this.themeManager.getCurrentTheme(); if (this.backgroundColor != null) { g2d.setColor(this.backgroundColor); @@ -155,9 +165,9 @@ public class CustomLabel extends Component { float h, s, b; if (this.foregroundColor == null) { - h = 0.0F; - s = 0.0F; - b = 0.80F; + h = theme.getTextNormal().getHue(); + s = theme.getTextNormal().getSaturation(); + b = theme.getTextNormal().getBrightness(); } else { 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) { - b -= 0.30F; + h = theme.getTextDisabled().getHue(); + s = theme.getTextDisabled().getSaturation(); + b = theme.getTextDisabled().getBrightness(); } else if ((this.state & STATE_PRESSED) != 0) { - h = ComponentRenderingSupport.HSB_COMPONENT_SELECTED_0.b(); - s = ComponentRenderingSupport.HSB_COMPONENT_SELECTED_0.s(); - b = ComponentRenderingSupport.HSB_COMPONENT_SELECTED_0.b() + 0.4f; + h = theme.getSelectionPrimary().getHue(); + s = theme.getSelectionPrimary().getSaturation(); + b = theme.getSelectionPrimary().getBrightness() + theme.getPressedBrightnessAdjustment(); } else if ((this.state & STATE_HOVERED) != 0) { - b += 0.15F; + b += theme.getHoverBrightnessAdjustment(); } h = Math.max(0.0f, Math.min(1.0f, h)); diff --git a/src/main/java/de/extio/game_engine/renderer/g2d/control/components/CustomSlider.java b/src/main/java/de/extio/game_engine/renderer/g2d/control/components/CustomSlider.java index fec9486..20c2d0c 100644 --- a/src/main/java/de/extio/game_engine/renderer/g2d/control/components/CustomSlider.java +++ b/src/main/java/de/extio/game_engine/renderer/g2d/control/components/CustomSlider.java @@ -10,6 +10,8 @@ import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; 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.ImmutableCoordI2; @@ -44,6 +46,8 @@ public class CustomSlider extends Component { private long lastActionEventReleased; + private final ThemeManager themeManager; + public void setScaleFactor(final double scaleFactor) { this.scaleFactor = scaleFactor; } @@ -92,9 +96,10 @@ public class CustomSlider extends Component { return this.lastMousePosition; } - public CustomSlider(final ActionListener listener) { + public CustomSlider(final ActionListener listener, final ThemeManager themeManager) { this.setIgnoreRepaint(true); this.actionListener = listener; + this.themeManager = themeManager; this.addMouseListener(new MouseAdapter() { @@ -168,79 +173,87 @@ public class CustomSlider extends Component { @Override public void paint(final Graphics g) { + if (this.themeManager == null) { + return; + } + final var g2d = (Graphics2D) g; + final Theme theme = this.themeManager.getCurrentTheme(); float h, s, b; if (this.color == null) { - h = 0.0F; - s = 0.0F; - b = 0.80F; + if (!this.enabled) { + h = theme.getTextDisabled().getHue(); + s = theme.getTextDisabled().getSaturation(); + b = theme.getTextDisabled().getBrightness(); + } + else { + h = theme.getTextNormal().getHue(); + s = theme.getTextNormal().getSaturation(); + b = theme.getTextNormal().getBrightness(); + } } else { final var hsb = Color.RGBtoHSB(this.color.getRed(), this.color.getGreen(), this.color.getBlue(), null); h = hsb[0]; s = hsb[1]; b = hsb[2]; - } - if (!this.enabled) { - b -= 0.30F; + if (!this.enabled) { + b -= 0.30F; + } } // Main line if (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 - b -= 0.20F; - final var length = this.horizontal ? this.getWidth() : this.getHeight(); - for (var i = 1; i < 10; i++) { - final var pos = (int) ((double) length / 10.0 * i); - if (this.horizontal) { - this.drawLine(g2d, h, s, b, pos, this.getHeight() / 4, pos, this.getHeight() / 4 * 3, !this.horizontal); - } - else { - this.drawLine(g2d, h, s, b, this.getWidth() / 4, pos, this.getWidth() / 4 * 3, pos, !this.horizontal); - } - } - b += 0.20F; - - // Pointer - if ((this.state & STATE_HOVERED) != 0 && this.lastMousePosition != null) { - try { + // Small lines + b -= 0.20F; + final var length = this.horizontal ? this.getWidth() : this.getHeight(); + for (var i = 1; i < 10; i++) { + final var pos = (int) ((double) length / 10.0 * i); if (this.horizontal) { - this.drawLine(g2d, h, s, b, this.lastMousePosition.getX(), 0, this.lastMousePosition.getX(), this.getHeight(), !this.horizontal); + this.drawLine(g2d, h, s, b, pos, this.getHeight() / 4, pos, this.getHeight() / 4 * 3, !this.horizontal); } else { - this.drawLine(g2d, h, s, b, 0, this.lastMousePosition.getY(), this.getWidth(), this.lastMousePosition.getY(), !this.horizontal); + this.drawLine(g2d, h, s, b, this.getWidth() / 4, pos, this.getWidth() / 4 * 3, pos, !this.horizontal); } } - catch (final NullPointerException exc) { - // AWT MouseAdapter can set this.lastMousePosition null at any time + b += 0.20F; + + // Pointer + if ((this.state & STATE_HOVERED) != 0 && this.lastMousePosition != null) { + try { + if (this.horizontal) { + this.drawLine(g2d, h, s, b, this.lastMousePosition.getX(), 0, this.lastMousePosition.getX(), this.getHeight(), !this.horizontal); + } + else { + this.drawLine(g2d, h, s, b, 0, this.lastMousePosition.getY(), this.getWidth(), this.lastMousePosition.getY(), !this.horizontal); + } + } + catch (final NullPointerException exc) { + // AWT MouseAdapter can set this.lastMousePosition null at any time + } } - } - - // Value2 - if (this.value2 != this.value) { + + // Value2 + if (this.value2 != this.value) { + if (this.horizontal) { + this.drawLine(g2d, h, s, b, (int) (this.getWidth() * this.value2), 0, (int) (this.getWidth() * this.value2), this.getHeight(), !this.horizontal); + } + else { + this.drawLine(g2d, h, s, b, 0, (int) (this.getHeight() * (1.0 - this.value2)), this.getWidth(), (int) (this.getHeight() * (1.0 - this.value2)), !this.horizontal); + } + } + + // Value + b += 0.15F; if (this.horizontal) { - this.drawLine(g2d, h, s, b, (int) (this.getWidth() * this.value2), 0, (int) (this.getWidth() * this.value2), this.getHeight(), !this.horizontal); + this.drawLine(g2d, h, s, b, (int) (this.getWidth() * this.value), 0, (int) (this.getWidth() * this.value), this.getHeight(), !this.horizontal); } else { - this.drawLine(g2d, h, s, b, 0, (int) (this.getHeight() * (1.0 - this.value2)), this.getWidth(), (int) (this.getHeight() * (1.0 - this.value2)), !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); } } - - // Value - b += 0.15F; - if (this.horizontal) { - this.drawLine(g2d, h, s, b, (int) (this.getWidth() * this.value), 0, (int) (this.getWidth() * this.value), this.getHeight(), !this.horizontal); - } - else { - 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) { diff --git a/src/main/java/de/extio/game_engine/renderer/g2d/control/components/CustomSwitch.java b/src/main/java/de/extio/game_engine/renderer/g2d/control/components/CustomSwitch.java index c0fcee1..db6fa52 100644 --- a/src/main/java/de/extio/game_engine/renderer/g2d/control/components/CustomSwitch.java +++ b/src/main/java/de/extio/game_engine/renderer/g2d/control/components/CustomSwitch.java @@ -6,77 +6,91 @@ import java.awt.Graphics2D; import java.awt.event.ActionListener; 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") public class CustomSwitch extends CustomAbstractButton { 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); + this.themeManager = themeManager; } @Override public void paint(final Graphics g) { + if (this.themeManager == null) { + return; + } + final var g2d = (Graphics2D) g; + final Theme theme = this.themeManager.getCurrentTheme(); final var dimY = this.getHeight() - 1; final var dimX = (int) (dimY * 1.5); float h, s, b; - if ((this.state & STATE_TOGGLED) == 0) { - h = ComponentRenderingSupport.HSB_COMPONENT_BGR.h(); - s = ComponentRenderingSupport.HSB_COMPONENT_BGR.s(); - b = ComponentRenderingSupport.HSB_COMPONENT_BGR.b(); - } - else { - h = ComponentRenderingSupport.HSB_COMPONENT_SELECTED_0.b(); - s = ComponentRenderingSupport.HSB_COMPONENT_SELECTED_0.s(); - b = ComponentRenderingSupport.HSB_COMPONENT_SELECTED_0.b(); - } + final var baseColor = (this.state & STATE_TOGGLED) == 0 ? theme.getBackgroundNormal() : theme.getBackgroundSelected(); + h = baseColor.getHue(); + s = baseColor.getSaturation(); + b = baseColor.getBrightness(); + if ((this.state & STATE_PRESSED) != 0) { - b += 0.40F; + b += theme.getPressedBrightnessAdjustment(); } 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); Color border0Color; Color border1Color; + 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; } else { - border0Color = ComponentRenderingSupport.COLOR_COMPONENT_BORDER0; - border1Color = this.isEnabled() ? ComponentRenderingSupport.COLOR_COMPONENT_BORDER1 : ComponentRenderingSupport.COLOR_COMPONENT_BORDER1_DIS; + border0Color = theme.getBorderOuter().toColor(); + border1Color = this.isEnabled() ? theme.getBorderInner().toColor() : theme.getBorderInnerDisabled().toColor(); } if ((this.state & STATE_TOGGLED) == 0) { g2d.setColor(bodyColor); 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); - ComponentRenderingSupport.drawDecorativeBorder(g2d, 8, 6, (int) (dimX / 1.5) - 10, this.getHeight() - 13, 2, border1Color); + final var patternRenderer = this.themeManager.getPatternRenderer(theme.getPatternRendererName()); + 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 { g2d.setColor(bodyColor); 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); - ComponentRenderingSupport.drawDecorativeBorder(g2d, dimX - (int) (dimX / 1.5) - 1, 6, (int) (dimX / 1.5) - 10, this.getHeight() - 13, 2, border1Color); + final var patternRenderer = this.themeManager.getPatternRenderer(theme.getPatternRendererName()); + 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) { g2d.setColor(Color.WHITE); } else if (this.isEnabled()) { - g2d.setColor(Color.LIGHT_GRAY); + g2d.setColor(theme.getTextNormal().toColor()); } else { - g2d.setColor(Color.GRAY); + g2d.setColor(theme.getTextDisabled().toColor()); } + final var textDim = G2DDrawFont.getTextDimensions(this.caption, g2d, this.fontSize, this.scaleFactor); G2DDrawFont.renderText(g2d, textDim, @@ -87,8 +101,16 @@ public class CustomSwitch extends CustomAbstractButton { this.caption); 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); - 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 borderColor1 = (this.state & STATE_HOVERED) != 0 ? theme.getSelectionPrimary().toColor() : theme.getBorderOuter().toColor(); + 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); + } } } diff --git a/src/main/java/de/extio/game_engine/renderer/g2d/control/components/CustomWindowCloseButton.java b/src/main/java/de/extio/game_engine/renderer/g2d/control/components/CustomWindowCloseButton.java index 12e1e3a..95098dd 100644 --- a/src/main/java/de/extio/game_engine/renderer/g2d/control/components/CustomWindowCloseButton.java +++ b/src/main/java/de/extio/game_engine/renderer/g2d/control/components/CustomWindowCloseButton.java @@ -1,55 +1,33 @@ package de.extio.game_engine.renderer.g2d.control.components; -import java.awt.Color; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.event.ActionListener; -import de.extio.game_engine.renderer.g2d.bo.rendering.G2DDrawFont; -import de.extio.game_engine.renderer.model.color.RgbaColor; +import de.extio.game_engine.renderer.g2d.theme.G2DThemeManager; @SuppressWarnings("serial") 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); + this.themeManager = themeManager; } @Override public void paint(final Graphics g) { + if (this.themeManager == null) { + return; + } + 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; - final var pressed = (this.state & STATE_PRESSED) != 0; - - 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"); + if (patternRenderer != null) { + patternRenderer.drawCloseButton(g2d, this.getWidth(), this.getHeight(), this.isEnabled(), this.state, this.backgroundColor, this.scaleFactor, theme); } } diff --git a/src/main/java/de/extio/game_engine/renderer/g2d/control/components/CustomWindowPanel.java b/src/main/java/de/extio/game_engine/renderer/g2d/control/components/CustomWindowPanel.java index c68aa82..7f1ca35 100644 --- a/src/main/java/de/extio/game_engine/renderer/g2d/control/components/CustomWindowPanel.java +++ b/src/main/java/de/extio/game_engine/renderer/g2d/control/components/CustomWindowPanel.java @@ -11,14 +11,12 @@ import java.awt.event.MouseWheelEvent; import java.awt.event.MouseWheelListener; import de.extio.game_engine.renderer.g2d.G2DMainFrame; -import de.extio.game_engine.renderer.model.color.ImmutableRgbaColor; -import de.extio.game_engine.renderer.model.color.RgbaColor; +import de.extio.game_engine.renderer.g2d.theme.Theme; +import de.extio.game_engine.renderer.g2d.theme.G2DThemeManager; import de.extio.game_engine.spatial2.model.ImmutableCoordI2; 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 Color color; @@ -27,6 +25,8 @@ public class CustomWindowPanel extends Component { protected boolean dirty = true; + private final G2DThemeManager themeManager; + public void setThickBorder(final boolean thickBorder) { this.thickBorder = thickBorder; } @@ -47,7 +47,8 @@ public class CustomWindowPanel extends Component { this.dirty = dirty; } - public CustomWindowPanel() { + public CustomWindowPanel(final G2DThemeManager themeManager) { + this.themeManager = themeManager; this.setIgnoreRepaint(true); this.addMouseListener(new MouseAdapter() { @@ -103,24 +104,26 @@ public class CustomWindowPanel extends Component { @Override public void paint(final Graphics g) { - final var g2d = (Graphics2D) g; - - 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); + if (this.themeManager == null) { + return; } - ComponentRenderingSupport.drawDecorativeBorder(g2d, 0, 0, this.getWidth(), this.getHeight(), strength, rgbaDarker); - ComponentRenderingSupport.drawDecorativeBorderFilled(g2d, strength, strength, this.getWidth() - strength * 2, this.getHeight() - strength * 2, strength, cMain); + final var g2d = (Graphics2D) g; + 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); + } } } diff --git a/src/main/java/de/extio/game_engine/renderer/g2d/control/impl/G2DButtonControlImpl.java b/src/main/java/de/extio/game_engine/renderer/g2d/control/impl/G2DButtonControlImpl.java index 8b165f3..5eafacf 100644 --- a/src/main/java/de/extio/game_engine/renderer/g2d/control/impl/G2DButtonControlImpl.java +++ b/src/main/java/de/extio/game_engine/renderer/g2d/control/impl/G2DButtonControlImpl.java @@ -71,7 +71,7 @@ public class G2DButtonControlImpl extends G2DBaseControlImpl implements ButtonCo } 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); } diff --git a/src/main/java/de/extio/game_engine/renderer/g2d/control/impl/G2DLabelControlImpl.java b/src/main/java/de/extio/game_engine/renderer/g2d/control/impl/G2DLabelControlImpl.java index 4c19218..e8f6dca 100644 --- a/src/main/java/de/extio/game_engine/renderer/g2d/control/impl/G2DLabelControlImpl.java +++ b/src/main/java/de/extio/game_engine/renderer/g2d/control/impl/G2DLabelControlImpl.java @@ -98,7 +98,7 @@ public class G2DLabelControlImpl extends G2DBaseControlImpl implements LabelCont } 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); } diff --git a/src/main/java/de/extio/game_engine/renderer/g2d/control/impl/G2DSliderControlImpl.java b/src/main/java/de/extio/game_engine/renderer/g2d/control/impl/G2DSliderControlImpl.java index 04092b7..5281035 100644 --- a/src/main/java/de/extio/game_engine/renderer/g2d/control/impl/G2DSliderControlImpl.java +++ b/src/main/java/de/extio/game_engine/renderer/g2d/control/impl/G2DSliderControlImpl.java @@ -136,7 +136,7 @@ public class G2DSliderControlImpl extends G2DBaseControlImpl implements SliderCo if (this.enabled) { this.performAction(); } - }); + }, ((G2DRenderer) this.rendererData.getRenderer()).getThemeManager()); this.control.setName(this.id); } diff --git a/src/main/java/de/extio/game_engine/renderer/g2d/control/impl/G2DSwitchControlImpl.java b/src/main/java/de/extio/game_engine/renderer/g2d/control/impl/G2DSwitchControlImpl.java index 0910c32..05b98cd 100644 --- a/src/main/java/de/extio/game_engine/renderer/g2d/control/impl/G2DSwitchControlImpl.java +++ b/src/main/java/de/extio/game_engine/renderer/g2d/control/impl/G2DSwitchControlImpl.java @@ -1,12 +1,13 @@ 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; public class G2DSwitchControlImpl extends G2DAbstractToggleButtonControlImpl { @Override 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); } diff --git a/src/main/java/de/extio/game_engine/renderer/g2d/control/impl/G2DTableControlImpl.java b/src/main/java/de/extio/game_engine/renderer/g2d/control/impl/G2DTableControlImpl.java index b97d541..c58de4b 100644 --- a/src/main/java/de/extio/game_engine/renderer/g2d/control/impl/G2DTableControlImpl.java +++ b/src/main/java/de/extio/game_engine/renderer/g2d/control/impl/G2DTableControlImpl.java @@ -18,8 +18,8 @@ import javax.swing.table.DefaultTableCellRenderer; import de.extio.game_engine.renderer.RendererData; 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.control.components.ComponentRenderingSupport; 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.TableControl; 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.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); if (this.dataModified || this.scrollPositionModified) { diff --git a/src/main/java/de/extio/game_engine/renderer/g2d/control/impl/G2DTextfieldControlImpl.java b/src/main/java/de/extio/game_engine/renderer/g2d/control/impl/G2DTextfieldControlImpl.java index 6c67cd9..4edc642 100644 --- a/src/main/java/de/extio/game_engine/renderer/g2d/control/impl/G2DTextfieldControlImpl.java +++ b/src/main/java/de/extio/game_engine/renderer/g2d/control/impl/G2DTextfieldControlImpl.java @@ -6,9 +6,9 @@ import java.awt.TextComponent; import java.awt.TextField; 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.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.CustomTextField; import de.extio.game_engine.renderer.model.bo.ControlRenderingBo.BaseControl; @@ -119,7 +119,9 @@ public class G2DTextfieldControlImpl extends G2DBaseControlImpl implements Textf } 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.setLocation(this.x, this.y)); this.safeInvoke(() -> this.textComponent.setSize(this.width, this.height)); diff --git a/src/main/java/de/extio/game_engine/renderer/g2d/control/impl/G2DTextfieldControlImpl2.java b/src/main/java/de/extio/game_engine/renderer/g2d/control/impl/G2DTextfieldControlImpl2.java index 401ddf4..93bee45 100644 --- a/src/main/java/de/extio/game_engine/renderer/g2d/control/impl/G2DTextfieldControlImpl2.java +++ b/src/main/java/de/extio/game_engine/renderer/g2d/control/impl/G2DTextfieldControlImpl2.java @@ -9,9 +9,9 @@ import javax.swing.JTextField; import javax.swing.text.JTextComponent; 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.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.CustomJTextField; import de.extio.game_engine.renderer.model.bo.ControlRenderingBo.BaseControl; @@ -227,7 +227,14 @@ public class G2DTextfieldControlImpl2 extends G2DBaseControlImpl implements Text } 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.setFont(G2DDrawFont.getFont(this.scaleFactor, this.fontSize))); if (this.multiLine) { diff --git a/src/main/java/de/extio/game_engine/renderer/g2d/control/impl/G2DToggleButtonControlImpl.java b/src/main/java/de/extio/game_engine/renderer/g2d/control/impl/G2DToggleButtonControlImpl.java index 6f890f0..24a3d68 100644 --- a/src/main/java/de/extio/game_engine/renderer/g2d/control/impl/G2DToggleButtonControlImpl.java +++ b/src/main/java/de/extio/game_engine/renderer/g2d/control/impl/G2DToggleButtonControlImpl.java @@ -1,12 +1,13 @@ 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; public class G2DToggleButtonControlImpl extends G2DAbstractToggleButtonControlImpl { @Override 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); } diff --git a/src/main/java/de/extio/game_engine/renderer/g2d/control/impl/G2DWindowCloseButtonControlImpl.java b/src/main/java/de/extio/game_engine/renderer/g2d/control/impl/G2DWindowCloseButtonControlImpl.java index 21ded30..930edbb 100644 --- a/src/main/java/de/extio/game_engine/renderer/g2d/control/impl/G2DWindowCloseButtonControlImpl.java +++ b/src/main/java/de/extio/game_engine/renderer/g2d/control/impl/G2DWindowCloseButtonControlImpl.java @@ -1,5 +1,6 @@ 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.model.bo.ControlRenderingBo.WindowCloseButtonControl; @@ -11,7 +12,7 @@ public class G2DWindowCloseButtonControlImpl extends G2DButtonControlImpl implem @Override 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); } diff --git a/src/main/java/de/extio/game_engine/renderer/g2d/control/impl/G2DWindowPanelControlImpl.java b/src/main/java/de/extio/game_engine/renderer/g2d/control/impl/G2DWindowPanelControlImpl.java index 45cf413..cde1f34 100644 --- a/src/main/java/de/extio/game_engine/renderer/g2d/control/impl/G2DWindowPanelControlImpl.java +++ b/src/main/java/de/extio/game_engine/renderer/g2d/control/impl/G2DWindowPanelControlImpl.java @@ -77,7 +77,7 @@ public class G2DWindowPanelControlImpl extends G2DBaseControlImpl implements Win } protected void createControl() { - this.control = new CustomWindowPanel(); + this.control = new CustomWindowPanel(((G2DRenderer) this.rendererData.getRenderer()).getThemeManager()); this.control.setName(this.id); } diff --git a/src/main/java/de/extio/game_engine/renderer/g2d/theme/ComponentState.java b/src/main/java/de/extio/game_engine/renderer/g2d/theme/ComponentState.java new file mode 100644 index 0000000..6ff709f --- /dev/null +++ b/src/main/java/de/extio/game_engine/renderer/g2d/theme/ComponentState.java @@ -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 +} diff --git a/src/main/java/de/extio/game_engine/renderer/g2d/theme/G2DThemeManager.java b/src/main/java/de/extio/game_engine/renderer/g2d/theme/G2DThemeManager.java new file mode 100644 index 0000000..a408905 --- /dev/null +++ b/src/main/java/de/extio/game_engine/renderer/g2d/theme/G2DThemeManager.java @@ -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 patternRenderers; + private final Map themes; + private final Theme defaultTheme; + + private Theme currentTheme; + + public G2DThemeManager( + final StaticResourceService staticResourceService, + final List patternRendererList, + final Map 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 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; + } +} diff --git a/src/main/java/de/extio/game_engine/renderer/g2d/theme/HSBColor.java b/src/main/java/de/extio/game_engine/renderer/g2d/theme/HSBColor.java new file mode 100644 index 0000000..d3d0cc2 --- /dev/null +++ b/src/main/java/de/extio/game_engine/renderer/g2d/theme/HSBColor.java @@ -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); + } +} diff --git a/src/main/java/de/extio/game_engine/renderer/g2d/theme/PatternRenderer.java b/src/main/java/de/extio/game_engine/renderer/g2d/theme/PatternRenderer.java new file mode 100644 index 0000000..7dafa0d --- /dev/null +++ b/src/main/java/de/extio/game_engine/renderer/g2d/theme/PatternRenderer.java @@ -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); +} diff --git a/src/main/java/de/extio/game_engine/renderer/g2d/theme/SpacecraftPatternRenderer.java b/src/main/java/de/extio/game_engine/renderer/g2d/theme/SpacecraftPatternRenderer.java new file mode 100644 index 0000000..39ce609 --- /dev/null +++ b/src/main/java/de/extio/game_engine/renderer/g2d/theme/SpacecraftPatternRenderer.java @@ -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"); + } + } +} diff --git a/src/main/java/de/extio/game_engine/renderer/g2d/theme/SpacecraftThemeFactoryBean.java b/src/main/java/de/extio/game_engine/renderer/g2d/theme/SpacecraftThemeFactoryBean.java new file mode 100644 index 0000000..6d3c6f4 --- /dev/null +++ b/src/main/java/de/extio/game_engine/renderer/g2d/theme/SpacecraftThemeFactoryBean.java @@ -0,0 +1,36 @@ +package de.extio.game_engine.renderer.g2d.theme; + +import org.springframework.beans.factory.FactoryBean; + +public class SpacecraftThemeFactoryBean implements FactoryBean { + + @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; + } +} diff --git a/src/main/java/de/extio/game_engine/renderer/g2d/theme/Theme.java b/src/main/java/de/extio/game_engine/renderer/g2d/theme/Theme.java new file mode 100644 index 0000000..672bd3e --- /dev/null +++ b/src/main/java/de/extio/game_engine/renderer/g2d/theme/Theme.java @@ -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); + } + } +} diff --git a/src/main/java/de/extio/game_engine/renderer/g2d/theme/VintagePatternRenderer.java b/src/main/java/de/extio/game_engine/renderer/g2d/theme/VintagePatternRenderer.java new file mode 100644 index 0000000..ff03fe4 --- /dev/null +++ b/src/main/java/de/extio/game_engine/renderer/g2d/theme/VintagePatternRenderer.java @@ -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); + } + } +} diff --git a/src/main/java/de/extio/game_engine/renderer/g2d/theme/VintageThemeFactoryBean.java b/src/main/java/de/extio/game_engine/renderer/g2d/theme/VintageThemeFactoryBean.java new file mode 100644 index 0000000..33a2a0c --- /dev/null +++ b/src/main/java/de/extio/game_engine/renderer/g2d/theme/VintageThemeFactoryBean.java @@ -0,0 +1,36 @@ +package de.extio.game_engine.renderer.g2d.theme; + +import org.springframework.beans.factory.FactoryBean; + +public class VintageThemeFactoryBean implements FactoryBean { + + @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; + } +} diff --git a/src/main/java/de/extio/game_engine/renderer/model/bo/DrawWindowRenderingBo.java b/src/main/java/de/extio/game_engine/renderer/model/bo/DrawWindowRenderingBo.java deleted file mode 100644 index d49d936..0000000 --- a/src/main/java/de/extio/game_engine/renderer/model/bo/DrawWindowRenderingBo.java +++ /dev/null @@ -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); - -} diff --git a/src/test/java/de/extio/game_engine/demo/DemoModule.java b/src/test/java/de/extio/game_engine/demo/DemoModule.java index 62cc27f..bfb07cf 100644 --- a/src/test/java/de/extio/game_engine/demo/DemoModule.java +++ b/src/test/java/de/extio/game_engine/demo/DemoModule.java @@ -1,6 +1,8 @@ package de.extio.game_engine.demo; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; 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.module.AbstractClientModule; 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.model.RenderingBoLayer; import de.extio.game_engine.renderer.model.bo.ControlRenderingBo; @@ -38,10 +41,17 @@ public class DemoModule extends AbstractClientModule { @Autowired private LocalizationService localizationService; + + @Autowired + private ThemeManager themeManager; private Window mainWindow; private Window secondaryWindow; + + private Window themeSelectionWindow; + + private final Map themeSelectionByControlId = new HashMap<>(); @Override public void onLoad() { @@ -56,6 +66,12 @@ public class DemoModule extends AbstractClientModule { this.secondaryWindow.setDraggable(true); this.secondaryWindow.setCloseButton(true); + this.themeSelectionWindow = this.applicationContext.getBean(Window.class); + this.themeSelectionWindow.setNormalizedPosition(RendererControl.REFERENCE_RESOLUTION.divide(7).multiply(3)); + this.themeSelectionWindow.setNormalizedDimension(RendererControl.REFERENCE_RESOLUTION.divide(7).multiply(2)); + this.themeSelectionWindow.setDraggable(true); + this.themeSelectionWindow.setCloseButton(true); + this.getModuleService().changeActiveState(this.getId(), true); this.getModuleService().changeDisplayState(this.getId(), true); } @@ -64,10 +80,16 @@ public class DemoModule extends AbstractClientModule { public void onUnload() { this.getModuleService().unloadModule(this.mainWindow.getId()); this.getModuleService().unloadModule(this.secondaryWindow.getId()); + this.getModuleService().unloadModule(this.themeSelectionWindow.getId()); } @Override 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.getModuleService().changeActiveState(this.mainWindow.getId(), true); this.eventService.register(UiControlEvent.class, this.getId(), this::onUiControlEvent); @@ -77,6 +99,7 @@ public class DemoModule extends AbstractClientModule { public void onDeactivate() { this.getModuleService().changeActiveState(this.mainWindow.getId(), false); this.getModuleService().changeActiveState(this.secondaryWindow.getId(), false); + this.getModuleService().changeActiveState(this.themeSelectionWindow.getId(), false); } @Override @@ -89,6 +112,7 @@ public class DemoModule extends AbstractClientModule { public void onHide() { this.getModuleService().changeDisplayState(this.mainWindow.getId(), false); this.getModuleService().changeActiveState(this.secondaryWindow.getId(), false); + this.getModuleService().changeActiveState(this.themeSelectionWindow.getId(), false); this.audioController.stopMusic(); } @@ -96,19 +120,31 @@ public class DemoModule extends AbstractClientModule { switch (event.getId()) { case "DemoModule_MainWindow_Label_Welcome" -> { 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.getModuleService().changeActiveState(this.secondaryWindow.getId(), true); this.getModuleService().changeDisplayState(this.secondaryWindow.getId(), true); } + + case "DemoModule_MainWindow_Label_Start" -> { + 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"), "alert0.ogg")); + this.audioController.play(new StaticResource(List.of("audio"), "alert2.ogg")); 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() { + this.secondaryWindow.clearRenderingBos(); + this.themeSelectionByControlId.clear(); + var bo = this.renderingBoPool.acquire("DemoModule_SecondaryWindow_Text", DrawFontRenderingBo.class) .setText(this.localizationService.translate("test-5").replace("\\n", "\n")) .setSize(32) @@ -159,5 +198,68 @@ public class DemoModule extends AbstractClientModule { .withDimensionAbsolute(160, 60) .withPositionRelative(RendererControl.REFERENCE_RESOLUTION.divide(7).getX() - 80, 200); 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]+", "_"); } }