feat: Implement fizzle fade transition effects for menus and intros, enhancing visual transitions
Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
@@ -13,6 +13,10 @@ enum WolfMenuScreen {
|
||||
|
||||
enum WolfIntroSlide { retailWarning, pg13, title }
|
||||
|
||||
enum WolfTransitionEffect { none, normalFade, fizzleFade }
|
||||
|
||||
enum WolfTransitionPhase { idle, covering, revealing }
|
||||
|
||||
enum _WolfIntroPhase { fadeIn, hold, fadeOut }
|
||||
|
||||
enum WolfMenuMainAction {
|
||||
@@ -102,9 +106,11 @@ class MenuManager {
|
||||
|
||||
WolfMenuScreen _activeMenu = WolfMenuScreen.difficultySelect;
|
||||
WolfMenuScreen? _transitionTarget;
|
||||
WolfTransitionEffect _transitionEffect = WolfTransitionEffect.normalFade;
|
||||
int _transitionElapsedMs = 0;
|
||||
bool _transitionSwappedMenu = false;
|
||||
WolfMenuScreen _introLandingMenu = WolfMenuScreen.mainMenu;
|
||||
WolfTransitionEffect _introEffect = WolfTransitionEffect.normalFade;
|
||||
int _introSlideIndex = 0;
|
||||
int _introElapsedMs = 0;
|
||||
_WolfIntroPhase _introPhase = _WolfIntroPhase.fadeIn;
|
||||
@@ -182,6 +188,34 @@ class MenuManager {
|
||||
}
|
||||
}
|
||||
|
||||
WolfTransitionEffect get introOverlayEffect {
|
||||
if (!isIntroSplashActive || _introPhase == _WolfIntroPhase.hold) {
|
||||
return WolfTransitionEffect.none;
|
||||
}
|
||||
return _introEffect;
|
||||
}
|
||||
|
||||
WolfTransitionPhase get introOverlayPhase {
|
||||
if (!isIntroSplashActive) {
|
||||
return WolfTransitionPhase.idle;
|
||||
}
|
||||
switch (_introPhase) {
|
||||
case _WolfIntroPhase.fadeIn:
|
||||
return WolfTransitionPhase.revealing;
|
||||
case _WolfIntroPhase.hold:
|
||||
return WolfTransitionPhase.idle;
|
||||
case _WolfIntroPhase.fadeOut:
|
||||
return WolfTransitionPhase.covering;
|
||||
}
|
||||
}
|
||||
|
||||
double get introOverlayPhaseProgress {
|
||||
if (!isIntroSplashActive || _introPhase == _WolfIntroPhase.hold) {
|
||||
return 0.0;
|
||||
}
|
||||
return (_introElapsedMs / introFadeDurationMs).clamp(0.0, 1.0);
|
||||
}
|
||||
|
||||
/// Returns the fade alpha during transitions (0.0..1.0).
|
||||
double get transitionAlpha {
|
||||
if (!isTransitioning) {
|
||||
@@ -195,6 +229,35 @@ class MenuManager {
|
||||
return (1.0 - (fadeInElapsed / half)).clamp(0.0, 1.0);
|
||||
}
|
||||
|
||||
WolfTransitionEffect get transitionEffect {
|
||||
if (!isTransitioning) {
|
||||
return WolfTransitionEffect.none;
|
||||
}
|
||||
return _transitionEffect;
|
||||
}
|
||||
|
||||
WolfTransitionPhase get transitionPhase {
|
||||
if (!isTransitioning) {
|
||||
return WolfTransitionPhase.idle;
|
||||
}
|
||||
final int half = transitionDurationMs ~/ 2;
|
||||
if (_transitionElapsedMs < half) {
|
||||
return WolfTransitionPhase.covering;
|
||||
}
|
||||
return WolfTransitionPhase.revealing;
|
||||
}
|
||||
|
||||
double get transitionPhaseProgress {
|
||||
if (!isTransitioning) {
|
||||
return 0.0;
|
||||
}
|
||||
final int half = transitionDurationMs ~/ 2;
|
||||
if (_transitionElapsedMs < half) {
|
||||
return (_transitionElapsedMs / half).clamp(0.0, 1.0);
|
||||
}
|
||||
return ((_transitionElapsedMs - half) / half).clamp(0.0, 1.0);
|
||||
}
|
||||
|
||||
int get selectedMainIndex => _selectedMainIndex;
|
||||
|
||||
int get selectedGameIndex => _selectedGameIndex;
|
||||
@@ -272,6 +335,7 @@ class MenuManager {
|
||||
Difficulty? initialDifficulty,
|
||||
bool hasResumableGame = false,
|
||||
bool initialGameIsRetail = false,
|
||||
WolfTransitionEffect introEffect = WolfTransitionEffect.normalFade,
|
||||
}) {
|
||||
_gameCount = gameCount;
|
||||
_showResumeOption = hasResumableGame;
|
||||
@@ -286,6 +350,7 @@ class MenuManager {
|
||||
_introLandingMenu = WolfMenuScreen.mainMenu;
|
||||
if (gameCount > 1) {
|
||||
_activeMenu = WolfMenuScreen.gameSelect;
|
||||
_introEffect = introEffect;
|
||||
_introElapsedMs = 0;
|
||||
_introPhase = _WolfIntroPhase.fadeIn;
|
||||
_introSlideIndex = 0;
|
||||
@@ -294,9 +359,13 @@ class MenuManager {
|
||||
WolfIntroSlide.title,
|
||||
];
|
||||
} else {
|
||||
_startIntroSequence(includeRetailWarning: initialGameIsRetail);
|
||||
_startIntroSequence(
|
||||
includeRetailWarning: initialGameIsRetail,
|
||||
effect: introEffect,
|
||||
);
|
||||
}
|
||||
_transitionTarget = null;
|
||||
_transitionEffect = WolfTransitionEffect.normalFade;
|
||||
_transitionElapsedMs = 0;
|
||||
_transitionSwappedMenu = false;
|
||||
_resetEdgeState();
|
||||
@@ -306,12 +375,17 @@ class MenuManager {
|
||||
void beginIntroSplash({
|
||||
WolfMenuScreen landingMenu = WolfMenuScreen.mainMenu,
|
||||
bool includeRetailWarning = false,
|
||||
WolfTransitionEffect effect = WolfTransitionEffect.normalFade,
|
||||
}) {
|
||||
_introLandingMenu = landingMenu;
|
||||
_transitionTarget = null;
|
||||
_transitionEffect = WolfTransitionEffect.normalFade;
|
||||
_transitionElapsedMs = 0;
|
||||
_transitionSwappedMenu = false;
|
||||
_startIntroSequence(includeRetailWarning: includeRetailWarning);
|
||||
_startIntroSequence(
|
||||
includeRetailWarning: includeRetailWarning,
|
||||
effect: effect,
|
||||
);
|
||||
_resetEdgeState();
|
||||
}
|
||||
|
||||
@@ -336,8 +410,10 @@ class MenuManager {
|
||||
}
|
||||
_activeMenu = WolfMenuScreen.mainMenu;
|
||||
_transitionTarget = null;
|
||||
_transitionEffect = WolfTransitionEffect.normalFade;
|
||||
_transitionElapsedMs = 0;
|
||||
_transitionSwappedMenu = false;
|
||||
_introEffect = WolfTransitionEffect.normalFade;
|
||||
_introElapsedMs = 0;
|
||||
_resetEdgeState();
|
||||
}
|
||||
@@ -449,6 +525,7 @@ class MenuManager {
|
||||
_isSelectableChangeViewIndex,
|
||||
);
|
||||
_transitionTarget = null;
|
||||
_transitionEffect = WolfTransitionEffect.normalFade;
|
||||
_transitionElapsedMs = 0;
|
||||
_transitionSwappedMenu = false;
|
||||
_resetEdgeState();
|
||||
@@ -464,6 +541,7 @@ class MenuManager {
|
||||
_isSelectableRendererOptionIndex,
|
||||
);
|
||||
_transitionTarget = null;
|
||||
_transitionEffect = WolfTransitionEffect.normalFade;
|
||||
_transitionElapsedMs = 0;
|
||||
_transitionSwappedMenu = false;
|
||||
_resetEdgeState();
|
||||
@@ -473,11 +551,15 @@ class MenuManager {
|
||||
///
|
||||
/// Hosts can reuse this fade timing for future pre-menu splash/image
|
||||
/// sequences so transitions feel consistent across the whole app.
|
||||
void startTransition(WolfMenuScreen target) {
|
||||
void startTransition(
|
||||
WolfMenuScreen target, {
|
||||
WolfTransitionEffect effect = WolfTransitionEffect.normalFade,
|
||||
}) {
|
||||
if (_activeMenu == target) {
|
||||
return;
|
||||
}
|
||||
_transitionTarget = target;
|
||||
_transitionEffect = effect;
|
||||
_transitionElapsedMs = 0;
|
||||
_transitionSwappedMenu = false;
|
||||
_resetEdgeState();
|
||||
@@ -501,6 +583,7 @@ class MenuManager {
|
||||
}
|
||||
if (_transitionElapsedMs >= transitionDurationMs) {
|
||||
_transitionTarget = null;
|
||||
_transitionEffect = WolfTransitionEffect.normalFade;
|
||||
_transitionElapsedMs = 0;
|
||||
_transitionSwappedMenu = false;
|
||||
}
|
||||
@@ -527,8 +610,12 @@ class MenuManager {
|
||||
_consumeEdgeState(input);
|
||||
}
|
||||
|
||||
void _startIntroSequence({required bool includeRetailWarning}) {
|
||||
void _startIntroSequence({
|
||||
required bool includeRetailWarning,
|
||||
required WolfTransitionEffect effect,
|
||||
}) {
|
||||
_activeMenu = WolfMenuScreen.introSplash;
|
||||
_introEffect = effect;
|
||||
_introSlides = includeRetailWarning
|
||||
? <WolfIntroSlide>[
|
||||
WolfIntroSlide.retailWarning,
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'dart:math' as math;
|
||||
|
||||
import 'package:arcane_helper_utils/arcane_helper_utils.dart';
|
||||
import 'package:wolf_3d_dart/src/menu/menu_manager.dart';
|
||||
import 'package:wolf_3d_dart/src/rendering/fizzle_fade.dart';
|
||||
import 'package:wolf_3d_dart/wolf_3d_data_types.dart';
|
||||
import 'package:wolf_3d_dart/wolf_3d_engine.dart';
|
||||
import 'package:wolf_3d_dart/wolf_3d_menu.dart';
|
||||
@@ -130,6 +131,8 @@ class AsciiRenderer extends CliRendererBackend<dynamic> {
|
||||
|
||||
late List<List<ColoredChar>> _screen;
|
||||
late List<List<int>> _scenePixels;
|
||||
List<List<ColoredChar>> _screenScratch = const <List<ColoredChar>>[];
|
||||
List<List<int>> _scenePixelsScratch = const <List<int>>[];
|
||||
List<int>? _mainMenuBandFirstColumn;
|
||||
String? _lastLoggedThemeName;
|
||||
|
||||
@@ -488,7 +491,7 @@ class AsciiRenderer extends CliRendererBackend<dynamic> {
|
||||
}
|
||||
|
||||
_drawCenteredMenuFooter();
|
||||
_applyMenuFade(engine.menuManager.transitionAlpha, bgColor);
|
||||
_applyMenuTransition(engine.menuManager, bgColor);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -538,7 +541,7 @@ class AsciiRenderer extends CliRendererBackend<dynamic> {
|
||||
}
|
||||
|
||||
_drawCenteredMenuFooter();
|
||||
_applyMenuFade(engine.menuManager.transitionAlpha, bgColor);
|
||||
_applyMenuTransition(engine.menuManager, bgColor);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -597,7 +600,7 @@ class AsciiRenderer extends CliRendererBackend<dynamic> {
|
||||
);
|
||||
}
|
||||
_drawCenteredMenuFooter();
|
||||
_applyMenuFade(engine.menuManager.transitionAlpha, bgColor);
|
||||
_applyMenuTransition(engine.menuManager, bgColor);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -638,7 +641,7 @@ class AsciiRenderer extends CliRendererBackend<dynamic> {
|
||||
}
|
||||
|
||||
_drawCenteredMenuFooter();
|
||||
_applyMenuFade(engine.menuManager.transitionAlpha, bgColor);
|
||||
_applyMenuTransition(engine.menuManager, bgColor);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -742,7 +745,7 @@ class AsciiRenderer extends CliRendererBackend<dynamic> {
|
||||
}
|
||||
|
||||
_drawCenteredMenuFooter();
|
||||
_applyMenuFade(engine.menuManager.transitionAlpha, bgColor);
|
||||
_applyMenuTransition(engine.menuManager, bgColor);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -795,7 +798,7 @@ class AsciiRenderer extends CliRendererBackend<dynamic> {
|
||||
}
|
||||
|
||||
_drawCenteredMenuFooter();
|
||||
_applyMenuFade(engine.menuManager.transitionAlpha, bgColor);
|
||||
_applyMenuTransition(engine.menuManager, bgColor);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -851,7 +854,7 @@ class AsciiRenderer extends CliRendererBackend<dynamic> {
|
||||
);
|
||||
}
|
||||
_drawCenteredMenuFooter();
|
||||
_applyMenuFade(engine.menuManager.transitionAlpha, bgColor);
|
||||
_applyMenuTransition(engine.menuManager, bgColor);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -878,7 +881,7 @@ class AsciiRenderer extends CliRendererBackend<dynamic> {
|
||||
}
|
||||
|
||||
_drawCenteredMenuFooter();
|
||||
_applyMenuFade(engine.menuManager.transitionAlpha, bgColor);
|
||||
_applyMenuTransition(engine.menuManager, bgColor);
|
||||
}
|
||||
|
||||
String _gameTitle(GameVersion version) {
|
||||
@@ -932,10 +935,7 @@ class AsciiRenderer extends CliRendererBackend<dynamic> {
|
||||
_blitVgaImageAscii(image, x, y);
|
||||
}
|
||||
|
||||
_applyMenuFade(
|
||||
engine.menuManager.introOverlayAlpha,
|
||||
_rgbToPaletteColor(0x000000),
|
||||
);
|
||||
_applyIntroTransition(engine.menuManager, _rgbToPaletteColor(0x000000));
|
||||
}
|
||||
|
||||
void _drawRetailWarningIntro(
|
||||
@@ -1085,6 +1085,148 @@ class AsciiRenderer extends CliRendererBackend<dynamic> {
|
||||
}
|
||||
}
|
||||
|
||||
void _applyMenuTransition(MenuManager menuManager, int coverColor) {
|
||||
switch (menuManager.transitionEffect) {
|
||||
case WolfTransitionEffect.none:
|
||||
return;
|
||||
case WolfTransitionEffect.normalFade:
|
||||
_applyMenuFade(menuManager.transitionAlpha, coverColor);
|
||||
return;
|
||||
case WolfTransitionEffect.fizzleFade:
|
||||
_applyFizzleTransition(
|
||||
phase: menuManager.transitionPhase,
|
||||
progress: menuManager.transitionPhaseProgress,
|
||||
coverColor: coverColor,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void _applyIntroTransition(MenuManager menuManager, int coverColor) {
|
||||
switch (menuManager.introOverlayEffect) {
|
||||
case WolfTransitionEffect.none:
|
||||
return;
|
||||
case WolfTransitionEffect.normalFade:
|
||||
_applyMenuFade(menuManager.introOverlayAlpha, coverColor);
|
||||
return;
|
||||
case WolfTransitionEffect.fizzleFade:
|
||||
_applyFizzleTransition(
|
||||
phase: menuManager.introOverlayPhase,
|
||||
progress: menuManager.introOverlayPhaseProgress,
|
||||
coverColor: coverColor,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void _applyFizzleTransition({
|
||||
required WolfTransitionPhase phase,
|
||||
required double progress,
|
||||
required int coverColor,
|
||||
}) {
|
||||
switch (phase) {
|
||||
case WolfTransitionPhase.idle:
|
||||
return;
|
||||
case WolfTransitionPhase.covering:
|
||||
_applyFizzleCover(progress, coverColor);
|
||||
return;
|
||||
case WolfTransitionPhase.revealing:
|
||||
_applyFizzleReveal(progress, coverColor);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void _applyFizzleCover(double progress, int coverColor) {
|
||||
final int coverCount = FizzleFade.revealCountForProgress(progress);
|
||||
if (coverCount <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
FizzleFade.forEachCanonicalPixel(coverCount, (int x, int y) {
|
||||
_fillRect320(x, y, 1, 1, coverColor);
|
||||
});
|
||||
}
|
||||
|
||||
void _applyFizzleReveal(double progress, int coverColor) {
|
||||
final int revealCount = FizzleFade.revealCountForProgress(progress);
|
||||
if (revealCount <= 0) {
|
||||
if (_usesTerminalLayout) {
|
||||
_fillTerminalRect(0, 0, width, _terminalPixelHeight, coverColor);
|
||||
} else {
|
||||
_fillRect(0, 0, width, height, ' ', coverColor);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (revealCount >= FizzleFade.canonicalPixelCount) {
|
||||
return;
|
||||
}
|
||||
|
||||
_captureTransitionScratch();
|
||||
if (_usesTerminalLayout) {
|
||||
_fillTerminalRect(0, 0, width, _terminalPixelHeight, coverColor);
|
||||
} else {
|
||||
_fillRect(0, 0, width, height, ' ', coverColor);
|
||||
}
|
||||
|
||||
FizzleFade.forEachCanonicalPixel(revealCount, (int x, int y) {
|
||||
_copyCanonicalPixelFromScratch(x, y);
|
||||
});
|
||||
}
|
||||
|
||||
void _captureTransitionScratch() {
|
||||
if (_usesTerminalLayout) {
|
||||
_scenePixelsScratch = List<List<int>>.generate(
|
||||
_scenePixels.length,
|
||||
(int y) => List<int>.from(_scenePixels[y]),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
_screenScratch = List<List<ColoredChar>>.generate(
|
||||
_screen.length,
|
||||
(int y) => List<ColoredChar>.from(_screen[y]),
|
||||
);
|
||||
}
|
||||
|
||||
void _copyCanonicalPixelFromScratch(int x320, int y200) {
|
||||
final double scaleX =
|
||||
(_usesTerminalLayout ? projectionWidth : width) / 320.0;
|
||||
final double scaleY =
|
||||
(_usesTerminalLayout ? _terminalPixelHeight : height) / 200.0;
|
||||
final int offsetX = _usesTerminalLayout ? projectionOffsetX : 0;
|
||||
final int startX = offsetX + (x320 * scaleX).floor();
|
||||
final int endX = offsetX + ((x320 + 1) * scaleX).ceil();
|
||||
final int startY = (y200 * scaleY).floor();
|
||||
final int endY = ((y200 + 1) * scaleY).ceil();
|
||||
|
||||
if (_usesTerminalLayout) {
|
||||
for (int y = startY; y < endY; y++) {
|
||||
if (y < 0 || y >= _terminalPixelHeight) {
|
||||
continue;
|
||||
}
|
||||
for (int x = startX; x < endX; x++) {
|
||||
if (x < 0 || x >= width) {
|
||||
continue;
|
||||
}
|
||||
_scenePixels[y][x] = _scenePixelsScratch[y][x];
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (int y = startY; y < endY; y++) {
|
||||
if (y < 0 || y >= height) {
|
||||
continue;
|
||||
}
|
||||
for (int x = startX; x < endX; x++) {
|
||||
if (x < 0 || x >= width) {
|
||||
continue;
|
||||
}
|
||||
_screen[y][x] = _screenScratch[y][x];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _applyMenuFade(double alpha, int fadeColor) {
|
||||
if (alpha <= 0.0) {
|
||||
return;
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import 'dart:collection';
|
||||
|
||||
/// Canonical Wolf3D-style 17-bit Galois LFSR used for Fizzle Fade ordering.
|
||||
///
|
||||
/// The sequence mirrors the original coordinate extraction strategy:
|
||||
/// low 8 bits minus one form `y`, the next 9 bits form `x`, and out-of-bounds
|
||||
/// points are skipped. For menu and intro rendering we use the original
|
||||
/// canonical 320x200 coordinate space and let renderers scale that to their
|
||||
/// output buffer.
|
||||
abstract final class FizzleFade {
|
||||
static const int canonicalWidth = 320;
|
||||
static const int canonicalHeight = 200;
|
||||
static const int canonicalPixelCount = canonicalWidth * canonicalHeight;
|
||||
static const int seed = 1;
|
||||
static const int _xorMask = 0x12000;
|
||||
static final List<int> _canonicalSequence = List<int>.unmodifiable(
|
||||
_buildSequence(width: canonicalWidth, height: canonicalHeight),
|
||||
);
|
||||
|
||||
static List<int> get canonicalSequence => _canonicalSequence;
|
||||
|
||||
static int nextState(int state) {
|
||||
final bool lsbSet = (state & 1) != 0;
|
||||
int next = state >> 1;
|
||||
if (lsbSet) {
|
||||
next ^= _xorMask;
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
static int xForState(int state) => (state >> 8) & 0x1FF;
|
||||
|
||||
static int yForState(int state) => ((state & 0xFF) - 1) & 0xFF;
|
||||
|
||||
static bool isInBounds(int state, {required int width, required int height}) {
|
||||
return xForState(state) < width && yForState(state) < height;
|
||||
}
|
||||
|
||||
static int revealCountForProgress(double progress, {int? pixelCount}) {
|
||||
final int total = pixelCount ?? canonicalPixelCount;
|
||||
return (progress.clamp(0.0, 1.0) * total).round().clamp(0, total);
|
||||
}
|
||||
|
||||
static void forEachCanonicalPixel(
|
||||
int count,
|
||||
void Function(int x, int y) plot,
|
||||
) {
|
||||
final int limit = count.clamp(0, canonicalPixelCount);
|
||||
for (int i = 0; i < limit; i++) {
|
||||
final int packed = _canonicalSequence[i];
|
||||
plot(packed & 0xFFFF, packed >> 16);
|
||||
}
|
||||
}
|
||||
|
||||
static List<int> _buildSequence({required int width, required int height}) {
|
||||
final List<int> points = <int>[];
|
||||
int state = seed;
|
||||
do {
|
||||
if (isInBounds(state, width: width, height: height)) {
|
||||
points.add((yForState(state) << 16) | xForState(state));
|
||||
}
|
||||
state = nextState(state);
|
||||
} while (state != seed);
|
||||
return UnmodifiableListView<int>(points);
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import 'dart:typed_data';
|
||||
|
||||
import 'package:wolf_3d_dart/src/input/cli_input.dart';
|
||||
import 'package:wolf_3d_dart/src/menu/menu_manager.dart';
|
||||
import 'package:wolf_3d_dart/src/rendering/fizzle_fade.dart';
|
||||
import 'package:wolf_3d_dart/wolf_3d_data_types.dart';
|
||||
import 'package:wolf_3d_dart/wolf_3d_engine.dart';
|
||||
import 'package:wolf_3d_dart/wolf_3d_menu.dart';
|
||||
@@ -36,6 +37,7 @@ class SixelRenderer extends CliRendererBackend<String> {
|
||||
static const int _headerHeadingY = 24;
|
||||
|
||||
late Uint8List _screen;
|
||||
Uint8List _transitionScratch = Uint8List(0);
|
||||
List<int>? _mainMenuBandFirstColumn;
|
||||
int _offsetColumns = 0;
|
||||
int _offsetRows = 0;
|
||||
@@ -435,7 +437,7 @@ class SixelRenderer extends CliRendererBackend<String> {
|
||||
scale: 1,
|
||||
);
|
||||
}
|
||||
_applyMenuFade(engine.menuManager.transitionAlpha, bgColor);
|
||||
_applyMenuTransition(engine.menuManager, bgColor);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -470,7 +472,7 @@ class SixelRenderer extends CliRendererBackend<String> {
|
||||
scale: 1,
|
||||
);
|
||||
}
|
||||
_applyMenuFade(engine.menuManager.transitionAlpha, bgColor);
|
||||
_applyMenuTransition(engine.menuManager, bgColor);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -522,7 +524,7 @@ class SixelRenderer extends CliRendererBackend<String> {
|
||||
);
|
||||
}
|
||||
}
|
||||
_applyMenuFade(engine.menuManager.transitionAlpha, bgColor);
|
||||
_applyMenuTransition(engine.menuManager, bgColor);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -615,7 +617,7 @@ class SixelRenderer extends CliRendererBackend<String> {
|
||||
scale: 1,
|
||||
);
|
||||
}
|
||||
_applyMenuFade(engine.menuManager.transitionAlpha, bgColor);
|
||||
_applyMenuTransition(engine.menuManager, bgColor);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -662,7 +664,7 @@ class SixelRenderer extends CliRendererBackend<String> {
|
||||
scale: 1,
|
||||
);
|
||||
}
|
||||
_applyMenuFade(engine.menuManager.transitionAlpha, bgColor);
|
||||
_applyMenuTransition(engine.menuManager, bgColor);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -676,7 +678,7 @@ class SixelRenderer extends CliRendererBackend<String> {
|
||||
_fillRect320(28, 70, 264, 82, panelColor);
|
||||
if (_useCompactMenuLayout) {
|
||||
_drawCompactMenu(selectedDifficultyIndex, headingIndex, panelColor);
|
||||
_applyMenuFade(engine.menuManager.transitionAlpha, bgColor);
|
||||
_applyMenuTransition(engine.menuManager, bgColor);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -717,7 +719,7 @@ class SixelRenderer extends CliRendererBackend<String> {
|
||||
);
|
||||
}
|
||||
|
||||
_applyMenuFade(engine.menuManager.transitionAlpha, bgColor);
|
||||
_applyMenuTransition(engine.menuManager, bgColor);
|
||||
}
|
||||
|
||||
void _drawCustomizeMenuHeader(
|
||||
@@ -816,10 +818,7 @@ class SixelRenderer extends CliRendererBackend<String> {
|
||||
_blitVgaImage(image, x, y);
|
||||
}
|
||||
|
||||
_applyMenuFade(
|
||||
engine.menuManager.introOverlayAlpha,
|
||||
_rgbToPaletteIndex(0x000000),
|
||||
);
|
||||
_applyIntroTransition(engine.menuManager, _rgbToPaletteIndex(0x000000));
|
||||
}
|
||||
|
||||
void _drawRetailWarningIntro(int backgroundColor) {
|
||||
@@ -861,6 +860,117 @@ class SixelRenderer extends CliRendererBackend<String> {
|
||||
return bestIndex;
|
||||
}
|
||||
|
||||
void _applyMenuTransition(MenuManager menuManager, int coverColor) {
|
||||
switch (menuManager.transitionEffect) {
|
||||
case WolfTransitionEffect.none:
|
||||
return;
|
||||
case WolfTransitionEffect.normalFade:
|
||||
_applyMenuFade(menuManager.transitionAlpha, coverColor);
|
||||
return;
|
||||
case WolfTransitionEffect.fizzleFade:
|
||||
_applyFizzleTransition(
|
||||
phase: menuManager.transitionPhase,
|
||||
progress: menuManager.transitionPhaseProgress,
|
||||
coverColor: coverColor,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void _applyIntroTransition(MenuManager menuManager, int coverColor) {
|
||||
switch (menuManager.introOverlayEffect) {
|
||||
case WolfTransitionEffect.none:
|
||||
return;
|
||||
case WolfTransitionEffect.normalFade:
|
||||
_applyMenuFade(menuManager.introOverlayAlpha, coverColor);
|
||||
return;
|
||||
case WolfTransitionEffect.fizzleFade:
|
||||
_applyFizzleTransition(
|
||||
phase: menuManager.introOverlayPhase,
|
||||
progress: menuManager.introOverlayPhaseProgress,
|
||||
coverColor: coverColor,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void _applyFizzleTransition({
|
||||
required WolfTransitionPhase phase,
|
||||
required double progress,
|
||||
required int coverColor,
|
||||
}) {
|
||||
switch (phase) {
|
||||
case WolfTransitionPhase.idle:
|
||||
return;
|
||||
case WolfTransitionPhase.covering:
|
||||
_applyFizzleCover(progress, coverColor);
|
||||
return;
|
||||
case WolfTransitionPhase.revealing:
|
||||
_applyFizzleReveal(progress, coverColor);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void _applyFizzleCover(double progress, int coverColor) {
|
||||
final int coverCount = FizzleFade.revealCountForProgress(progress);
|
||||
if (coverCount <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
FizzleFade.forEachCanonicalPixel(coverCount, (int x, int y) {
|
||||
_fillRect320(x, y, 1, 1, coverColor);
|
||||
});
|
||||
}
|
||||
|
||||
void _applyFizzleReveal(double progress, int coverColor) {
|
||||
final int revealCount = FizzleFade.revealCountForProgress(progress);
|
||||
if (revealCount <= 0) {
|
||||
_screen.fillRange(0, _screen.length, coverColor);
|
||||
return;
|
||||
}
|
||||
if (revealCount >= FizzleFade.canonicalPixelCount) {
|
||||
return;
|
||||
}
|
||||
|
||||
_ensureTransitionScratch();
|
||||
_transitionScratch.setAll(0, _screen);
|
||||
_screen.fillRange(0, _screen.length, coverColor);
|
||||
|
||||
FizzleFade.forEachCanonicalPixel(revealCount, (int x, int y) {
|
||||
_copyCanonicalPixelFromScratch(x, y);
|
||||
});
|
||||
}
|
||||
|
||||
void _ensureTransitionScratch() {
|
||||
if (_transitionScratch.length == _screen.length) {
|
||||
return;
|
||||
}
|
||||
_transitionScratch = Uint8List(_screen.length);
|
||||
}
|
||||
|
||||
void _copyCanonicalPixelFromScratch(int x320, int y200) {
|
||||
final double scaleX = width / 320.0;
|
||||
final double scaleY = height / 200.0;
|
||||
final int startX = (x320 * scaleX).floor();
|
||||
final int endX = ((x320 + 1) * scaleX).ceil();
|
||||
final int startY = (y200 * scaleY).floor();
|
||||
final int endY = ((y200 + 1) * scaleY).ceil();
|
||||
|
||||
for (int y = startY; y < endY; y++) {
|
||||
if (y < 0 || y >= height) {
|
||||
continue;
|
||||
}
|
||||
final int rowOffset = y * width;
|
||||
for (int x = startX; x < endX; x++) {
|
||||
if (x < 0 || x >= width) {
|
||||
continue;
|
||||
}
|
||||
final int index = rowOffset + x;
|
||||
_screen[index] = _transitionScratch[index];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _applyMenuFade(double alpha, int bgColor) {
|
||||
if (alpha <= 0.0) {
|
||||
return;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import 'dart:math' as math;
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:wolf_3d_dart/src/menu/menu_manager.dart';
|
||||
import 'package:wolf_3d_dart/src/rendering/fizzle_fade.dart';
|
||||
import 'package:wolf_3d_dart/src/rendering/menu_font.dart';
|
||||
import 'package:wolf_3d_dart/src/rendering/renderer_backend.dart';
|
||||
import 'package:wolf_3d_dart/wolf_3d_data_types.dart';
|
||||
@@ -18,6 +20,7 @@ class SoftwareRenderer extends RendererBackend<FrameBuffer> {
|
||||
static const int _headerHeadingY = 24;
|
||||
|
||||
late FrameBuffer _buffer;
|
||||
Uint32List _transitionScratch = Uint32List(0);
|
||||
List<int>? _mainMenuBandFirstColumn;
|
||||
|
||||
@override
|
||||
@@ -233,7 +236,7 @@ class SoftwareRenderer extends RendererBackend<FrameBuffer> {
|
||||
break;
|
||||
}
|
||||
|
||||
_applyMenuFade(engine.menuManager.transitionAlpha, bgColor);
|
||||
_applyMenuTransition(engine.menuManager, bgColor);
|
||||
}
|
||||
|
||||
void _drawIntroSplash(WolfEngine engine, WolfClassicMenuArt art) {
|
||||
@@ -275,10 +278,7 @@ class SoftwareRenderer extends RendererBackend<FrameBuffer> {
|
||||
}
|
||||
}
|
||||
|
||||
_applyMenuFade(
|
||||
engine.menuManager.introOverlayAlpha,
|
||||
_rgbToFrameColor(0x000000),
|
||||
);
|
||||
_applyIntroTransition(engine.menuManager, _rgbToFrameColor(0x000000));
|
||||
}
|
||||
|
||||
void _drawRetailWarningIntro(int backgroundColor) {
|
||||
@@ -550,7 +550,8 @@ class SoftwareRenderer extends RendererBackend<FrameBuffer> {
|
||||
? 0
|
||||
: ((optionEntries.length - 1) * optionsRowStep) + 10;
|
||||
final int optionsRowStart =
|
||||
optionsPanelY + ((optionsPanelH - optionsRowsHeight) ~/ 2).clamp(0, 200);
|
||||
optionsPanelY +
|
||||
((optionsPanelH - optionsRowsHeight) ~/ 2).clamp(0, 200);
|
||||
for (int i = 0; i < optionEntries.length; i++) {
|
||||
final int optionIndex = modeCount + i;
|
||||
final bool isSelected = optionIndex == selectedIndex;
|
||||
@@ -937,6 +938,40 @@ class SoftwareRenderer extends RendererBackend<FrameBuffer> {
|
||||
}
|
||||
}
|
||||
|
||||
void _applyMenuTransition(MenuManager menuManager, int coverColor) {
|
||||
switch (menuManager.transitionEffect) {
|
||||
case WolfTransitionEffect.none:
|
||||
return;
|
||||
case WolfTransitionEffect.normalFade:
|
||||
_applyMenuFade(menuManager.transitionAlpha, coverColor);
|
||||
return;
|
||||
case WolfTransitionEffect.fizzleFade:
|
||||
_applyFizzleTransition(
|
||||
phase: menuManager.transitionPhase,
|
||||
progress: menuManager.transitionPhaseProgress,
|
||||
coverColor: coverColor,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void _applyIntroTransition(MenuManager menuManager, int coverColor) {
|
||||
switch (menuManager.introOverlayEffect) {
|
||||
case WolfTransitionEffect.none:
|
||||
return;
|
||||
case WolfTransitionEffect.normalFade:
|
||||
_applyMenuFade(menuManager.introOverlayAlpha, coverColor);
|
||||
return;
|
||||
case WolfTransitionEffect.fizzleFade:
|
||||
_applyFizzleTransition(
|
||||
phase: menuManager.introOverlayPhase,
|
||||
progress: menuManager.introOverlayPhaseProgress,
|
||||
coverColor: coverColor,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void _applyMenuFade(double alpha, int fadeColor) {
|
||||
if (alpha <= 0.0) {
|
||||
return;
|
||||
@@ -959,6 +994,81 @@ class SoftwareRenderer extends RendererBackend<FrameBuffer> {
|
||||
}
|
||||
}
|
||||
|
||||
void _applyFizzleTransition({
|
||||
required WolfTransitionPhase phase,
|
||||
required double progress,
|
||||
required int coverColor,
|
||||
}) {
|
||||
switch (phase) {
|
||||
case WolfTransitionPhase.idle:
|
||||
return;
|
||||
case WolfTransitionPhase.covering:
|
||||
_applyFizzleCover(progress, coverColor);
|
||||
return;
|
||||
case WolfTransitionPhase.revealing:
|
||||
_applyFizzleReveal(progress, coverColor);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void _applyFizzleCover(double progress, int coverColor) {
|
||||
final int coverCount = FizzleFade.revealCountForProgress(progress);
|
||||
if (coverCount <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
FizzleFade.forEachCanonicalPixel(coverCount, (int x, int y) {
|
||||
_fillCanonicalRect(x, y, 1, 1, coverColor);
|
||||
});
|
||||
}
|
||||
|
||||
void _applyFizzleReveal(double progress, int coverColor) {
|
||||
final int revealCount = FizzleFade.revealCountForProgress(progress);
|
||||
if (revealCount <= 0) {
|
||||
_buffer.pixels.fillRange(0, _buffer.pixels.length, coverColor);
|
||||
return;
|
||||
}
|
||||
if (revealCount >= FizzleFade.canonicalPixelCount) {
|
||||
return;
|
||||
}
|
||||
|
||||
_ensureTransitionScratch();
|
||||
_transitionScratch.setAll(0, _buffer.pixels);
|
||||
_buffer.pixels.fillRange(0, _buffer.pixels.length, coverColor);
|
||||
|
||||
FizzleFade.forEachCanonicalPixel(revealCount, (int x, int y) {
|
||||
_copyCanonicalPixelFromScratch(x, y);
|
||||
});
|
||||
}
|
||||
|
||||
void _ensureTransitionScratch() {
|
||||
if (_transitionScratch.length == _buffer.pixels.length) {
|
||||
return;
|
||||
}
|
||||
_transitionScratch = Uint32List(_buffer.pixels.length);
|
||||
}
|
||||
|
||||
void _copyCanonicalPixelFromScratch(int startX320, int startY200) {
|
||||
final int startX = (startX320 * _uiScaleX).floor();
|
||||
final int endX = ((startX320 + 1) * _uiScaleX).ceil();
|
||||
final int startY = (startY200 * _uiScaleY).floor();
|
||||
final int endY = ((startY200 + 1) * _uiScaleY).ceil();
|
||||
|
||||
for (int y = startY; y < endY; y++) {
|
||||
if (y < 0 || y >= height) {
|
||||
continue;
|
||||
}
|
||||
final int rowStart = y * width;
|
||||
for (int x = startX; x < endX; x++) {
|
||||
if (x < 0 || x >= width) {
|
||||
continue;
|
||||
}
|
||||
final int index = rowStart + x;
|
||||
_buffer.pixels[index] = _transitionScratch[index];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts an `RRGGBB` menu color into the framebuffer's packed channel
|
||||
/// order (`0xAABBGGRR`) used throughout this renderer.
|
||||
int _rgbToFrameColor(int rgb) {
|
||||
|
||||
Reference in New Issue
Block a user