Files
r36ultra_app/menu_demo.c
2026-02-01 16:41:07 -03:00

331 lines
8.9 KiB
C

#include <fcntl.h>
#include <linux/input.h>
#include <poll.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define WIDTH 720
#define HEIGHT 720
#define BPP 4 // BGRA = 4 bytes per pixel
#define MAX_BUTTONS 10
#define BUTTON_HEIGHT 60
#define BUTTON_WIDTH 500
#define BUTTON_MARGIN 10
typedef struct {
int fb_fd;
uint8_t *framebuffer;
} FramebufferDemo;
typedef struct {
char text[64];
int x, y, width, height;
int id;
} Button;
typedef struct {
Button buttons[MAX_BUTTONS];
int count;
int selected;
} Menu;
// Initialize framebuffer
int fb_init(FramebufferDemo *fb) {
fb->fb_fd = open("/dev/fb0", O_RDWR);
if (fb->fb_fd < 0) {
fprintf(stderr, "Error: Cannot open framebuffer device /dev/fb0\n");
return -1;
}
fb->framebuffer = (uint8_t *)malloc(WIDTH * HEIGHT * BPP);
if (!fb->framebuffer) {
fprintf(stderr, "Error: Cannot allocate framebuffer memory\n");
close(fb->fb_fd);
return -1;
}
printf("Framebuffer initialized: %dx%d BGRA\n", WIDTH, HEIGHT);
return 0;
}
void fb_close(FramebufferDemo *fb) {
if (fb->framebuffer) {
free(fb->framebuffer);
fb->framebuffer = NULL;
}
if (fb->fb_fd >= 0) {
close(fb->fb_fd);
fb->fb_fd = -1;
}
}
void set_pixel(FramebufferDemo *fb, int x, int y, uint8_t b, uint8_t g,
uint8_t r, uint8_t a) {
if (x < 0 || x >= WIDTH || y < 0 || y >= HEIGHT) {
return;
}
int offset = (y * WIDTH + x) * BPP;
fb->framebuffer[offset + 0] = b;
fb->framebuffer[offset + 1] = g;
fb->framebuffer[offset + 2] = r;
fb->framebuffer[offset + 3] = a;
}
void fill_screen(FramebufferDemo *fb, uint8_t b, uint8_t g, uint8_t r,
uint8_t a) {
for (int y = 0; y < HEIGHT; y++) {
for (int x = 0; x < WIDTH; x++) {
set_pixel(fb, x, y, b, g, r, a);
}
}
}
void draw_rectangle(FramebufferDemo *fb, int x, int y, int w, int h, uint8_t b,
uint8_t g, uint8_t r, uint8_t a) {
for (int dy = 0; dy < h; dy++) {
for (int dx = 0; dx < w; dx++) {
set_pixel(fb, x + dx, y + dy, b, g, r, a);
}
}
}
void draw_border(FramebufferDemo *fb, int x, int y, int w, int h, int thickness,
uint8_t b, uint8_t g, uint8_t r, uint8_t a) {
// Top
draw_rectangle(fb, x, y, w, thickness, b, g, r, a);
// Bottom
draw_rectangle(fb, x, y + h - thickness, w, thickness, b, g, r, a);
// Left
draw_rectangle(fb, x, y, thickness, h, b, g, r, a);
// Right
draw_rectangle(fb, x + w - thickness, y, thickness, h, b, g, r, a);
}
// Simple 8x8 font for characters
void draw_char(FramebufferDemo *fb, int x, int y, char c, uint8_t b, uint8_t g,
uint8_t r, uint8_t a) {
// Simple pixel patterns for some characters
static const uint8_t font[][8] = {
{0x3C, 0x66, 0x6E, 0x76, 0x66, 0x66, 0x3C, 0x00}, // A
{0x7C, 0x66, 0x66, 0x7C, 0x66, 0x66, 0x7C, 0x00}, // B
{0x3C, 0x66, 0x60, 0x60, 0x60, 0x66, 0x3C, 0x00}, // C
{0x78, 0x6C, 0x66, 0x66, 0x66, 0x6C, 0x78, 0x00}, // D
{0x7E, 0x60, 0x60, 0x7C, 0x60, 0x60, 0x7E, 0x00}, // E
{0x7E, 0x60, 0x60, 0x7C, 0x60, 0x60, 0x60, 0x00}, // F
{0x3C, 0x66, 0x60, 0x6E, 0x66, 0x66, 0x3C, 0x00}, // G
{0x66, 0x66, 0x66, 0x7E, 0x66, 0x66, 0x66, 0x00}, // H
{0x3C, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00}, // I
{0x1E, 0x0C, 0x0C, 0x0C, 0x6C, 0x6C, 0x38, 0x00}, // J
};
if (c >= 'A' && c <= 'J') {
const uint8_t *pattern = font[c - 'A'];
for (int row = 0; row < 8; row++) {
for (int col = 0; col < 8; col++) {
if (pattern[row] & (1 << (7 - col))) {
set_pixel(fb, x + col * 2, y + row * 2, b, g, r, a);
set_pixel(fb, x + col * 2 + 1, y + row * 2, b, g, r, a);
set_pixel(fb, x + col * 2, y + row * 2 + 1, b, g, r, a);
set_pixel(fb, x + col * 2 + 1, y + row * 2 + 1, b, g, r, a);
}
}
}
}
}
void draw_text(FramebufferDemo *fb, int x, int y, const char *text, uint8_t b,
uint8_t g, uint8_t r, uint8_t a) {
int offset = 0;
for (int i = 0; text[i] != '\0'; i++) {
if (text[i] >= 'A' && text[i] <= 'Z') {
draw_char(fb, x + offset, y, text[i], b, g, r, a);
offset += 18;
} else if (text[i] >= 'a' && text[i] <= 'z') {
draw_char(fb, x + offset, y, text[i] - 32, b, g, r, a);
offset += 18;
} else {
offset += 10; // Space
}
}
}
void draw_button(FramebufferDemo *fb, Button *btn, int selected) {
if (selected) {
// Selected: bright blue background with yellow border
draw_rectangle(fb, btn->x, btn->y, btn->width, btn->height, 200, 100, 50,
255);
draw_border(fb, btn->x, btn->y, btn->width, btn->height, 4, 0, 200, 255,
255);
} else {
// Normal: dark gray background with light border
draw_rectangle(fb, btn->x, btn->y, btn->width, btn->height, 80, 80, 80,
255);
draw_border(fb, btn->x, btn->y, btn->width, btn->height, 2, 150, 150, 150,
255);
}
// Draw text
int text_x = btn->x + 20;
int text_y = btn->y + (btn->height - 16) / 2;
draw_text(fb, text_x, text_y, btn->text, 255, 255, 255, 255);
}
void fb_update(FramebufferDemo *fb) {
if (fb->fb_fd < 0)
return;
lseek(fb->fb_fd, 0, SEEK_SET);
write(fb->fb_fd, fb->framebuffer, WIDTH * HEIGHT * BPP);
}
// Initialize menu
void menu_init(Menu *menu) {
menu->count = 0;
menu->selected = 0;
}
// Add button to menu
void menu_add_button(Menu *menu, const char *text) {
if (menu->count >= MAX_BUTTONS)
return;
Button *btn = &menu->buttons[menu->count];
strncpy(btn->text, text, sizeof(btn->text) - 1);
btn->text[sizeof(btn->text) - 1] = '\0';
btn->width = BUTTON_WIDTH;
btn->height = BUTTON_HEIGHT;
btn->x = (WIDTH - BUTTON_WIDTH) / 2;
btn->y = 100 + menu->count * (BUTTON_HEIGHT + BUTTON_MARGIN);
btn->id = menu->count;
menu->count++;
}
// Draw entire menu
void menu_draw(FramebufferDemo *fb, Menu *menu) {
// Clear screen with dark background
fill_screen(fb, 30, 30, 30, 255);
// Draw title
draw_text(fb, 250, 30, "MENU PRINCIPAL", 0, 255, 255, 255);
// Draw all buttons
for (int i = 0; i < menu->count; i++) {
draw_button(fb, &menu->buttons[i], i == menu->selected);
}
// Draw instructions at bottom
draw_text(fb, 200, HEIGHT - 40, "USE DIRECIONAL E BOTAO A", 200, 200, 200,
255);
}
// Open joystick/gamepad device
int joystick_open() {
// Try common input device paths
const char *devices[] = {"/dev/input/event0", "/dev/input/event1",
"/dev/input/event2", "/dev/input/event3",
"/dev/input/js0", NULL};
for (int i = 0; devices[i] != NULL; i++) {
int fd = open(devices[i], O_RDONLY | O_NONBLOCK);
if (fd >= 0) {
printf("Opened input device: %s\n", devices[i]);
return fd;
}
}
fprintf(stderr, "Warning: Could not open joystick device\n");
fprintf(stderr, "Falling back to keyboard input (arrow keys + Enter)\n");
return -1;
}
int main(int argc, char *argv[]) {
FramebufferDemo fb = {0};
Menu menu = {0};
if (fb_init(&fb) < 0) {
return 1;
}
// Initialize menu
menu_init(&menu);
menu_add_button(&menu, "INICIAR JOGO");
menu_add_button(&menu, "CONFIGURACOES");
menu_add_button(&menu, "CARREGAR SAVE");
menu_add_button(&menu, "CREDITOS");
menu_add_button(&menu, "SAIR");
// Open joystick
int joy_fd = joystick_open();
// Main loop
int running = 1;
printf("Menu running. Use D-pad to navigate, A button to select.\n");
printf("Press Ctrl+C to exit.\n");
while (running) {
// Draw menu
menu_draw(&fb, &menu);
fb_update(&fb);
// Handle input
if (joy_fd >= 0) {
struct input_event ev;
struct pollfd pfd = {.fd = joy_fd, .events = POLLIN};
if (poll(&pfd, 1, 100) > 0) {
if (read(joy_fd, &ev, sizeof(ev)) == sizeof(ev)) {
if (ev.type == EV_KEY && ev.value == 1) {
// Key pressed
switch (ev.code) {
case BTN_DPAD_UP:
case KEY_UP:
if (menu.selected > 0) {
menu.selected--;
printf("Selected: %s\n", menu.buttons[menu.selected].text);
}
break;
case BTN_DPAD_DOWN:
case KEY_DOWN:
if (menu.selected < menu.count - 1) {
menu.selected++;
printf("Selected: %s\n", menu.buttons[menu.selected].text);
}
break;
case BTN_SOUTH: // A button
case KEY_ENTER:
printf("Activated: %s\n", menu.buttons[menu.selected].text);
if (menu.selected == 4) { // SAIR
running = 0;
}
break;
case KEY_ESC:
running = 0;
break;
}
}
}
}
} else {
// Fallback: wait a bit
usleep(100000);
}
}
// Cleanup
if (joy_fd >= 0) {
close(joy_fd);
}
fill_screen(&fb, 0, 0, 0, 255);
fb_update(&fb);
fb_close(&fb);
printf("Menu closed.\n");
return 0;
}