290 lines
8.1 KiB
C++
290 lines
8.1 KiB
C++
#include <SFML/Graphics.hpp>
|
|
#include <vector>
|
|
#include <cstdint>
|
|
#include <cmath>
|
|
#include <unistd.h>
|
|
#include <sys/select.h>
|
|
#include <termios.h>
|
|
#include "./fonts/acme_5_outlines_font.h"
|
|
|
|
const int V_WIDTH = 400;
|
|
const int V_HEIGHT = 300;
|
|
uint8_t bit_buffer[V_WIDTH * V_HEIGHT / 8];
|
|
|
|
// --- 1-BIT DRAWING PRIMITIVES ---
|
|
|
|
void set_pixel(int x, int y, bool on)
|
|
{
|
|
if (x < 0 || x >= V_WIDTH || y < 0 || y >= V_HEIGHT)
|
|
return;
|
|
int bit_pos = y * V_WIDTH + x;
|
|
if (on)
|
|
bit_buffer[bit_pos / 8] |= (1 << (7 - (bit_pos % 8)));
|
|
else
|
|
bit_buffer[bit_pos / 8] &= ~(1 << (7 - (bit_pos % 8)));
|
|
}
|
|
|
|
void draw_line(int x0, int y0, int x1, int y1, bool on)
|
|
{
|
|
int dx = abs(x1 - x0), sx = x0 < x1 ? 1 : -1;
|
|
int dy = -abs(y1 - y0), sy = y0 < y1 ? 1 : -1;
|
|
int err = dx + dy, e2;
|
|
while (true)
|
|
{
|
|
set_pixel(x0, y0, on);
|
|
if (x0 == x1 && y0 == y1)
|
|
break;
|
|
e2 = 2 * err;
|
|
if (e2 >= dy)
|
|
{
|
|
err += dy;
|
|
x0 += sx;
|
|
}
|
|
if (e2 <= dx)
|
|
{
|
|
err += dx;
|
|
y0 += sy;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Minimal 8x8 Bitmap Font (Example for 'A' and 'B')
|
|
void draw_char(int x, int y, char c)
|
|
{
|
|
static const uint8_t font[2][8] = {
|
|
{0x18, 0x3C, 0x66, 0x66, 0x7E, 0x66, 0x66, 0x00}, // A
|
|
{0x7C, 0x66, 0x66, 0x7C, 0x66, 0x66, 0x7C, 0x00} // B
|
|
};
|
|
int idx = (c == 'A') ? 0 : 1;
|
|
for (int row = 0; row < 8; row++)
|
|
{
|
|
for (int col = 0; col < 8; col++)
|
|
{
|
|
if (font[idx][row] & (1 << (7 - col)))
|
|
set_pixel(x + col, y + row, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Draws a single character using the 6x8 vertical column format
|
|
* @param x Start x position
|
|
* @param y Start y position
|
|
* @param c The character to draw
|
|
*/
|
|
void draw_char_vcol(int x, int y, char c)
|
|
{
|
|
// The font table starts at space (ASCII 32)
|
|
if (c < 32 || c > 127)
|
|
return;
|
|
int font_idx = c - 32;
|
|
|
|
for (int col = 0; col < 6; col++)
|
|
{
|
|
unsigned char column_byte = font[font_idx][col];
|
|
|
|
for (int row = 0; row < 8; row++)
|
|
{
|
|
// Check if the bit for this row is set
|
|
// Most of these 1-bit fonts use bit 0 as the top pixel
|
|
if (column_byte & (1 << row))
|
|
{
|
|
set_pixel(x + col, y + row, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Draws a full string of text
|
|
* @param spacing pixels between characters (usually 1 or 2)
|
|
*/
|
|
void draw_string(int x, int y, const std::string &text, int spacing = 1)
|
|
{
|
|
for (size_t i = 0; i < text.length(); i++)
|
|
{
|
|
draw_char_vcol(x + (i * (6 + spacing)), y, text[i]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Draws a scaled character using the 6x8 vertical column format
|
|
* @param scale Integer multiplier (1 = 1x, 2 = 2x, etc.)
|
|
*/
|
|
void draw_char_scaled(int x, int y, char c, int scale)
|
|
{
|
|
if (c < 32 || c > 127)
|
|
return;
|
|
if (scale < 1)
|
|
scale = 1; // Safety check
|
|
|
|
int font_idx = c - 32;
|
|
|
|
for (int col = 0; col < 6; col++)
|
|
{
|
|
unsigned char column_byte = font[font_idx][col];
|
|
|
|
for (int row = 0; row < 8; row++)
|
|
{
|
|
if (column_byte & (1 << row))
|
|
{
|
|
// Draw a square of size [scale x scale]
|
|
for (int sy = 0; sy < scale; sy++)
|
|
{
|
|
for (int sx = 0; sx < scale; sx++)
|
|
{
|
|
set_pixel(x + (col * scale) + sx,
|
|
y + (row * scale) + sy,
|
|
true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Draws a scaled string
|
|
*/
|
|
void draw_string_scaled(int x, int y, const char* text, int scale, int spacing = 1)
|
|
{
|
|
int i = 0;
|
|
while(text[i] != '\0')
|
|
{
|
|
// We multiply the character width (6) and spacing by the scale
|
|
int next_x = x + (i * (6 + spacing) * scale);
|
|
draw_char_scaled(next_x, y, text[i], scale);
|
|
i++;
|
|
}
|
|
}
|
|
|
|
char command_buffer[256];
|
|
int command_buffer_index = 0;
|
|
|
|
int main()
|
|
{
|
|
// Save original terminal settings
|
|
struct termios old_tio, new_tio;
|
|
tcgetattr(STDIN_FILENO, &old_tio);
|
|
new_tio = old_tio;
|
|
new_tio.c_lflag &= (~ICANON & ~ECHO); // Disable canonical mode and echo
|
|
tcsetattr(STDIN_FILENO, TCSANOW, &new_tio);
|
|
|
|
sf::RenderWindow window(sf::VideoMode(400, 300), "0.5Hz Update Emulator");
|
|
|
|
std::vector<sf::Uint8> display_pixels(V_WIDTH * V_HEIGHT * 4);
|
|
sf::Texture texture;
|
|
texture.create(V_WIDTH, V_HEIGHT);
|
|
texture.setSmooth(false); // Keeps the 1-bit pixels sharp and blocky
|
|
sf::Sprite sprite(texture);
|
|
sprite.setScale(1.f, 1.f);
|
|
|
|
sf::Clock clock;
|
|
bool toggle = false;
|
|
|
|
int counter = 0;
|
|
|
|
while (window.isOpen())
|
|
{
|
|
sf::Event event;
|
|
while (window.pollEvent(event))
|
|
{
|
|
if (event.type == sf::Event::Closed)
|
|
window.close();
|
|
}
|
|
|
|
// Check for console input with timeout
|
|
fd_set readfds;
|
|
FD_ZERO(&readfds);
|
|
FD_SET(STDIN_FILENO, &readfds);
|
|
struct timeval tv;
|
|
tv.tv_sec = 0;
|
|
tv.tv_usec = 10000; // 10ms timeout
|
|
int retval = select(STDIN_FILENO + 1, &readfds, NULL, NULL, &tv);
|
|
if (retval > 0)
|
|
{
|
|
char c = getchar();
|
|
//printf("Key Pressed: %c\n", c);
|
|
if (c == '\n' || c == '\r')
|
|
{
|
|
printf("Command entered: %s\n", command_buffer);
|
|
command_buffer_index = 0;
|
|
command_buffer[0] = '\0';
|
|
}
|
|
else if (c == '\b' || c == 127) // Backspace
|
|
{
|
|
if (command_buffer_index > 0)
|
|
{
|
|
command_buffer_index--;
|
|
command_buffer[command_buffer_index] = '\0';
|
|
}
|
|
}
|
|
else if (c >= 32 && c <= 126) // Printable characters
|
|
{
|
|
if (command_buffer_index < sizeof(command_buffer) - 1)
|
|
{
|
|
command_buffer[command_buffer_index++] = c;
|
|
command_buffer[command_buffer_index] = '\0';
|
|
}
|
|
}
|
|
|
|
// Update display immediately after input
|
|
// Clear buffer
|
|
for (int i = 0; i < sizeof(bit_buffer); i++)
|
|
bit_buffer[i] = 0;
|
|
|
|
// Draw content
|
|
draw_string_scaled(10, 10, "Hello World!", 3);
|
|
draw_string_scaled(10, 270, command_buffer, 1);
|
|
|
|
// Bridge: 1-bit to RGBA
|
|
for (int i = 0; i < V_WIDTH * V_HEIGHT; ++i)
|
|
{
|
|
bool is_on = (bit_buffer[i / 8] >> (7 - (i % 8))) & 1;
|
|
int base = i * 4;
|
|
sf::Uint8 color = is_on ? 0xFF : 0x00;
|
|
display_pixels[base] = display_pixels[base + 1] = display_pixels[base + 2] = color;
|
|
display_pixels[base + 3] = 255;
|
|
}
|
|
texture.update(display_pixels.data());
|
|
}
|
|
|
|
// --- TIMING LOGIC: Update twice per second (500ms) ---
|
|
if (clock.getElapsedTime().asMilliseconds() >= 1000)
|
|
{
|
|
//printf("Updating display %d...\n", counter++);
|
|
clock.restart();
|
|
toggle = !toggle; // Change something every half second
|
|
|
|
// Clear buffer
|
|
for (int i = 0; i < sizeof(bit_buffer); i++)
|
|
bit_buffer[i] = 0;
|
|
|
|
// Draw content
|
|
// draw_line(10, 10, 390, 290, true);
|
|
draw_string_scaled(10, 10, "Hello World!", 3);
|
|
|
|
draw_string_scaled(10, 270, command_buffer, 1);
|
|
|
|
// Bridge: 1-bit to RGBA
|
|
for (int i = 0; i < V_WIDTH * V_HEIGHT; ++i)
|
|
{
|
|
bool is_on = (bit_buffer[i / 8] >> (7 - (i % 8))) & 1;
|
|
int base = i * 4;
|
|
sf::Uint8 color = is_on ? 0xFF : 0x00;
|
|
display_pixels[base] = display_pixels[base + 1] = display_pixels[base + 2] = color;
|
|
display_pixels[base + 3] = 255;
|
|
}
|
|
texture.update(display_pixels.data());
|
|
}
|
|
|
|
window.clear();
|
|
window.draw(sprite);
|
|
window.display();
|
|
|
|
}
|
|
|
|
// Restore original terminal settings
|
|
tcsetattr(STDIN_FILENO, TCSANOW, &old_tio);
|
|
return 0;
|
|
} |