feat: Implement Change View and Renderer Options menus
- Added functionality to display and navigate the Change View menu in SixelRenderer and SoftwareRenderer. - Introduced methods to draw the Change View and Renderer Options menus, including handling cursor and selection states. - Updated WolfClassicMenuArt to include a customize label for the new menu. - Enhanced WolfMenuScreen to support new menu states. - Created tests for Change View menu interactions, ensuring proper transitions and renderer settings toggling. - Implemented persistence for renderer settings in Flutter, allowing settings to be saved and loaded from a local file. Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
@@ -7,6 +7,8 @@ enum WolfMenuScreen {
|
||||
gameSelect,
|
||||
episodeSelect,
|
||||
difficultySelect,
|
||||
changeView,
|
||||
rendererOptions,
|
||||
}
|
||||
|
||||
enum WolfIntroSlide { retailWarning, pg13, title }
|
||||
@@ -40,6 +42,36 @@ class WolfMenuMainEntry {
|
||||
final bool isEnabled;
|
||||
}
|
||||
|
||||
class WolfMenuRendererEntry {
|
||||
const WolfMenuRendererEntry({
|
||||
required this.mode,
|
||||
required this.label,
|
||||
required this.hasOptions,
|
||||
this.isEnabled = true,
|
||||
this.isChecked = false,
|
||||
});
|
||||
|
||||
final WolfRendererMode mode;
|
||||
final String label;
|
||||
final bool hasOptions;
|
||||
final bool isEnabled;
|
||||
final bool isChecked;
|
||||
}
|
||||
|
||||
class WolfMenuRendererOptionEntry {
|
||||
const WolfMenuRendererOptionEntry({
|
||||
required this.id,
|
||||
required this.label,
|
||||
this.isEnabled = true,
|
||||
this.isChecked = false,
|
||||
});
|
||||
|
||||
final WolfRendererOptionId id;
|
||||
final String label;
|
||||
final bool isEnabled;
|
||||
final bool isChecked;
|
||||
}
|
||||
|
||||
bool _isWiredMainMenuAction(WolfMenuMainAction action) {
|
||||
switch (action) {
|
||||
case WolfMenuMainAction.newGame:
|
||||
@@ -48,11 +80,12 @@ bool _isWiredMainMenuAction(WolfMenuMainAction action) {
|
||||
case WolfMenuMainAction.backToDemo:
|
||||
case WolfMenuMainAction.quit:
|
||||
return true;
|
||||
case WolfMenuMainAction.changeView:
|
||||
return true;
|
||||
case WolfMenuMainAction.sound:
|
||||
case WolfMenuMainAction.control:
|
||||
case WolfMenuMainAction.loadGame:
|
||||
case WolfMenuMainAction.saveGame:
|
||||
case WolfMenuMainAction.changeView:
|
||||
case WolfMenuMainAction.readThis:
|
||||
case WolfMenuMainAction.viewScores:
|
||||
return false;
|
||||
@@ -85,6 +118,13 @@ class MenuManager {
|
||||
int _selectedGameIndex = 0;
|
||||
int _selectedEpisodeIndex = 0;
|
||||
int _selectedDifficultyIndex = 0;
|
||||
int _selectedChangeViewIndex = 0;
|
||||
int _selectedRendererOptionIndex = 0;
|
||||
String _rendererOptionsTitle = 'CUSTOMIZE';
|
||||
List<WolfMenuRendererEntry> _changeViewEntries =
|
||||
const <WolfMenuRendererEntry>[];
|
||||
List<WolfMenuRendererOptionEntry> _rendererOptionEntries =
|
||||
const <WolfMenuRendererOptionEntry>[];
|
||||
bool _showResumeOption = false;
|
||||
int _gameCount = 1;
|
||||
|
||||
@@ -161,6 +201,18 @@ class MenuManager {
|
||||
|
||||
int get selectedEpisodeIndex => _selectedEpisodeIndex;
|
||||
|
||||
int get selectedChangeViewIndex => _selectedChangeViewIndex;
|
||||
|
||||
int get selectedRendererOptionIndex => _selectedRendererOptionIndex;
|
||||
|
||||
String get rendererOptionsTitle => _rendererOptionsTitle;
|
||||
|
||||
List<WolfMenuRendererEntry> get changeViewEntries =>
|
||||
List<WolfMenuRendererEntry>.unmodifiable(_changeViewEntries);
|
||||
|
||||
List<WolfMenuRendererOptionEntry> get rendererOptionEntries =>
|
||||
List<WolfMenuRendererOptionEntry>.unmodifiable(_rendererOptionEntries);
|
||||
|
||||
List<WolfMenuMainEntry> get mainMenuEntries {
|
||||
return List<WolfMenuMainEntry>.unmodifiable(
|
||||
<WolfMenuMainEntry>[
|
||||
@@ -290,6 +342,133 @@ class MenuManager {
|
||||
_resetEdgeState();
|
||||
}
|
||||
|
||||
int get _changeViewItemCount =>
|
||||
_changeViewEntries.length + _rendererOptionEntries.length;
|
||||
|
||||
void setChangeViewEntries(List<WolfMenuRendererEntry> entries) {
|
||||
final WolfRendererMode? previouslySelectedMode =
|
||||
(_selectedChangeViewIndex >= 0 &&
|
||||
_selectedChangeViewIndex < _changeViewEntries.length)
|
||||
? _changeViewEntries[_selectedChangeViewIndex].mode
|
||||
: null;
|
||||
|
||||
_changeViewEntries = List<WolfMenuRendererEntry>.unmodifiable(entries);
|
||||
|
||||
final int itemCount = _changeViewItemCount;
|
||||
if (itemCount == 0) {
|
||||
_selectedChangeViewIndex = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (previouslySelectedMode != null) {
|
||||
final int modeIndex = _changeViewEntries.indexWhere(
|
||||
(entry) => entry.mode == previouslySelectedMode,
|
||||
);
|
||||
if (modeIndex >= 0 && _isSelectableChangeViewIndex(modeIndex)) {
|
||||
_selectedChangeViewIndex = modeIndex;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_selectedChangeViewIndex = _findSelectableIndex(
|
||||
_clampIndex(_selectedChangeViewIndex, itemCount),
|
||||
itemCount,
|
||||
_isSelectableChangeViewIndex,
|
||||
);
|
||||
}
|
||||
|
||||
void setRendererOptionEntries({
|
||||
required String title,
|
||||
required List<WolfMenuRendererOptionEntry> entries,
|
||||
}) {
|
||||
final bool wasSelectingOption =
|
||||
_selectedChangeViewIndex >= _changeViewEntries.length;
|
||||
final WolfRendererOptionId? previousOption =
|
||||
(_selectedRendererOptionIndex >= 0 &&
|
||||
_selectedRendererOptionIndex < _rendererOptionEntries.length)
|
||||
? _rendererOptionEntries[_selectedRendererOptionIndex].id
|
||||
: null;
|
||||
|
||||
_rendererOptionsTitle = title;
|
||||
_rendererOptionEntries = List<WolfMenuRendererOptionEntry>.unmodifiable(
|
||||
entries,
|
||||
);
|
||||
|
||||
final int totalCount = _changeViewItemCount;
|
||||
if (_rendererOptionEntries.isEmpty || totalCount == 0) {
|
||||
_selectedRendererOptionIndex = 0;
|
||||
if (_changeViewEntries.isNotEmpty) {
|
||||
_selectedChangeViewIndex = _findSelectableIndex(
|
||||
0,
|
||||
_changeViewEntries.length,
|
||||
_isSelectableChangeViewIndex,
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (previousOption != null) {
|
||||
final int previousIndex = _rendererOptionEntries.indexWhere(
|
||||
(entry) => entry.id == previousOption,
|
||||
);
|
||||
if (previousIndex >= 0 &&
|
||||
_isSelectableRendererOptionIndex(previousIndex)) {
|
||||
_selectedRendererOptionIndex = previousIndex;
|
||||
if (wasSelectingOption) {
|
||||
_selectedChangeViewIndex = _changeViewEntries.length + previousIndex;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_selectedRendererOptionIndex = _findSelectableIndex(
|
||||
_clampIndex(_selectedRendererOptionIndex, _rendererOptionEntries.length),
|
||||
_rendererOptionEntries.length,
|
||||
_isSelectableRendererOptionIndex,
|
||||
);
|
||||
|
||||
if (wasSelectingOption) {
|
||||
_selectedChangeViewIndex =
|
||||
_changeViewEntries.length + _selectedRendererOptionIndex;
|
||||
} else {
|
||||
_selectedChangeViewIndex = _findSelectableIndex(
|
||||
_clampIndex(_selectedChangeViewIndex, totalCount),
|
||||
totalCount,
|
||||
_isSelectableChangeViewIndex,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void showChangeViewMenu() {
|
||||
_activeMenu = WolfMenuScreen.changeView;
|
||||
_selectedChangeViewIndex = _changeViewItemCount == 0
|
||||
? 0
|
||||
: _findSelectableIndex(
|
||||
0,
|
||||
_changeViewItemCount,
|
||||
_isSelectableChangeViewIndex,
|
||||
);
|
||||
_transitionTarget = null;
|
||||
_transitionElapsedMs = 0;
|
||||
_transitionSwappedMenu = false;
|
||||
_resetEdgeState();
|
||||
}
|
||||
|
||||
void showRendererOptionsMenu() {
|
||||
_activeMenu = WolfMenuScreen.rendererOptions;
|
||||
_selectedRendererOptionIndex = _rendererOptionEntries.isEmpty
|
||||
? 0
|
||||
: _findSelectableIndex(
|
||||
0,
|
||||
_rendererOptionEntries.length,
|
||||
_isSelectableRendererOptionIndex,
|
||||
);
|
||||
_transitionTarget = null;
|
||||
_transitionElapsedMs = 0;
|
||||
_transitionSwappedMenu = false;
|
||||
_resetEdgeState();
|
||||
}
|
||||
|
||||
/// Starts a menu transition. Input is locked until it completes.
|
||||
///
|
||||
/// Hosts can reuse this fade timing for future pre-menu splash/image
|
||||
@@ -453,6 +632,88 @@ class MenuManager {
|
||||
);
|
||||
}
|
||||
|
||||
({
|
||||
WolfRendererMode? selectedMode,
|
||||
WolfRendererOptionId? selectedOption,
|
||||
bool goBack,
|
||||
})
|
||||
updateChangeViewMenu(EngineInput input) {
|
||||
if (isTransitioning) {
|
||||
_consumeEdgeState(input);
|
||||
return (
|
||||
selectedMode: null,
|
||||
selectedOption: null,
|
||||
goBack: false,
|
||||
);
|
||||
}
|
||||
|
||||
final _MenuAction action = _updateLinearSelection(
|
||||
input,
|
||||
currentIndex: _selectedChangeViewIndex,
|
||||
itemCount: _changeViewItemCount,
|
||||
isSelectableIndex: _isSelectableChangeViewIndex,
|
||||
);
|
||||
_selectedChangeViewIndex = action.index;
|
||||
|
||||
if (!action.confirmed) {
|
||||
return (
|
||||
selectedMode: null,
|
||||
selectedOption: null,
|
||||
goBack: action.goBack,
|
||||
);
|
||||
}
|
||||
|
||||
if (_selectedChangeViewIndex < _changeViewEntries.length) {
|
||||
final WolfMenuRendererEntry entry =
|
||||
_changeViewEntries[_selectedChangeViewIndex];
|
||||
return (
|
||||
selectedMode: entry.mode,
|
||||
selectedOption: null,
|
||||
goBack: action.goBack,
|
||||
);
|
||||
}
|
||||
|
||||
final int optionIndex =
|
||||
_selectedChangeViewIndex - _changeViewEntries.length;
|
||||
if (optionIndex < 0 || optionIndex >= _rendererOptionEntries.length) {
|
||||
return (
|
||||
selectedMode: null,
|
||||
selectedOption: null,
|
||||
goBack: action.goBack,
|
||||
);
|
||||
}
|
||||
_selectedRendererOptionIndex = optionIndex;
|
||||
|
||||
return (
|
||||
selectedMode: null,
|
||||
selectedOption: _rendererOptionEntries[optionIndex].id,
|
||||
goBack: action.goBack,
|
||||
);
|
||||
}
|
||||
|
||||
({WolfRendererOptionId? selectedOption, bool goBack})
|
||||
updateRendererOptionsMenu(EngineInput input) {
|
||||
if (isTransitioning) {
|
||||
_consumeEdgeState(input);
|
||||
return (selectedOption: null, goBack: false);
|
||||
}
|
||||
|
||||
final _MenuAction action = _updateLinearSelection(
|
||||
input,
|
||||
currentIndex: _selectedRendererOptionIndex,
|
||||
itemCount: _rendererOptionEntries.length,
|
||||
isSelectableIndex: _isSelectableRendererOptionIndex,
|
||||
);
|
||||
_selectedRendererOptionIndex = action.index;
|
||||
|
||||
return (
|
||||
selectedOption: action.confirmed && _rendererOptionEntries.isNotEmpty
|
||||
? _rendererOptionEntries[_selectedRendererOptionIndex].id
|
||||
: null,
|
||||
goBack: action.goBack,
|
||||
);
|
||||
}
|
||||
|
||||
/// Returns a menu action snapshot for this frame.
|
||||
({Difficulty? selected, bool goBack}) updateDifficultySelection(
|
||||
EngineInput input,
|
||||
@@ -612,6 +873,27 @@ class MenuManager {
|
||||
return mainMenuEntries[index].isEnabled;
|
||||
}
|
||||
|
||||
bool _isSelectableChangeViewIndex(int index) {
|
||||
if (index < 0) {
|
||||
return false;
|
||||
}
|
||||
if (index < _changeViewEntries.length) {
|
||||
return _changeViewEntries[index].isEnabled;
|
||||
}
|
||||
final int optionIndex = index - _changeViewEntries.length;
|
||||
if (optionIndex >= 0 && optionIndex < _rendererOptionEntries.length) {
|
||||
return _rendererOptionEntries[optionIndex].isEnabled;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _isSelectableRendererOptionIndex(int index) {
|
||||
if (index < 0 || index >= _rendererOptionEntries.length) {
|
||||
return false;
|
||||
}
|
||||
return _rendererOptionEntries[index].isEnabled;
|
||||
}
|
||||
|
||||
WolfMenuMainEntry _mainMenuEntry({
|
||||
required WolfMenuMainAction action,
|
||||
required String label,
|
||||
|
||||
Reference in New Issue
Block a user