Enhance menu options configuration for improved modularity and flexibility

This commit is contained in:
extio-de
2026-06-13 19:09:22 +02:00
parent fbb1fb6ea6
commit b510b41698
5 changed files with 76 additions and 55 deletions

View File

@@ -1146,24 +1146,36 @@ Framework for building in-game menus and UI screens as modules. Provides reusabl
**Configuration Class**: `MenuAutoConfiguration` **Configuration Class**: `MenuAutoConfiguration`
**Properties**: **Properties**:
- `game-engine.menu.enabled` (default: `true`) - `game-engine.menu.enabled` (default: `true`) — Master switch for the menu subsystem
- `game-engine.menu.options.enabled` (default: `true`) - `game-engine.menu.options.enabled` (default: `true`) — Enables the `OptionsModule`
- `game-engine.menu.theme-editor.enabled` (default: `true`) - `game-engine.menu.options.audio.enabled` (default: `true`) — Enables the audio options tab (also requires `game-engine.audio.enabled`)
- `game-engine.menu.hsb-color-picker.enabled` (default: `true`) - `game-engine.menu.options.video.enabled` (default: `true`) — Enables the video options tab
- `game-engine.menu.options.video.scaleFactor.enabled` (default: `true`) - Enables changing the global scale factor in video options tab
- `game-engine.menu.options.keyboard.enabled` (default: `true`) — Enables the keyboard options tab (also requires `game-engine.keycode-registry.enabled`)
- `game-engine.menu.options.themes.enabled` (default: `true`) — Enables the themes tab; also required by `HsbColorPickerModule` and `ThemeEditorModule`
- `game-engine.menu.theme-editor.enabled` (default: `true`) — Enables the theme editor
- `game-engine.menu.hsb-color-picker.enabled` (default: `true`) — Enables the HSB color picker
The menu subsystem is auto-configured through explicit bean definitions, matching the rest of the engine. The options dialog auto-discovers all `OptionsTab` beans, so applications can contribute additional tabs from their own codebase without modifying the library. The theme editor implements `OptionsThemeEditorSupport`, and the reusable themes tab will use it when present. The menu subsystem requires the renderer to be enabled (`game-engine.renderer.enabled`). Each options tab can be independently toggled via its own property, replacing the previous single `game-engine.menu.options.enabled` gate.
`ThemeEditorModule` depends on `HsbColorPickerModule`, so disabling `game-engine.menu.hsb-color-picker.enabled` also prevents theme editor registration. The options dialog auto-discovers all `OptionsTab` beans, so applications can contribute additional tabs from their own codebase without modifying the library. The theme editor implements `OptionsThemeEditorSupport`, and the reusable themes tab will use it when present.
`ThemeEditorModule` depends on both `HsbColorPickerModule` and the themes tab (`game-engine.menu.options.themes.enabled`). `HsbColorPickerModule` also requires the themes tab to be enabled.
#### Exposed Spring Beans #### Exposed Spring Beans
- `OptionsModule` when `game-engine.menu.options.enabled=true` - `OptionsModule` when `game-engine.menu.options.enabled=true`
- `OptionsAudioTab`, `OptionsVideoTab`, `OptionsKeyboardTab`, and `OptionsThemesTab` when `game-engine.menu.options.enabled=true` - `OptionsAudioTab` when `game-engine.menu.options.audio.enabled=true` and `game-engine.audio.enabled=true`
- `ThemeEditorModule` when `game-engine.menu.theme-editor.enabled=true` - `OptionsVideoTab` when `game-engine.menu.options.video.enabled=true`
- `HsbColorPickerModule` when `game-engine.menu.hsb-color-picker.enabled=true` - `OptionsKeyboardTab` when `game-engine.menu.options.keyboard.enabled=true` and `game-engine.keycode-registry.enabled=true`
- `OptionsThemesTab` when `game-engine.menu.options.themes.enabled=true`
- `HsbColorPickerModule` when `game-engine.menu.options.themes.enabled=true` and `game-engine.menu.hsb-color-picker.enabled=true`
- `ThemeEditorModule` when `game-engine.menu.options.themes.enabled=true`, `game-engine.menu.theme-editor.enabled=true`, and `game-engine.menu.hsb-color-picker.enabled=true`
#### Integration Notes #### Integration Notes
All menu implementations are still regular `AbstractClientModule` modules at runtime. The auto-configuration only controls bean registration. All menu implementations are still regular `AbstractClientModule` modules at runtime. The auto-configuration only controls bean registration.
`AbstractOptionsTab` injects `AudioControl` and `KeycodeRegistry` as optional (`@Autowired(required = false)`), so subclasses are safe to use even when audio or keyboard subsystems are disabled — the corresponding `keycodeRegistry()` and `audioControl()` accessors return `null` in that case.
--- ---
### Utilities ### Utilities

View File

@@ -31,7 +31,7 @@ public abstract class AbstractOptionsTab {
@Autowired @Autowired
private RendererControl rendererControl; private RendererControl rendererControl;
@Autowired @Autowired(required = false)
private AudioControl audioControl; private AudioControl audioControl;
@Autowired @Autowired
@@ -40,7 +40,7 @@ public abstract class AbstractOptionsTab {
@Autowired @Autowired
private StorageService storageService; private StorageService storageService;
@Autowired @Autowired(required = false)
private KeycodeRegistry keycodeRegistry; private KeycodeRegistry keycodeRegistry;
protected final ApplicationContext applicationContext() { protected final ApplicationContext applicationContext() {

View File

@@ -1,5 +1,6 @@
package de.extio.game_engine.menu; package de.extio.game_engine.menu;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@@ -12,6 +13,7 @@ import de.extio.game_engine.renderer.work.RenderingBoPool;
@AutoConfiguration @AutoConfiguration
@ConditionalOnProperty(name = "game-engine.menu.enabled", havingValue = "true", matchIfMissing = true) @ConditionalOnProperty(name = "game-engine.menu.enabled", havingValue = "true", matchIfMissing = true)
@ConditionalOnProperty(name = "game-engine.renderer.enabled", havingValue = "true", matchIfMissing = true)
public class MenuAutoConfiguration { public class MenuAutoConfiguration {
@Bean @Bean
@@ -24,34 +26,37 @@ public class MenuAutoConfiguration {
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
@ConditionalOnProperty(name = "game-engine.menu.options.enabled", havingValue = "true", matchIfMissing = true) @ConditionalOnProperty(name = "game-engine.menu.options.audio.enabled", havingValue = "true", matchIfMissing = true)
@ConditionalOnProperty(name = "game-engine.audio.enabled", havingValue = "true", matchIfMissing = true)
OptionsAudioTab optionsAudioTab() { OptionsAudioTab optionsAudioTab() {
return new OptionsAudioTab(); return new OptionsAudioTab();
} }
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
@ConditionalOnProperty(name = "game-engine.menu.options.enabled", havingValue = "true", matchIfMissing = true) @ConditionalOnProperty(name = "game-engine.menu.options.video.enabled", havingValue = "true", matchIfMissing = true)
OptionsVideoTab optionsVideoTab() { OptionsVideoTab optionsVideoTab(@Value("${game-engine.menu.options.video.scaleFactor.enabled:true}") final boolean scaleFactorEnabled) {
return new OptionsVideoTab(); return new OptionsVideoTab(scaleFactorEnabled);
} }
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
@ConditionalOnProperty(name = "game-engine.menu.options.enabled", havingValue = "true", matchIfMissing = true) @ConditionalOnProperty(name = "game-engine.menu.options.keyboard.enabled", havingValue = "true", matchIfMissing = true)
@ConditionalOnProperty(name = "game-engine.keycode-registry.enabled", havingValue = "true", matchIfMissing = true)
OptionsKeyboardTab optionsKeyboardTab() { OptionsKeyboardTab optionsKeyboardTab() {
return new OptionsKeyboardTab(); return new OptionsKeyboardTab();
} }
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
@ConditionalOnProperty(name = "game-engine.menu.options.enabled", havingValue = "true", matchIfMissing = true) @ConditionalOnProperty(name = "game-engine.menu.options.themes.enabled", havingValue = "true", matchIfMissing = true)
OptionsThemesTab optionsThemesTab() { OptionsThemesTab optionsThemesTab() {
return new OptionsThemesTab(); return new OptionsThemesTab();
} }
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
@ConditionalOnProperty(name = "game-engine.menu.options.themes.enabled", havingValue = "true", matchIfMissing = true)
@ConditionalOnProperty(name = "game-engine.menu.hsb-color-picker.enabled", havingValue = "true", matchIfMissing = true) @ConditionalOnProperty(name = "game-engine.menu.hsb-color-picker.enabled", havingValue = "true", matchIfMissing = true)
HsbColorPickerModule hsbColorPickerModule() { HsbColorPickerModule hsbColorPickerModule() {
return new HsbColorPickerModule(); return new HsbColorPickerModule();
@@ -59,6 +64,7 @@ public class MenuAutoConfiguration {
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
@ConditionalOnProperty(name = "game-engine.menu.options.themes.enabled", havingValue = "true", matchIfMissing = true)
@ConditionalOnProperty(name = "game-engine.menu.theme-editor.enabled", havingValue = "true", matchIfMissing = true) @ConditionalOnProperty(name = "game-engine.menu.theme-editor.enabled", havingValue = "true", matchIfMissing = true)
@ConditionalOnProperty(name = "game-engine.menu.hsb-color-picker.enabled", havingValue = "true", matchIfMissing = true) @ConditionalOnProperty(name = "game-engine.menu.hsb-color-picker.enabled", havingValue = "true", matchIfMissing = true)
ThemeEditorModule themeEditorModule() { ThemeEditorModule themeEditorModule() {

View File

@@ -37,11 +37,6 @@ final class OptionsKeyboardTab extends AbstractOptionsTab implements OptionsTab
return 50; return 50;
} }
@Override
public boolean visibleInNavigation() {
return false;
}
@Override @Override
public void render(final OptionsModuleContext context) { public void render(final OptionsModuleContext context) {
final var keycodeRegistrations = this.keycodeRegistry().getAll(); final var keycodeRegistrations = this.keycodeRegistry().getAll();

View File

@@ -22,6 +22,12 @@ final class OptionsVideoTab extends AbstractOptionsTab implements OptionsTab {
private final Map<String, Integer> screenSelectionByControlId = new HashMap<>(); private final Map<String, Integer> screenSelectionByControlId = new HashMap<>();
private final Map<String, Double> scaleFactorSelectionByControlId = new HashMap<>(); private final Map<String, Double> scaleFactorSelectionByControlId = new HashMap<>();
private final boolean scaleFactorOptionEnabled;
public OptionsVideoTab(final boolean scaleFactorOptionEnabled) {
this.scaleFactorOptionEnabled = scaleFactorOptionEnabled;
}
@Override @Override
public String tabId() { public String tabId() {
return TAB_ID; return TAB_ID;
@@ -146,6 +152,7 @@ final class OptionsVideoTab extends AbstractOptionsTab implements OptionsTab {
yPos += 100; yPos += 100;
if (scaleFactorOptionEnabled) {
bo = this.renderingBoPool().acquire("OptionsModule_Video_Label_ScaleFactor", ControlRenderingBo.class) bo = this.renderingBoPool().acquire("OptionsModule_Video_Label_ScaleFactor", ControlRenderingBo.class)
.setCaption(this.localizationService().translate("ecyoa-95", "Scale Factor:")) .setCaption(this.localizationService().translate("ecyoa-95", "Scale Factor:"))
.setFontSize(48) .setFontSize(48)
@@ -183,6 +190,7 @@ final class OptionsVideoTab extends AbstractOptionsTab implements OptionsTab {
} }
yPos += 100; yPos += 100;
}
bo = this.renderingBoPool().acquire("OptionsModule_Video_Label_Framerate", ControlRenderingBo.class) bo = this.renderingBoPool().acquire("OptionsModule_Video_Label_Framerate", ControlRenderingBo.class)
.setCaption(this.localizationService().translate("ecyoa-96", "Target Framerate:")) .setCaption(this.localizationService().translate("ecyoa-96", "Target Framerate:"))