diff --git a/Source/Emulator/video.c b/Source/Emulator/video.c index 908b9c8..ea558c7 100644 --- a/Source/Emulator/video.c +++ b/Source/Emulator/video.c @@ -38,8 +38,14 @@ int videoRows(void) { return rowsFor(mode); } // Only two, and the rest of the palette left at zero. A program that wants colour sets it, // and a machine that guessed sixteen entries on its behalf would be sixteen entries it had // to overwrite. What it must not do is wake up unable to show text at all. -static const uint8_t defaultInk[3] = { 0xDC, 0xE6, 0xDC }; -static const uint8_t defaultPaper[3] = { 0x10, 0x14, 0x12 }; +// +// BLACK IS BLACK AND GREY IS GREY. These were tinted towards green to begin with, on the +// theory that a phosphor never was neutral, and on a real screen it read as a fault rather +// than as character - a background that is nearly black looks like a background that failed +// to be black. A default should be the unsurprising thing; anything with a point of view +// about colour is 254 entries away and belongs to a program. +static const uint8_t defaultInk[3] = { 0xD8, 0xD8, 0xD8 }; +static const uint8_t defaultPaper[3] = { 0x00, 0x00, 0x00 }; void videoLoadFont(void) { // One bit a pixel becomes one byte a pixel: index 1 where the font has a dot and 0 diff --git a/Source/Emulator/voyager.c b/Source/Emulator/voyager.c index a6e2d65..dd721e5 100644 --- a/Source/Emulator/voyager.c +++ b/Source/Emulator/voyager.c @@ -29,9 +29,8 @@ #include #include -// The window is the largest screen the video device can produce, scaled up because a 320 by -// 200 window is a postage stamp on a modern display. A smaller mode is drawn into the middle -// of it rather than resizing the window out from under whoever is looking at it. +// The window opens at the largest screen the device can produce, doubled, because a 640 by +// 400 window is small on a modern display and a 320 by 200 one is a postage stamp. #define SCREEN_SCALE 2 // ---- Running without a window ---- @@ -79,16 +78,32 @@ static void presentFrame(void) { frame); } BeginDrawing(); - // Not black. The border around a smaller mode should look like a machine with a screen - // on, rather than like a window that failed to open. - ClearBackground((Color){ 18, 22, 20, 255 }); + // Clearly not the screen. What is left over when the window's shape does not match the + // picture's is a bezel, and it should look like one rather than like more screen. + ClearBackground((Color){ 40, 40, 40, 255 }); if (width > 0 && height > 0) { - // Centred, so changing mode moves the picture rather than the window. + // ---- Filling the window, in whole pixels ---- + // + // The largest whole-number scale that still fits. Whole numbers because a 320 by 200 + // picture stretched by 2.7 is a picture with some rows twice as tall as their + // neighbours, which on eight pixel glyphs is the difference between text and mush. + // + // The two modes are exactly a factor of two apart and the window opens at twice the + // larger, so both fill it exactly: 320 by 200 at four, and 640 by 400 at two. + // Changing mode therefore changes how sharp the screen is and not how big it is. + const int windowWidth = GetScreenWidth(); + const int windowHeight = GetScreenHeight(); + int scale = windowWidth / width; + const int fits = windowHeight / height; + if (fits < scale) scale = fits; + if (scale < 1) scale = 1; + const int drawnWidth = width * scale; + const int drawnHeight = height * scale; Rectangle from = { 0, 0, (float)width, (float)height }; Rectangle to = { - (float)((VIDEO_MAX_WIDTH * SCREEN_SCALE - width * SCREEN_SCALE) / 2), - (float)((VIDEO_MAX_HEIGHT * SCREEN_SCALE - height * SCREEN_SCALE) / 2), - (float)(width * SCREEN_SCALE), (float)(height * SCREEN_SCALE) + (float)((windowWidth - drawnWidth) / 2), + (float)((windowHeight - drawnHeight) / 2), + (float)drawnWidth, (float)drawnHeight }; DrawTexturePro(screenTexture, from, to, (Vector2){ 0, 0 }, 0.0f, WHITE); } @@ -156,6 +171,9 @@ int main(int argc, char *argv[]) { machineRunSlice(&machine); } } else { + // Resizable, because how big somebody wants a screen is not the machine's business. + // The picture is rescaled to whatever the window becomes, in whole pixels. + SetConfigFlags(FLAG_WINDOW_RESIZABLE); InitWindow(VIDEO_MAX_WIDTH * SCREEN_SCALE, VIDEO_MAX_HEIGHT * SCREEN_SCALE, "Segan Voyager"); SetTargetFPS(60); diff --git a/Tests/video.sh b/Tests/video.sh index 5f46f53..40e07f4 100755 --- a/Tests/video.sh +++ b/Tests/video.sh @@ -144,10 +144,10 @@ echo "Checking what the video device draws." epilogue } | run wakeup || exit 1 -[ "$(pixel wakeup 0 0)" = "16,20,18" ] \ - && result ok "the machine wakes with paper" "before any program set one" \ +[ "$(pixel wakeup 0 0)" = "0,0,0" ] \ + && result ok "the machine wakes with paper" "black, before any program set one" \ || result no "the machine wakes with paper" "got $(pixel wakeup 0 0)" -[ "$(pixel wakeup 2 1)" = "220,230,220" ] \ +[ "$(pixel wakeup 2 1)" = "216,216,216" ] \ && result ok "and with a font to write in" "a letter A, drawn in ink" \ || result no "and with a font to write in" "got $(pixel wakeup 2 1)" @@ -286,8 +286,8 @@ GOT="$(head -c 1 "$BUILD/badmode.out" | od -An -tu1 | tr -d ' ')" # # 'A' has ink at (2,1) inside its cell and paper at the corner, which is what makes a letter # tellable from an empty cell one pixel at a time. -inked() { [ "$(pixel "$1" "$2" "$3")" = "220,230,220" ]; } -papered() { [ "$(pixel "$1" "$2" "$3")" = "16,20,18" ]; } +inked() { [ "$(pixel "$1" "$2" "$3")" = "216,216,216" ]; } +papered() { [ "$(pixel "$1" "$2" "$3")" = "0,0,0" ]; } { printf '#Program\nstart:\n'; say "AA"; epilogue; } | run twoletters || exit 1 inked twoletters 2 1 \