feat: Add intro splash screen with transition effects and rendering support
Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
@@ -295,6 +295,9 @@ class WolfEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (menuManager.activeMenu) {
|
switch (menuManager.activeMenu) {
|
||||||
|
case WolfMenuScreen.introSplash:
|
||||||
|
menuManager.updateIntroSplash(input);
|
||||||
|
break;
|
||||||
case WolfMenuScreen.mainMenu:
|
case WolfMenuScreen.mainMenu:
|
||||||
_tickMainMenu(input);
|
_tickMainMenu(input);
|
||||||
break;
|
break;
|
||||||
@@ -370,7 +373,7 @@ class WolfEngine {
|
|||||||
_currentEpisodeIndex = 0;
|
_currentEpisodeIndex = 0;
|
||||||
onEpisodeSelected?.call(null);
|
onEpisodeSelected?.call(null);
|
||||||
menuManager.clearEpisodeSelection();
|
menuManager.clearEpisodeSelection();
|
||||||
menuManager.startTransition(WolfMenuScreen.mainMenu);
|
menuManager.beginIntroSplash();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,15 @@
|
|||||||
import 'package:wolf_3d_dart/wolf_3d_data_types.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_engine.dart';
|
||||||
|
|
||||||
enum WolfMenuScreen { mainMenu, gameSelect, episodeSelect, difficultySelect }
|
enum WolfMenuScreen {
|
||||||
|
introSplash,
|
||||||
|
mainMenu,
|
||||||
|
gameSelect,
|
||||||
|
episodeSelect,
|
||||||
|
difficultySelect,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum _WolfIntroPhase { fadeIn, hold, fadeOut }
|
||||||
|
|
||||||
enum WolfMenuMainAction {
|
enum WolfMenuMainAction {
|
||||||
newGame,
|
newGame,
|
||||||
@@ -52,11 +60,20 @@ bool _isWiredMainMenuAction(WolfMenuMainAction action) {
|
|||||||
/// Handles menu-only input state such as selection movement and edge triggers.
|
/// Handles menu-only input state such as selection movement and edge triggers.
|
||||||
class MenuManager {
|
class MenuManager {
|
||||||
static const int transitionDurationMs = 280;
|
static const int transitionDurationMs = 280;
|
||||||
|
static const int introFadeDurationMs = 280;
|
||||||
|
static const int _introSlideCount = 2;
|
||||||
|
static const int _introPg13BackgroundRgb = 0x33A2E8;
|
||||||
|
static const int _introTitleBackgroundRgb = 0x000000;
|
||||||
|
|
||||||
WolfMenuScreen _activeMenu = WolfMenuScreen.difficultySelect;
|
WolfMenuScreen _activeMenu = WolfMenuScreen.difficultySelect;
|
||||||
WolfMenuScreen? _transitionTarget;
|
WolfMenuScreen? _transitionTarget;
|
||||||
int _transitionElapsedMs = 0;
|
int _transitionElapsedMs = 0;
|
||||||
bool _transitionSwappedMenu = false;
|
bool _transitionSwappedMenu = false;
|
||||||
|
WolfMenuScreen _introLandingMenu = WolfMenuScreen.mainMenu;
|
||||||
|
int _introSlideIndex = 0;
|
||||||
|
int _introElapsedMs = 0;
|
||||||
|
_WolfIntroPhase _introPhase = _WolfIntroPhase.fadeIn;
|
||||||
|
bool _introAdvanceRequested = false;
|
||||||
|
|
||||||
int _selectedMainIndex = 0;
|
int _selectedMainIndex = 0;
|
||||||
int _selectedGameIndex = 0;
|
int _selectedGameIndex = 0;
|
||||||
@@ -74,6 +91,27 @@ class MenuManager {
|
|||||||
|
|
||||||
bool get isTransitioning => _transitionTarget != null;
|
bool get isTransitioning => _transitionTarget != null;
|
||||||
|
|
||||||
|
bool get isIntroSplashActive => _activeMenu == WolfMenuScreen.introSplash;
|
||||||
|
|
||||||
|
bool get isIntroPg13Slide => _introSlideIndex == 0;
|
||||||
|
|
||||||
|
int get introBackgroundRgb =>
|
||||||
|
isIntroPg13Slide ? _introPg13BackgroundRgb : _introTitleBackgroundRgb;
|
||||||
|
|
||||||
|
double get introOverlayAlpha {
|
||||||
|
if (!isIntroSplashActive) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
switch (_introPhase) {
|
||||||
|
case _WolfIntroPhase.fadeIn:
|
||||||
|
return (1.0 - (_introElapsedMs / introFadeDurationMs)).clamp(0.0, 1.0);
|
||||||
|
case _WolfIntroPhase.hold:
|
||||||
|
return 0.0;
|
||||||
|
case _WolfIntroPhase.fadeOut:
|
||||||
|
return (_introElapsedMs / introFadeDurationMs).clamp(0.0, 1.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the fade alpha during transitions (0.0..1.0).
|
/// Returns the fade alpha during transitions (0.0..1.0).
|
||||||
double get transitionAlpha {
|
double get transitionAlpha {
|
||||||
if (!isTransitioning) {
|
if (!isTransitioning) {
|
||||||
@@ -162,15 +200,33 @@ class MenuManager {
|
|||||||
: Difficulty.values
|
: Difficulty.values
|
||||||
.indexOf(initialDifficulty)
|
.indexOf(initialDifficulty)
|
||||||
.clamp(0, Difficulty.values.length - 1);
|
.clamp(0, Difficulty.values.length - 1);
|
||||||
_activeMenu = gameCount > 1
|
_introLandingMenu = WolfMenuScreen.mainMenu;
|
||||||
? WolfMenuScreen.gameSelect
|
if (gameCount > 1) {
|
||||||
: WolfMenuScreen.mainMenu;
|
_activeMenu = WolfMenuScreen.gameSelect;
|
||||||
|
_introElapsedMs = 0;
|
||||||
|
_introPhase = _WolfIntroPhase.fadeIn;
|
||||||
|
_introSlideIndex = 0;
|
||||||
|
} else {
|
||||||
|
_startIntroSequence();
|
||||||
|
}
|
||||||
_transitionTarget = null;
|
_transitionTarget = null;
|
||||||
_transitionElapsedMs = 0;
|
_transitionElapsedMs = 0;
|
||||||
_transitionSwappedMenu = false;
|
_transitionSwappedMenu = false;
|
||||||
_resetEdgeState();
|
_resetEdgeState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Starts the intro splash sequence and lands on [landingMenu] when done.
|
||||||
|
void beginIntroSplash({
|
||||||
|
WolfMenuScreen landingMenu = WolfMenuScreen.mainMenu,
|
||||||
|
}) {
|
||||||
|
_introLandingMenu = landingMenu;
|
||||||
|
_transitionTarget = null;
|
||||||
|
_transitionElapsedMs = 0;
|
||||||
|
_transitionSwappedMenu = false;
|
||||||
|
_startIntroSequence();
|
||||||
|
_resetEdgeState();
|
||||||
|
}
|
||||||
|
|
||||||
/// Resets menu navigation state for a new difficulty selection flow.
|
/// Resets menu navigation state for a new difficulty selection flow.
|
||||||
void beginDifficultySelection({Difficulty? initialDifficulty}) {
|
void beginDifficultySelection({Difficulty? initialDifficulty}) {
|
||||||
beginSelectionFlow(
|
beginSelectionFlow(
|
||||||
@@ -194,6 +250,7 @@ class MenuManager {
|
|||||||
_transitionTarget = null;
|
_transitionTarget = null;
|
||||||
_transitionElapsedMs = 0;
|
_transitionElapsedMs = 0;
|
||||||
_transitionSwappedMenu = false;
|
_transitionSwappedMenu = false;
|
||||||
|
_introElapsedMs = 0;
|
||||||
_resetEdgeState();
|
_resetEdgeState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,6 +270,11 @@ class MenuManager {
|
|||||||
|
|
||||||
/// Advances transition timers and swaps menu at midpoint.
|
/// Advances transition timers and swaps menu at midpoint.
|
||||||
void tickTransition(int deltaMs) {
|
void tickTransition(int deltaMs) {
|
||||||
|
if (isIntroSplashActive) {
|
||||||
|
_tickIntro(deltaMs);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!isTransitioning) {
|
if (!isTransitioning) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -229,6 +291,85 @@ class MenuManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void updateIntroSplash(EngineInput input) {
|
||||||
|
if (!isIntroSplashActive) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final bool confirmNow = input.isInteracting;
|
||||||
|
if (confirmNow && !_prevConfirm) {
|
||||||
|
if (_introPhase == _WolfIntroPhase.fadeOut) {
|
||||||
|
// Ignore repeat confirms while already transitioning out.
|
||||||
|
} else if (_introPhase == _WolfIntroPhase.hold) {
|
||||||
|
_introPhase = _WolfIntroPhase.fadeOut;
|
||||||
|
_introElapsedMs = 0;
|
||||||
|
} else {
|
||||||
|
// Queue advance while fade-in is still in progress.
|
||||||
|
_introAdvanceRequested = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_consumeEdgeState(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _startIntroSequence() {
|
||||||
|
_activeMenu = WolfMenuScreen.introSplash;
|
||||||
|
_introSlideIndex = 0;
|
||||||
|
_introElapsedMs = 0;
|
||||||
|
_introPhase = _WolfIntroPhase.fadeIn;
|
||||||
|
_introAdvanceRequested = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _tickIntro(int deltaMs) {
|
||||||
|
if (!isIntroSplashActive) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_introElapsedMs += deltaMs;
|
||||||
|
|
||||||
|
switch (_introPhase) {
|
||||||
|
case _WolfIntroPhase.fadeIn:
|
||||||
|
if (_introElapsedMs >= introFadeDurationMs) {
|
||||||
|
_introElapsedMs = 0;
|
||||||
|
if (_introAdvanceRequested) {
|
||||||
|
_introPhase = _WolfIntroPhase.fadeOut;
|
||||||
|
_introAdvanceRequested = false;
|
||||||
|
} else {
|
||||||
|
_introPhase = _WolfIntroPhase.hold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case _WolfIntroPhase.hold:
|
||||||
|
// Hold indefinitely until the user confirms.
|
||||||
|
_introElapsedMs = 0;
|
||||||
|
if (_introAdvanceRequested) {
|
||||||
|
_introPhase = _WolfIntroPhase.fadeOut;
|
||||||
|
_introAdvanceRequested = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case _WolfIntroPhase.fadeOut:
|
||||||
|
if (_introElapsedMs >= introFadeDurationMs) {
|
||||||
|
_advanceIntroSlide();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _advanceIntroSlide() {
|
||||||
|
if (_introSlideIndex < _introSlideCount - 1) {
|
||||||
|
_introSlideIndex += 1;
|
||||||
|
_introElapsedMs = 0;
|
||||||
|
_introPhase = _WolfIntroPhase.fadeIn;
|
||||||
|
_introAdvanceRequested = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_activeMenu = _introLandingMenu;
|
||||||
|
_introElapsedMs = 0;
|
||||||
|
_introPhase = _WolfIntroPhase.fadeIn;
|
||||||
|
_introAdvanceRequested = false;
|
||||||
|
}
|
||||||
|
|
||||||
void clearEpisodeSelection() {
|
void clearEpisodeSelection() {
|
||||||
_selectedEpisodeIndex = 0;
|
_selectedEpisodeIndex = 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -419,6 +419,11 @@ class AsciiRenderer extends CliRendererBackend<dynamic> {
|
|||||||
|
|
||||||
final art = WolfClassicMenuArt(engine.data);
|
final art = WolfClassicMenuArt(engine.data);
|
||||||
|
|
||||||
|
if (engine.menuManager.activeMenu == WolfMenuScreen.introSplash) {
|
||||||
|
_drawIntroSplash(engine, art);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (engine.menuManager.activeMenu == WolfMenuScreen.mainMenu) {
|
if (engine.menuManager.activeMenu == WolfMenuScreen.mainMenu) {
|
||||||
_fillRect320(68, 52, 178, 136, panelColor);
|
_fillRect320(68, 52, 178, 136, panelColor);
|
||||||
|
|
||||||
@@ -703,6 +708,59 @@ class AsciiRenderer extends CliRendererBackend<dynamic> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _drawIntroSplash(WolfEngine engine, WolfClassicMenuArt art) {
|
||||||
|
final image = engine.menuManager.isIntroPg13Slide
|
||||||
|
? art.mappedPic(WolfMenuPic.pg13)
|
||||||
|
: art.mappedPic(WolfMenuPic.title);
|
||||||
|
|
||||||
|
int splashBg = _rgbToPaletteColor(engine.menuManager.introBackgroundRgb);
|
||||||
|
if (engine.menuManager.isIntroPg13Slide &&
|
||||||
|
image != null &&
|
||||||
|
image.pixels.isNotEmpty) {
|
||||||
|
splashBg = ColorPalette.vga32Bit[_dominantPaletteIndex(image)];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_usesTerminalLayout) {
|
||||||
|
_fillTerminalRect(0, 0, width, _terminalPixelHeight, splashBg);
|
||||||
|
} else {
|
||||||
|
_fillRect(0, 0, width, height, activeTheme.solid, splashBg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (image != null) {
|
||||||
|
final int x = engine.menuManager.isIntroPg13Slide
|
||||||
|
? (320 - image.width).clamp(0, 319)
|
||||||
|
: ((320 - image.width) ~/ 2).clamp(0, 319);
|
||||||
|
final int y = engine.menuManager.isIntroPg13Slide
|
||||||
|
? (200 - image.height).clamp(0, 199)
|
||||||
|
: ((200 - image.height) ~/ 2).clamp(0, 199);
|
||||||
|
_blitVgaImageAscii(image, x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
_applyMenuFade(
|
||||||
|
engine.menuManager.introOverlayAlpha,
|
||||||
|
_rgbToPaletteColor(0x000000),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
int _dominantPaletteIndex(VgaImage image) {
|
||||||
|
final List<int> histogram = List<int>.filled(256, 0);
|
||||||
|
for (final int colorIndex in image.pixels) {
|
||||||
|
if (colorIndex >= 0 && colorIndex < histogram.length) {
|
||||||
|
histogram[colorIndex]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int bestIndex = 0;
|
||||||
|
int bestCount = -1;
|
||||||
|
for (int i = 0; i < histogram.length; i++) {
|
||||||
|
if (histogram[i] > bestCount) {
|
||||||
|
bestCount = histogram[i];
|
||||||
|
bestIndex = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bestIndex;
|
||||||
|
}
|
||||||
|
|
||||||
void _applyMenuFade(double alpha, int fadeColor) {
|
void _applyMenuFade(double alpha, int fadeColor) {
|
||||||
if (alpha <= 0.0) {
|
if (alpha <= 0.0) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -372,6 +372,11 @@ class SixelRenderer extends CliRendererBackend<String> {
|
|||||||
// Draw footer first so menu panels can clip overlap in the center.
|
// Draw footer first so menu panels can clip overlap in the center.
|
||||||
_drawMenuFooterArt(art);
|
_drawMenuFooterArt(art);
|
||||||
|
|
||||||
|
if (engine.menuManager.activeMenu == WolfMenuScreen.introSplash) {
|
||||||
|
_drawIntroSplash(engine, art);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (engine.menuManager.activeMenu == WolfMenuScreen.mainMenu) {
|
if (engine.menuManager.activeMenu == WolfMenuScreen.mainMenu) {
|
||||||
_fillRect320(68, 52, 178, 136, panelColor);
|
_fillRect320(68, 52, 178, 136, panelColor);
|
||||||
|
|
||||||
@@ -555,6 +560,57 @@ class SixelRenderer extends CliRendererBackend<String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _drawIntroSplash(WolfEngine engine, WolfClassicMenuArt art) {
|
||||||
|
final image = engine.menuManager.isIntroPg13Slide
|
||||||
|
? art.mappedPic(WolfMenuPic.pg13)
|
||||||
|
: art.mappedPic(WolfMenuPic.title);
|
||||||
|
|
||||||
|
int splashBg = _rgbToPaletteIndex(engine.menuManager.introBackgroundRgb);
|
||||||
|
if (engine.menuManager.isIntroPg13Slide &&
|
||||||
|
image != null &&
|
||||||
|
image.pixels.isNotEmpty) {
|
||||||
|
splashBg = _dominantPaletteIndex(image);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < _screen.length; i++) {
|
||||||
|
_screen[i] = splashBg;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (image != null) {
|
||||||
|
final int x = engine.menuManager.isIntroPg13Slide
|
||||||
|
? (320 - image.width).clamp(0, 319)
|
||||||
|
: ((320 - image.width) ~/ 2).clamp(0, 319);
|
||||||
|
final int y = engine.menuManager.isIntroPg13Slide
|
||||||
|
? (200 - image.height).clamp(0, 199)
|
||||||
|
: ((200 - image.height) ~/ 2).clamp(0, 199);
|
||||||
|
_blitVgaImage(image, x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
_applyMenuFade(
|
||||||
|
engine.menuManager.introOverlayAlpha,
|
||||||
|
_rgbToPaletteIndex(0x000000),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
int _dominantPaletteIndex(VgaImage image) {
|
||||||
|
final List<int> histogram = List<int>.filled(256, 0);
|
||||||
|
for (final int colorIndex in image.pixels) {
|
||||||
|
if (colorIndex >= 0 && colorIndex < histogram.length) {
|
||||||
|
histogram[colorIndex]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int bestIndex = 0;
|
||||||
|
int bestCount = -1;
|
||||||
|
for (int i = 0; i < histogram.length; i++) {
|
||||||
|
if (histogram[i] > bestCount) {
|
||||||
|
bestCount = histogram[i];
|
||||||
|
bestIndex = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bestIndex;
|
||||||
|
}
|
||||||
|
|
||||||
void _applyMenuFade(double alpha, int bgColor) {
|
void _applyMenuFade(double alpha, int bgColor) {
|
||||||
if (alpha <= 0.0) {
|
if (alpha <= 0.0) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -159,6 +159,9 @@ class SoftwareRenderer extends RendererBackend<FrameBuffer> {
|
|||||||
_drawCenteredMenuFooter(art);
|
_drawCenteredMenuFooter(art);
|
||||||
|
|
||||||
switch (engine.menuManager.activeMenu) {
|
switch (engine.menuManager.activeMenu) {
|
||||||
|
case WolfMenuScreen.introSplash:
|
||||||
|
_drawIntroSplash(engine, art);
|
||||||
|
break;
|
||||||
case WolfMenuScreen.mainMenu:
|
case WolfMenuScreen.mainMenu:
|
||||||
_drawMainMenu(
|
_drawMainMenu(
|
||||||
engine,
|
engine,
|
||||||
@@ -205,6 +208,99 @@ class SoftwareRenderer extends RendererBackend<FrameBuffer> {
|
|||||||
_applyMenuFade(engine.menuManager.transitionAlpha, bgColor);
|
_applyMenuFade(engine.menuManager.transitionAlpha, bgColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _drawIntroSplash(WolfEngine engine, WolfClassicMenuArt art) {
|
||||||
|
final image = engine.menuManager.isIntroPg13Slide
|
||||||
|
? art.mappedPic(WolfMenuPic.pg13)
|
||||||
|
: art.mappedPic(WolfMenuPic.title);
|
||||||
|
|
||||||
|
int splashBgColor = _rgbToFrameColor(engine.menuManager.introBackgroundRgb);
|
||||||
|
int? matteIndex;
|
||||||
|
if (engine.menuManager.isIntroPg13Slide &&
|
||||||
|
image != null &&
|
||||||
|
image.pixels.isNotEmpty) {
|
||||||
|
matteIndex = _dominantPaletteIndex(image);
|
||||||
|
splashBgColor =
|
||||||
|
ColorPalette.vga32Bit[_pg13BackgroundPaletteIndex(image, matteIndex)];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < _buffer.pixels.length; i++) {
|
||||||
|
_buffer.pixels[i] = splashBgColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (image != null) {
|
||||||
|
final int x = engine.menuManager.isIntroPg13Slide
|
||||||
|
? (320 - image.width).clamp(0, 319)
|
||||||
|
: ((320 - image.width) ~/ 2).clamp(0, 319);
|
||||||
|
final int y = engine.menuManager.isIntroPg13Slide
|
||||||
|
? (200 - image.height).clamp(0, 199)
|
||||||
|
: ((200 - image.height) ~/ 2).clamp(0, 199);
|
||||||
|
if (engine.menuManager.isIntroPg13Slide && matteIndex != null) {
|
||||||
|
_blitVgaImage(image, x, y, transparentIndex: matteIndex);
|
||||||
|
} else {
|
||||||
|
_blitVgaImage(image, x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_applyMenuFade(
|
||||||
|
engine.menuManager.introOverlayAlpha,
|
||||||
|
_rgbToFrameColor(0x000000),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
int _dominantPaletteIndex(VgaImage image) {
|
||||||
|
final List<int> histogram = List<int>.filled(256, 0);
|
||||||
|
for (final int colorIndex in image.pixels) {
|
||||||
|
if (colorIndex >= 0 && colorIndex < histogram.length) {
|
||||||
|
histogram[colorIndex]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int bestIndex = 0;
|
||||||
|
int bestCount = -1;
|
||||||
|
for (int i = 0; i < histogram.length; i++) {
|
||||||
|
if (histogram[i] > bestCount) {
|
||||||
|
bestCount = histogram[i];
|
||||||
|
bestIndex = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bestIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
int _pg13BackgroundPaletteIndex(VgaImage image, int matteIndex) {
|
||||||
|
final List<(int x, int y)> probes = <(int x, int y)>[
|
||||||
|
(0, 0),
|
||||||
|
(image.width - 1, 0),
|
||||||
|
(0, image.height - 1),
|
||||||
|
(image.width - 1, image.height - 1),
|
||||||
|
(image.width - 8, image.height - 8),
|
||||||
|
(image.width - 16, image.height - 16),
|
||||||
|
];
|
||||||
|
final Map<int, int> counts = <int, int>{};
|
||||||
|
for (final probe in probes) {
|
||||||
|
final int x = probe.$1.clamp(0, image.width - 1);
|
||||||
|
final int y = probe.$2.clamp(0, image.height - 1);
|
||||||
|
final int idx = image.decodePixel(x, y);
|
||||||
|
if (idx == matteIndex) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
counts[idx] = (counts[idx] ?? 0) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (counts.isEmpty) {
|
||||||
|
return matteIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bestIndex = counts.keys.first;
|
||||||
|
int bestCount = counts[bestIndex] ?? 0;
|
||||||
|
for (final MapEntry<int, int> entry in counts.entries) {
|
||||||
|
if (entry.value > bestCount) {
|
||||||
|
bestCount = entry.value;
|
||||||
|
bestIndex = entry.key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bestIndex;
|
||||||
|
}
|
||||||
|
|
||||||
void _drawMainMenu(
|
void _drawMainMenu(
|
||||||
WolfEngine engine,
|
WolfEngine engine,
|
||||||
WolfClassicMenuArt art,
|
WolfClassicMenuArt art,
|
||||||
@@ -663,7 +759,12 @@ class SoftwareRenderer extends RendererBackend<FrameBuffer> {
|
|||||||
///
|
///
|
||||||
/// UI coordinates are expressed in canonical 320x200 space and scaled to the
|
/// UI coordinates are expressed in canonical 320x200 space and scaled to the
|
||||||
/// current framebuffer so higher-resolution render targets preserve layout.
|
/// current framebuffer so higher-resolution render targets preserve layout.
|
||||||
void _blitVgaImage(VgaImage image, int startX, int startY) {
|
void _blitVgaImage(
|
||||||
|
VgaImage image,
|
||||||
|
int startX,
|
||||||
|
int startY, {
|
||||||
|
int? transparentIndex,
|
||||||
|
}) {
|
||||||
final int destStartX = (startX * _uiScaleX).floor();
|
final int destStartX = (startX * _uiScaleX).floor();
|
||||||
final int destStartY = (startY * _uiScaleY).floor();
|
final int destStartY = (startY * _uiScaleY).floor();
|
||||||
final int destWidth = math.max(1, (image.width * _uiScaleX).ceil());
|
final int destWidth = math.max(1, (image.width * _uiScaleX).ceil());
|
||||||
@@ -678,7 +779,7 @@ class SoftwareRenderer extends RendererBackend<FrameBuffer> {
|
|||||||
final int srcX = (dx / _uiScaleX).toInt().clamp(0, image.width - 1);
|
final int srcX = (dx / _uiScaleX).toInt().clamp(0, image.width - 1);
|
||||||
final int srcY = (dy / _uiScaleY).toInt().clamp(0, image.height - 1);
|
final int srcY = (dy / _uiScaleY).toInt().clamp(0, image.height - 1);
|
||||||
final int colorByte = image.decodePixel(srcX, srcY);
|
final int colorByte = image.decodePixel(srcX, srcY);
|
||||||
if (colorByte != 255) {
|
if (colorByte != 255 && colorByte != transparentIndex) {
|
||||||
_buffer.pixels[drawY * width + drawX] =
|
_buffer.pixels[drawY * width + drawX] =
|
||||||
ColorPalette.vga32Bit[colorByte];
|
ColorPalette.vga32Bit[colorByte];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,9 +53,7 @@ class _WolfGlslRendererState extends BaseWolfRendererState<WolfGlslRenderer> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Color get scaffoldColor => widget.engine.difficulty == null
|
Color get scaffoldColor => Colors.black;
|
||||||
? _colorFromRgb(widget.engine.menuBackgroundRgb)
|
|
||||||
: const Color.fromARGB(255, 4, 64, 64);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
@@ -149,10 +147,6 @@ class _WolfGlslRendererState extends BaseWolfRendererState<WolfGlslRenderer> {
|
|||||||
widget.onUnavailable?.call();
|
widget.onUnavailable?.call();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Color _colorFromRgb(int rgb) {
|
|
||||||
return Color(0xFF000000 | (rgb & 0x00FFFFFF));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class _GlslFramePainter extends CustomPainter {
|
class _GlslFramePainter extends CustomPainter {
|
||||||
|
|||||||
Reference in New Issue
Block a user