|
|
|
|
@@ -203,9 +203,9 @@ class SoftwareRenderer extends RendererBackend<FrameBuffer> {
|
|
|
|
|
const int panelY = 58;
|
|
|
|
|
const int panelW = 264;
|
|
|
|
|
const int panelH = 104;
|
|
|
|
|
_fillMenuPanel(panelX, panelY, panelW, panelH, panelColor);
|
|
|
|
|
_fillCanonicalRect(panelX, panelY, panelW, panelH, panelColor);
|
|
|
|
|
|
|
|
|
|
_drawMenuTextCentered('SELECT GAME', 38, headingColor, scale: 2);
|
|
|
|
|
_drawCanonicalMenuTextCentered('SELECT GAME', 38, headingColor, scale: 2);
|
|
|
|
|
|
|
|
|
|
final cursor = art.mappedPic(
|
|
|
|
|
engine.menuManager.isCursorAltFrame(engine.timeAliveMs) ? 9 : 8,
|
|
|
|
|
@@ -222,7 +222,7 @@ class SoftwareRenderer extends RendererBackend<FrameBuffer> {
|
|
|
|
|
if (isSelected && cursor != null) {
|
|
|
|
|
_blitVgaImage(cursor, panelX + 10, y - 2);
|
|
|
|
|
}
|
|
|
|
|
_drawMenuText(
|
|
|
|
|
_drawCanonicalMenuText(
|
|
|
|
|
_gameTitle(engine.availableGames[i].version),
|
|
|
|
|
textX,
|
|
|
|
|
y,
|
|
|
|
|
@@ -243,9 +243,14 @@ class SoftwareRenderer extends RendererBackend<FrameBuffer> {
|
|
|
|
|
const int panelY = 20;
|
|
|
|
|
const int panelW = 296;
|
|
|
|
|
const int panelH = 158;
|
|
|
|
|
_fillMenuPanel(panelX, panelY, panelW, panelH, panelColor);
|
|
|
|
|
_fillCanonicalRect(panelX, panelY, panelW, panelH, panelColor);
|
|
|
|
|
|
|
|
|
|
_drawMenuTextCentered('WHICH EPISODE TO PLAY?', 6, headingColor, scale: 2);
|
|
|
|
|
_drawCanonicalMenuTextCentered(
|
|
|
|
|
'WHICH EPISODE TO PLAY?',
|
|
|
|
|
6,
|
|
|
|
|
headingColor,
|
|
|
|
|
scale: 2,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
final cursor = art.mappedPic(
|
|
|
|
|
engine.menuManager.isCursorAltFrame(engine.timeAliveMs) ? 9 : 8,
|
|
|
|
|
@@ -271,7 +276,7 @@ class SoftwareRenderer extends RendererBackend<FrameBuffer> {
|
|
|
|
|
|
|
|
|
|
final parts = engine.data.episodes[i].name.split('\n');
|
|
|
|
|
if (parts.isNotEmpty) {
|
|
|
|
|
_drawMenuText(
|
|
|
|
|
_drawCanonicalMenuText(
|
|
|
|
|
parts.first,
|
|
|
|
|
textX,
|
|
|
|
|
y + 1,
|
|
|
|
|
@@ -279,7 +284,7 @@ class SoftwareRenderer extends RendererBackend<FrameBuffer> {
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
if (parts.length > 1) {
|
|
|
|
|
_drawMenuText(
|
|
|
|
|
_drawCanonicalMenuText(
|
|
|
|
|
parts.sublist(1).join(' '),
|
|
|
|
|
textX,
|
|
|
|
|
y + 12,
|
|
|
|
|
@@ -292,8 +297,8 @@ class SoftwareRenderer extends RendererBackend<FrameBuffer> {
|
|
|
|
|
void _drawCenteredMenuFooter(WolfClassicMenuArt art) {
|
|
|
|
|
final bottom = art.mappedPic(15);
|
|
|
|
|
if (bottom != null) {
|
|
|
|
|
final int x = ((width - bottom.width) ~/ 2).clamp(0, width - 1);
|
|
|
|
|
final int y = (height - bottom.height - 8).clamp(0, height - 1);
|
|
|
|
|
final int x = ((320 - bottom.width) ~/ 2).clamp(0, 319);
|
|
|
|
|
final int y = (200 - bottom.height - 8).clamp(0, 199);
|
|
|
|
|
_blitVgaImage(bottom, x, y);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
@@ -316,9 +321,9 @@ class SoftwareRenderer extends RendererBackend<FrameBuffer> {
|
|
|
|
|
textWidth += WolfMenuFont.measureTextWidth(text, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
final int panelWidth = (textWidth + 12).clamp(1, width);
|
|
|
|
|
final int panelX = ((width - panelWidth) ~/ 2).clamp(0, width - 1);
|
|
|
|
|
_fillMenuPanel(
|
|
|
|
|
final int panelWidth = (textWidth + 12).clamp(1, 320);
|
|
|
|
|
final int panelX = ((320 - panelWidth) ~/ 2).clamp(0, 319);
|
|
|
|
|
_fillCanonicalRect(
|
|
|
|
|
panelX,
|
|
|
|
|
_menuFooterY,
|
|
|
|
|
panelWidth,
|
|
|
|
|
@@ -329,7 +334,7 @@ class SoftwareRenderer extends RendererBackend<FrameBuffer> {
|
|
|
|
|
int cursorX = panelX + 6;
|
|
|
|
|
const int textY = _menuFooterY + 2;
|
|
|
|
|
for (final (text, color) in segments) {
|
|
|
|
|
_drawMenuText(text, cursorX, textY, color, scale: 1);
|
|
|
|
|
_drawCanonicalMenuText(text, cursorX, textY, color, scale: 1);
|
|
|
|
|
cursorX += WolfMenuFont.measureTextWidth(text, 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -348,14 +353,19 @@ class SoftwareRenderer extends RendererBackend<FrameBuffer> {
|
|
|
|
|
const int panelY = 70;
|
|
|
|
|
const int panelW = 264;
|
|
|
|
|
const int panelH = 82;
|
|
|
|
|
_fillMenuPanel(panelX, panelY, panelW, panelH, panelColor);
|
|
|
|
|
_fillCanonicalRect(panelX, panelY, panelW, panelH, panelColor);
|
|
|
|
|
|
|
|
|
|
_drawMenuTextCentered(Difficulty.menuText, 48, headingColor, scale: 2);
|
|
|
|
|
_drawCanonicalMenuTextCentered(
|
|
|
|
|
Difficulty.menuText,
|
|
|
|
|
48,
|
|
|
|
|
headingColor,
|
|
|
|
|
scale: 2,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
final bottom = art.mappedPic(15);
|
|
|
|
|
if (bottom != null) {
|
|
|
|
|
final x = (width - bottom.width) ~/ 2;
|
|
|
|
|
final y = height - bottom.height - 8;
|
|
|
|
|
final int x = ((320 - bottom.width) ~/ 2).clamp(0, 319);
|
|
|
|
|
final int y = (200 - bottom.height - 8).clamp(0, 199);
|
|
|
|
|
_blitVgaImage(bottom, x, y);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -380,7 +390,7 @@ class SoftwareRenderer extends RendererBackend<FrameBuffer> {
|
|
|
|
|
_blitVgaImage(cursor, panelX + 10, y - 2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_drawMenuText(
|
|
|
|
|
_drawCanonicalMenuText(
|
|
|
|
|
Difficulty.values[i].title,
|
|
|
|
|
textX,
|
|
|
|
|
y,
|
|
|
|
|
@@ -453,6 +463,74 @@ class SoftwareRenderer extends RendererBackend<FrameBuffer> {
|
|
|
|
|
return (0xFF000000) | (b << 16) | (g << 8) | r;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
double get _uiScaleX => width / 320.0;
|
|
|
|
|
|
|
|
|
|
double get _uiScaleY => height / 200.0;
|
|
|
|
|
|
|
|
|
|
void _fillCanonicalRect(
|
|
|
|
|
int startX320,
|
|
|
|
|
int startY200,
|
|
|
|
|
int width320,
|
|
|
|
|
int height200,
|
|
|
|
|
int color,
|
|
|
|
|
) {
|
|
|
|
|
final int startX = (startX320 * _uiScaleX).floor();
|
|
|
|
|
final int endX = ((startX320 + width320) * _uiScaleX).ceil();
|
|
|
|
|
final int startY = (startY200 * _uiScaleY).floor();
|
|
|
|
|
final int endY = ((startY200 + height200) * _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) {
|
|
|
|
|
_buffer.pixels[rowStart + x] = color;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void _drawCanonicalMenuText(
|
|
|
|
|
String text,
|
|
|
|
|
int startX320,
|
|
|
|
|
int startY200,
|
|
|
|
|
int color, {
|
|
|
|
|
int scale = 1,
|
|
|
|
|
}) {
|
|
|
|
|
int x320 = startX320;
|
|
|
|
|
for (final rune in text.runes) {
|
|
|
|
|
final String char = String.fromCharCode(rune).toUpperCase();
|
|
|
|
|
final List<String> pattern = WolfMenuFont.glyphFor(char);
|
|
|
|
|
|
|
|
|
|
for (int row = 0; row < pattern.length; row++) {
|
|
|
|
|
final String bits = pattern[row];
|
|
|
|
|
for (int col = 0; col < bits.length; col++) {
|
|
|
|
|
if (bits[col] != '1') continue;
|
|
|
|
|
_fillCanonicalRect(
|
|
|
|
|
x320 + (col * scale),
|
|
|
|
|
startY200 + (row * scale),
|
|
|
|
|
scale,
|
|
|
|
|
scale,
|
|
|
|
|
color,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
x320 += WolfMenuFont.glyphAdvance(char, scale);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void _drawCanonicalMenuTextCentered(
|
|
|
|
|
String text,
|
|
|
|
|
int y200,
|
|
|
|
|
int color, {
|
|
|
|
|
int scale = 1,
|
|
|
|
|
}) {
|
|
|
|
|
final int textWidth = WolfMenuFont.measureTextWidth(text, scale);
|
|
|
|
|
final int x320 = ((320 - textWidth) ~/ 2).clamp(0, 319);
|
|
|
|
|
_drawCanonicalMenuText(text, x320, y200, color, scale: scale);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Draws bitmap menu text directly into the framebuffer.
|
|
|
|
|
void _drawMenuText(
|
|
|
|
|
String text,
|
|
|
|
|
@@ -486,18 +564,6 @@ class SoftwareRenderer extends RendererBackend<FrameBuffer> {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Draws bitmap menu text centered in the current framebuffer width.
|
|
|
|
|
void _drawMenuTextCentered(
|
|
|
|
|
String text,
|
|
|
|
|
int y,
|
|
|
|
|
int color, {
|
|
|
|
|
int scale = 1,
|
|
|
|
|
}) {
|
|
|
|
|
final int textWidth = WolfMenuFont.measureTextWidth(text, scale);
|
|
|
|
|
final int x = ((width - textWidth) ~/ 2).clamp(0, width - 1);
|
|
|
|
|
_drawMenuText(text, x, y, color, scale: scale);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
FrameBuffer finalizeFrame() {
|
|
|
|
|
// If the player took damage, overlay a red tint across the 3D view
|
|
|
|
|
@@ -513,17 +579,23 @@ class SoftwareRenderer extends RendererBackend<FrameBuffer> {
|
|
|
|
|
|
|
|
|
|
/// Maps planar VGA image data directly to 32-bit framebuffer pixels.
|
|
|
|
|
///
|
|
|
|
|
/// This renderer assumes a 1:1 mapping with the canonical 320x200 layout.
|
|
|
|
|
/// UI coordinates are expressed in canonical 320x200 space and scaled to the
|
|
|
|
|
/// current framebuffer so higher-resolution render targets preserve layout.
|
|
|
|
|
void _blitVgaImage(VgaImage image, int startX, int startY) {
|
|
|
|
|
for (int dy = 0; dy < image.height; dy++) {
|
|
|
|
|
for (int dx = 0; dx < image.width; dx++) {
|
|
|
|
|
int drawX = startX + dx;
|
|
|
|
|
int drawY = startY + dy;
|
|
|
|
|
final int destStartX = (startX * _uiScaleX).floor();
|
|
|
|
|
final int destStartY = (startY * _uiScaleY).floor();
|
|
|
|
|
final int destWidth = math.max(1, (image.width * _uiScaleX).ceil());
|
|
|
|
|
final int destHeight = math.max(1, (image.height * _uiScaleY).ceil());
|
|
|
|
|
|
|
|
|
|
for (int dy = 0; dy < destHeight; dy++) {
|
|
|
|
|
for (int dx = 0; dx < destWidth; dx++) {
|
|
|
|
|
final int drawX = destStartX + dx;
|
|
|
|
|
final int drawY = destStartY + dy;
|
|
|
|
|
|
|
|
|
|
if (drawX >= 0 && drawX < width && drawY >= 0 && drawY < height) {
|
|
|
|
|
int srcX = dx.clamp(0, image.width - 1);
|
|
|
|
|
int srcY = dy.clamp(0, image.height - 1);
|
|
|
|
|
int colorByte = image.decodePixel(srcX, srcY);
|
|
|
|
|
final int srcX = (dx / _uiScaleX).toInt().clamp(0, image.width - 1);
|
|
|
|
|
final int srcY = (dy / _uiScaleY).toInt().clamp(0, image.height - 1);
|
|
|
|
|
final int colorByte = image.decodePixel(srcX, srcY);
|
|
|
|
|
if (colorByte != 255) {
|
|
|
|
|
_buffer.pixels[drawY * width + drawX] =
|
|
|
|
|
ColorPalette.vga32Bit[colorByte];
|
|
|
|
|
|