Initial commit

This commit is contained in:
2026-04-29 21:54:47 +01:00
commit 02241bbd9e
60 changed files with 1561 additions and 0 deletions
+534
View File
@@ -0,0 +1,534 @@
#include "../../include/window/window.h"
Window::Window(Vec2<float> size, const char *title) {
SDL_Init(SDL_INIT_VIDEO);
TTF_Init();
virtual_size = size;
window = SDL_CreateWindow(title, size.x, size.y, SDL_WINDOW_RESIZABLE);
renderer = SDL_CreateRenderer(window, NULL);
int w, h;
SDL_GetWindowSize(window, &w, &h);
real_size = {(float)w, (float)h};
compute_transform();
images.reserve(100);
free_image_indices.reserve(100);
fonts.reserve(100);
free_font_indices.reserve(100);
texts.reserve(100);
free_text_indices.reserve(100);
text_input_active = false;
white_tex = SDL_CreateTexture(
renderer,
SDL_PIXELFORMAT_RGBA8888,
SDL_TEXTUREACCESS_STATIC,
1,
1
);
uint32_t pixel = 0xFFFFFFFF;
SDL_UpdateTexture(white_tex, nullptr, &pixel, sizeof(pixel));
};
Window::~Window() {
for (auto tex : images)
if (tex)
SDL_DestroyTexture(tex);
for (auto tex : texts)
if (tex)
SDL_DestroyTexture(tex);
for (auto &f : fonts)
if (f.font)
TTF_CloseFont(f.font);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
TTF_Quit();
SDL_Quit();
}
ImageID Window::load_image(const char *path, bool pixel_art) {
SDL_Texture *texture = IMG_LoadTexture(renderer, path);
if (pixel_art) {
SDL_SetTextureScaleMode(texture, SDL_SCALEMODE_NEAREST);
}
if (texture == nullptr) {
fprintf(stderr, "Failed to load image: %s\n", SDL_GetError());
return UINT32_MAX;
}
if (!free_image_indices.empty()) {
ImageID id = free_image_indices.back();
free_image_indices.pop_back();
images[id] = texture;
return id;
} else {
images.push_back(texture);
return images.size() - 1;
}
return UINT32_MAX;
}
void Window::unload_image(ImageID image_id) {
if (image_id >= images.size() || images[image_id] == nullptr) {
fprintf(stderr, "Invalid image ID: %d\n", image_id);
return;
}
SDL_DestroyTexture(images[image_id]);
images[image_id] = nullptr;
free_image_indices.push_back(image_id);
}
FontID Window::load_font(const char *path, int font_size, bool bold, bool italic, bool underline, bool pixel_art) {
TTF_Font *font = TTF_OpenFont(path, font_size);
if (font == nullptr) {
fprintf(stderr, "Failed to load font: %s\n", SDL_GetError());
return UINT32_MAX;
}
int style = TTF_STYLE_NORMAL;
if (bold)
style |= TTF_STYLE_BOLD;
if (italic)
style |= TTF_STYLE_ITALIC;
if (underline)
style |= TTF_STYLE_UNDERLINE;
TTF_SetFontStyle(font, style);
if (!free_font_indices.empty()) {
FontID id = free_font_indices.back();
free_font_indices.pop_back();
fonts[id] = {font, pixel_art};
return id;
} else {
fonts.push_back({font, pixel_art});
return fonts.size() - 1;
}
return UINT32_MAX;
}
void Window::unload_font(FontID font_id) {
if (font_id >= fonts.size() || fonts[font_id].font == nullptr) {
fprintf(stderr, "Invalid font ID: %d\n", font_id);
return;
}
TTF_CloseFont(fonts[font_id].font);
fonts[font_id].font = nullptr;
free_font_indices.push_back(font_id);
}
TextID Window::create_text(FontID font_id, const char *text, uint32_t length, Color color) {
if (font_id >= fonts.size() || fonts[font_id].font == nullptr) {
fprintf(stderr, "Invalid font ID: %d\n", font_id);
return UINT32_MAX;
}
SDL_Color sdl_color = {color.r, color.g, color.b, color.a};
SDL_Surface *surface = TTF_RenderText_Solid(fonts[font_id].font, text, length, sdl_color);
if (surface == nullptr) {
fprintf(stderr, "Failed to create text surface: %s\n", SDL_GetError());
return UINT32_MAX;
}
SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer, surface);
SDL_DestroySurface(surface);
if (texture == nullptr) {
fprintf(stderr, "Failed to create text texture: %s\n", SDL_GetError());
return UINT32_MAX;
}
if (fonts[font_id].pixel_art)
SDL_SetTextureScaleMode(texture, SDL_SCALEMODE_NEAREST);
if (!free_text_indices.empty()) {
TextID id = free_text_indices.back();
free_text_indices.pop_back();
texts[id] = texture;
return id;
} else {
texts.push_back(texture);
return texts.size() - 1;
}
return UINT32_MAX;
}
void Window::unload_text(TextID text_id) {
if (text_id >= texts.size() || texts[text_id] == nullptr) {
fprintf(stderr, "Invalid text ID: %d\n", text_id);
return;
}
SDL_DestroyTexture(texts[text_id]);
texts[text_id] = nullptr;
free_text_indices.push_back(text_id);
}
bool Window::write(TextID text_id, Vec2<float> position, float z, float angle, Vec2<float> scale) {
if (text_id >= texts.size() || texts[text_id] == nullptr) {
fprintf(stderr, "Invalid text ID: %d\n", text_id);
return false;
}
render_instructions.push_back(
{.type = RenderInstruction::DRAW_TEXT,
.z = z,
.angle = angle,
.scale = scale,
.draw_text = {
.text_id = text_id,
.position = position,
}}
);
return true;
};
bool Window::draw(ImageID image_id, Vec2<float> position, float z, float angle, Vec2<float> scale, float alpha) {
if (image_id >= images.size() || images[image_id] == nullptr) {
fprintf(stderr, "Invalid image ID: %d\n", image_id);
return false;
}
render_instructions.push_back(
{.type = RenderInstruction::DRAW_IMAGE,
.z = z,
.angle = angle,
.scale = scale,
.draw_image = {
.image_id = image_id,
.position = position,
.alpha = alpha,
}}
);
return true;
};
bool Window::draw_rect(Rect rect, Color color, float z, float angle) {
render_instructions.push_back(
{.type = RenderInstruction::DRAW_RECT,
.z = z,
.angle = angle,
.scale = {1.0f, 1.0f},
.draw_rect = {
.rect = rect,
.color = color,
}}
);
return true;
};
void Window::draw_letterbox() {
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
// left bar
SDL_FRect left = {0, 0, offset.x, real_size.y};
SDL_RenderFillRect(renderer, &left);
// right bar
SDL_FRect right = {
real_size.x - offset.x,
0,
offset.x,
real_size.y
};
SDL_RenderFillRect(renderer, &right);
// top bar
SDL_FRect top = {0, 0, real_size.x, offset.y};
SDL_RenderFillRect(renderer, &top);
// bottom bar
SDL_FRect bottom = {
0,
real_size.y - offset.y,
real_size.x,
offset.y
};
SDL_RenderFillRect(renderer, &bottom);
}
void Window::render() {
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderClear(renderer);
// sort render instructions by z value
std::stable_sort(
render_instructions.begin(),
render_instructions.end(),
[](const RenderInstruction &a, const RenderInstruction &b) {
return a.z < b.z;
}
);
for (const RenderInstruction &instruction : render_instructions) {
switch (instruction.type) {
case RenderInstruction::DRAW_TEXT: {
SDL_Texture *texture = texts[instruction.draw_text.text_id];
SDL_FlipMode flip = SDL_FLIP_NONE;
if (instruction.scale.x < 0)
flip = (SDL_FlipMode)(flip | SDL_FLIP_HORIZONTAL);
if (instruction.scale.y < 0)
flip = (SDL_FlipMode)(flip | SDL_FLIP_VERTICAL);
float scale_x = std::abs(instruction.scale.x) * scale;
float scale_y = std::abs(instruction.scale.y) * scale;
float w, h;
SDL_GetTextureSize(texture, &w, &h);
SDL_FRect dst_rect;
dst_rect.x = instruction.draw_text.position.x * scale + offset.x;
dst_rect.y = instruction.draw_text.position.y * scale + offset.y;
dst_rect.w = scale_x * w;
dst_rect.h = scale_y * h;
SDL_RenderTextureRotated(
renderer,
texture,
nullptr,
&dst_rect,
instruction.angle,
nullptr,
flip
);
} break;
case RenderInstruction::DRAW_IMAGE: {
SDL_Texture *texture = images[instruction.draw_image.image_id];
float w, h;
SDL_GetTextureSize(texture, &w, &h);
SDL_FlipMode flip = SDL_FLIP_NONE;
if (instruction.scale.x < 0)
flip = (SDL_FlipMode)(flip | SDL_FLIP_HORIZONTAL);
if (instruction.scale.y < 0)
flip = (SDL_FlipMode)(flip | SDL_FLIP_VERTICAL);
float scale_x = std::abs(instruction.scale.x) * scale;
float scale_y = std::abs(instruction.scale.y) * scale;
SDL_FRect dst_rect;
dst_rect.x = instruction.draw_image.position.x * scale + offset.x;
dst_rect.y = instruction.draw_image.position.y * scale + offset.y;
dst_rect.w = scale_x * w;
dst_rect.h = scale_y * h;
SDL_SetTextureAlphaMod(texture, static_cast<Uint8>(instruction.draw_image.alpha * 255));
SDL_RenderTextureRotated(
renderer,
texture,
nullptr,
&dst_rect,
instruction.angle,
nullptr,
flip
);
} break;
case RenderInstruction::DRAW_RECT: {
SDL_FRect dst_rect;
dst_rect.x = instruction.draw_rect.rect.x() * scale + offset.x;
dst_rect.y = instruction.draw_rect.rect.y() * scale + offset.y;
dst_rect.w = instruction.draw_rect.rect.w() * scale;
dst_rect.h = instruction.draw_rect.rect.h() * scale;
SDL_SetTextureColorMod(
white_tex,
instruction.draw_rect.color.r,
instruction.draw_rect.color.g,
instruction.draw_rect.color.b
);
SDL_SetTextureAlphaMod(white_tex, instruction.draw_rect.color.a);
SDL_RenderTextureRotated(
renderer,
white_tex,
nullptr,
&dst_rect,
instruction.angle,
nullptr,
SDL_FLIP_NONE
);
} break;
}
}
draw_letterbox();
SDL_RenderPresent(renderer);
}
void Window::clear() {
render_instructions.clear();
}
bool Window::poll(Event &event) {
SDL_Event sdl_event;
if (SDL_PollEvent(&sdl_event)) {
switch (sdl_event.type) {
case SDL_EVENT_QUIT:
event.type = Event::QUIT;
break;
case SDL_EVENT_KEY_DOWN:
if (text_input_active && sdl_event.key.key > '0' && sdl_event.key.key < 'z')
return false;
event.type = Event::KEY_DOWN;
event.key.key = sdl_event.key.scancode;
break;
case SDL_EVENT_KEY_UP:
if (text_input_active && sdl_event.key.key > '0' && sdl_event.key.key < 'z')
return false;
event.type = Event::KEY_UP;
event.key.key = sdl_event.key.scancode;
break;
case SDL_EVENT_TEXT_INPUT:
if (!text_input_active)
return false;
event.type = Event::TEXT_INPUT;
event.text_input.length = strlen(sdl_event.text.text);
event.text_input.text = (char *)malloc(event.text_input.length + 1);
strcpy(event.text_input.text, sdl_event.text.text);
event.text_input.text[event.text_input.length] = '\0';
break;
case SDL_EVENT_MOUSE_BUTTON_DOWN: {
event.type = Event::MOUSE_BUTTON_DOWN;
event.mouse_button.button = sdl_event.button.button;
Vec2<float> virtual_position;
if (mouse_in_window({sdl_event.button.x, sdl_event.button.y}, virtual_position))
event.mouse_button.position = virtual_position;
else
return false;
} break;
case SDL_EVENT_MOUSE_BUTTON_UP: {
event.type = Event::MOUSE_BUTTON_UP;
event.mouse_button.button = sdl_event.button.button;
Vec2<float> virtual_position;
if (mouse_in_window({sdl_event.button.x, sdl_event.button.y}, virtual_position))
event.mouse_button.position = virtual_position;
else
return false;
} break;
case SDL_EVENT_MOUSE_MOTION: {
event.type = Event::MOUSE_MOVE;
event.mouse_move.delta = {sdl_event.motion.xrel, sdl_event.motion.yrel};
Vec2<float> virtual_position;
if (mouse_in_window({sdl_event.button.x, sdl_event.button.y}, virtual_position))
event.mouse_button.position = virtual_position;
else
return false;
} break;
case SDL_EVENT_MOUSE_WHEEL:
event.type = Event::SCROLL;
event.scroll.x = sdl_event.wheel.x;
event.scroll.y = sdl_event.wheel.y;
break;
case SDL_EVENT_WINDOW_RESIZED:
sdl_event.window.data1 = MAX(sdl_event.window.data1, 1);
sdl_event.window.data2 = MAX(sdl_event.window.data2, 1);
real_size = {(float)sdl_event.window.data1, (float)sdl_event.window.data2};
compute_transform();
break;
default:
return false;
}
return true;
}
return false;
}
void Window::consume(Event &event) {
if (event.type == Event::TEXT_INPUT) {
free(event.text_input.text);
event.text_input.text = nullptr;
event.text_input.length = 0;
}
event.type = Event::NONE;
}
void Window::start_text_input() {
text_input_active = true;
SDL_StartTextInput(window);
}
void Window::stop_text_input() {
text_input_active = false;
SDL_StopTextInput(window);
}
const char *Window::paste() {
const char *clipboard_text = SDL_GetClipboardText();
if (clipboard_text == nullptr) {
fprintf(stderr, "Failed to get clipboard text: %s\n", SDL_GetError());
return nullptr;
}
const char *copy = strdup(clipboard_text);
SDL_free((void *)clipboard_text);
return copy;
}
uint64_t Window::get_time() {
return SDL_GetTicks();
}
uint64_t Window::delta_time() {
static uint64_t last_time = 0;
uint64_t current_time = SDL_GetTicks();
uint64_t delta = current_time - last_time;
last_time = current_time;
return delta;
}
void Window::compute_transform() {
float scale_factor_x = real_size.x / virtual_size.x;
float scale_factor_y = real_size.y / virtual_size.y;
scale = MIN(scale_factor_x, scale_factor_y);
offset.x = (real_size.x - virtual_size.x * scale) / 2.0f;
offset.y = (real_size.y - virtual_size.y * scale) / 2.0f;
}
bool Window::mouse_in_window(const Vec2<float> real_position, Vec2<float> &virtual_position) {
virtual_position = (real_position - offset) / scale;
return virtual_position.x >= 0 && virtual_position.x <= virtual_size.x &&
virtual_position.y >= 0 && virtual_position.y <= virtual_size.y;
}