diff --git a/README.md b/README.md index 10cd14c..2b80fc6 100644 --- a/README.md +++ b/README.md @@ -1146,24 +1146,36 @@ Framework for building in-game menus and UI screens as modules. Provides reusabl **Configuration Class**: `MenuAutoConfiguration` **Properties**: -- `game-engine.menu.enabled` (default: `true`) -- `game-engine.menu.options.enabled` (default: `true`) -- `game-engine.menu.theme-editor.enabled` (default: `true`) -- `game-engine.menu.hsb-color-picker.enabled` (default: `true`) +- `game-engine.menu.enabled` (default: `true`) — Master switch for the menu subsystem +- `game-engine.menu.options.enabled` (default: `true`) — Enables the `OptionsModule` +- `game-engine.menu.options.audio.enabled` (default: `true`) — Enables the audio options tab (also requires `game-engine.audio.enabled`) +- `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 - `OptionsModule` when `game-engine.menu.options.enabled=true` -- `OptionsAudioTab`, `OptionsVideoTab`, `OptionsKeyboardTab`, and `OptionsThemesTab` when `game-engine.menu.options.enabled=true` -- `ThemeEditorModule` when `game-engine.menu.theme-editor.enabled=true` -- `HsbColorPickerModule` when `game-engine.menu.hsb-color-picker.enabled=true` +- `OptionsAudioTab` when `game-engine.menu.options.audio.enabled=true` and `game-engine.audio.enabled=true` +- `OptionsVideoTab` when `game-engine.menu.options.video.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 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 diff --git a/src/main/java/de/extio/game_engine/menu/AbstractOptionsTab.java b/src/main/java/de/extio/game_engine/menu/AbstractOptionsTab.java index 9c2077a..dfbf79f 100644 --- a/src/main/java/de/extio/game_engine/menu/AbstractOptionsTab.java +++ b/src/main/java/de/extio/game_engine/menu/AbstractOptionsTab.java @@ -31,7 +31,7 @@ public abstract class AbstractOptionsTab { @Autowired private RendererControl rendererControl; - @Autowired + @Autowired(required = false) private AudioControl audioControl; @Autowired @@ -40,7 +40,7 @@ public abstract class AbstractOptionsTab { @Autowired private StorageService storageService; - @Autowired + @Autowired(required = false) private KeycodeRegistry keycodeRegistry; protected final ApplicationContext applicationContext() { diff --git a/src/main/java/de/extio/game_engine/menu/MenuAutoConfiguration.java b/src/main/java/de/extio/game_engine/menu/MenuAutoConfiguration.java index da03af5..1f8e8ea 100644 --- a/src/main/java/de/extio/game_engine/menu/MenuAutoConfiguration.java +++ b/src/main/java/de/extio/game_engine/menu/MenuAutoConfiguration.java @@ -1,5 +1,6 @@ package de.extio.game_engine.menu; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -12,6 +13,7 @@ import de.extio.game_engine.renderer.work.RenderingBoPool; @AutoConfiguration @ConditionalOnProperty(name = "game-engine.menu.enabled", havingValue = "true", matchIfMissing = true) +@ConditionalOnProperty(name = "game-engine.renderer.enabled", havingValue = "true", matchIfMissing = true) public class MenuAutoConfiguration { @Bean @@ -24,34 +26,37 @@ public class MenuAutoConfiguration { @Bean @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() { return new OptionsAudioTab(); } @Bean @ConditionalOnMissingBean - @ConditionalOnProperty(name = "game-engine.menu.options.enabled", havingValue = "true", matchIfMissing = true) - OptionsVideoTab optionsVideoTab() { - return new OptionsVideoTab(); + @ConditionalOnProperty(name = "game-engine.menu.options.video.enabled", havingValue = "true", matchIfMissing = true) + OptionsVideoTab optionsVideoTab(@Value("${game-engine.menu.options.video.scaleFactor.enabled:true}") final boolean scaleFactorEnabled) { + return new OptionsVideoTab(scaleFactorEnabled); } @Bean @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() { return new OptionsKeyboardTab(); } @Bean @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() { return new OptionsThemesTab(); } @Bean @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) HsbColorPickerModule hsbColorPickerModule() { return new HsbColorPickerModule(); @@ -59,6 +64,7 @@ public class MenuAutoConfiguration { @Bean @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.hsb-color-picker.enabled", havingValue = "true", matchIfMissing = true) ThemeEditorModule themeEditorModule() { diff --git a/src/main/java/de/extio/game_engine/menu/OptionsKeyboardTab.java b/src/main/java/de/extio/game_engine/menu/OptionsKeyboardTab.java index 4b6d9c1..35776e1 100644 --- a/src/main/java/de/extio/game_engine/menu/OptionsKeyboardTab.java +++ b/src/main/java/de/extio/game_engine/menu/OptionsKeyboardTab.java @@ -37,11 +37,6 @@ final class OptionsKeyboardTab extends AbstractOptionsTab implements OptionsTab return 50; } - @Override - public boolean visibleInNavigation() { - return false; - } - @Override public void render(final OptionsModuleContext context) { final var keycodeRegistrations = this.keycodeRegistry().getAll(); diff --git a/src/main/java/de/extio/game_engine/menu/OptionsVideoTab.java b/src/main/java/de/extio/game_engine/menu/OptionsVideoTab.java index 4511de3..e01e8ae 100644 --- a/src/main/java/de/extio/game_engine/menu/OptionsVideoTab.java +++ b/src/main/java/de/extio/game_engine/menu/OptionsVideoTab.java @@ -22,6 +22,12 @@ final class OptionsVideoTab extends AbstractOptionsTab implements OptionsTab { private final Map screenSelectionByControlId = new HashMap<>(); private final Map scaleFactorSelectionByControlId = new HashMap<>(); + private final boolean scaleFactorOptionEnabled; + + public OptionsVideoTab(final boolean scaleFactorOptionEnabled) { + this.scaleFactorOptionEnabled = scaleFactorOptionEnabled; + } + @Override public String tabId() { return TAB_ID; @@ -146,43 +152,45 @@ final class OptionsVideoTab extends AbstractOptionsTab implements OptionsTab { yPos += 100; - bo = this.renderingBoPool().acquire("OptionsModule_Video_Label_ScaleFactor", ControlRenderingBo.class) - .setCaption(this.localizationService().translate("ecyoa-95", "Scale Factor:")) - .setFontSize(48) - .setType(LabelControl.class) - .setControlData(new LabelData(null, null, HorizontalAlignment.LEFT)) - .setVisible(true) - .setEnabled(false) - .withDimensionAbsolute(800, 80) - .withPositionRelative(10, yPos); - this.contentScrollArea(context).putRenderingBo(bo); - - yPos += 90; - - final var currentScaleFactor = this.rendererControl().getVideoOptions().getScaleFactorModifier(); - final var scaleFactors = List.of(0.75, 0.90, 1.00, 1.15, 1.25); - - var scaleXPos = 10; - for (final var scaleFactor : scaleFactors) { - final var controlId = "OptionsModule_Video_ScaleFactor_" + (int) (scaleFactor * 100); - this.scaleFactorSelectionByControlId.put(controlId, scaleFactor); - - bo = this.renderingBoPool().acquire(controlId, ControlRenderingBo.class) - .setCaption((int) (scaleFactor * 100) + "%") - .setFontSize(42) - .setType(ToggleButtonControl.class) - .setControlGroup("OptionsModule_ScaleFactorGroup") - .setControlData(new ToggleButtonData(Math.abs(currentScaleFactor - scaleFactor) < 0.01, false, null)) + if (scaleFactorOptionEnabled) { + bo = this.renderingBoPool().acquire("OptionsModule_Video_Label_ScaleFactor", ControlRenderingBo.class) + .setCaption(this.localizationService().translate("ecyoa-95", "Scale Factor:")) + .setFontSize(48) + .setType(LabelControl.class) + .setControlData(new LabelData(null, null, HorizontalAlignment.LEFT)) .setVisible(true) - .setEnabled(true) - .withDimensionAbsolute(150, 60) - .withPositionRelative(scaleXPos, yPos); + .setEnabled(false) + .withDimensionAbsolute(800, 80) + .withPositionRelative(10, yPos); this.contentScrollArea(context).putRenderingBo(bo); - scaleXPos += 160; - } + yPos += 90; - yPos += 100; + final var currentScaleFactor = this.rendererControl().getVideoOptions().getScaleFactorModifier(); + final var scaleFactors = List.of(0.75, 0.90, 1.00, 1.15, 1.25); + + var scaleXPos = 10; + for (final var scaleFactor : scaleFactors) { + final var controlId = "OptionsModule_Video_ScaleFactor_" + (int) (scaleFactor * 100); + this.scaleFactorSelectionByControlId.put(controlId, scaleFactor); + + bo = this.renderingBoPool().acquire(controlId, ControlRenderingBo.class) + .setCaption((int) (scaleFactor * 100) + "%") + .setFontSize(42) + .setType(ToggleButtonControl.class) + .setControlGroup("OptionsModule_ScaleFactorGroup") + .setControlData(new ToggleButtonData(Math.abs(currentScaleFactor - scaleFactor) < 0.01, false, null)) + .setVisible(true) + .setEnabled(true) + .withDimensionAbsolute(150, 60) + .withPositionRelative(scaleXPos, yPos); + this.contentScrollArea(context).putRenderingBo(bo); + + scaleXPos += 160; + } + + yPos += 100; + } bo = this.renderingBoPool().acquire("OptionsModule_Video_Label_Framerate", ControlRenderingBo.class) .setCaption(this.localizationService().translate("ecyoa-96", "Target Framerate:"))