feat(theme): Introduce theming system with pattern renderers

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

View File

@@ -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<String, String> 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]+", "_");
}
}