#include "../../include/window/window.h" Window::Window(Vec2 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(); 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)); SDL_SetTextureBlendMode(white_tex, SDL_BLENDMODE_BLEND); }; void Window::update(Vec2 new_size, const char *new_title) { if (new_size.x > 0 && new_size.y > 0) { virtual_size = new_size; compute_transform(); } if (new_title) SDL_SetWindowTitle(window, new_title); } Window::~Window() { for (auto &tex : image_pool.all()) SDL_DestroyTexture(tex->texture); for (auto tex : text_pool.all()) SDL_DestroyTexture(tex->texture); for (auto &f : font_pool.all()) TTF_CloseFont(f->font); SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); TTF_Quit(); SDL_Quit(); } uint64_t Window::load_image(const char *path, float alpha, 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 UINT64_MAX; } SDL_SetTextureAlphaMod(texture, (uint8_t)(alpha * 255)); float w, h; SDL_GetTextureSize(texture, &w, &h); Image *img = new Image{texture, {w, h}}; uint64_t id = image_pool.acquire(img); image_pool.use(id); return id; } Vec2 Window::get_image_size(uint64_t image_id) { if (!image_pool.is_valid(image_id)) { fprintf(stderr, "Invalid image ID: %llu\n", (unsigned long long)image_id); return {0, 0}; } return image_pool[image_id]->size; } uint64_t 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 UINT64_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); Font *f = new Font{font, pixel_art}; uint64_t id = font_pool.acquire(f); font_pool.use(id); return id; } uint64_t Window::create_text(uint64_t font_id, const char *text, uint32_t length, Color color) { if (!font_pool.is_valid(font_id)) { fprintf(stderr, "Invalid font ID: %llu\n", (unsigned long long)font_id); return UINT64_MAX; } SDL_Color sdl_color = {color.r, color.g, color.b, color.a}; SDL_Surface *surface = TTF_RenderText_Solid(font_pool[font_id]->font, text, length, sdl_color); if (surface == nullptr) { fprintf(stderr, "Failed to create text surface: %s\n", SDL_GetError()); return UINT64_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 UINT64_MAX; } if (font_pool[font_id]->pixel_art) SDL_SetTextureScaleMode(texture, SDL_SCALEMODE_NEAREST); float w, h; SDL_GetTextureSize(texture, &w, &h); Text *t = new Text{texture, {w, h}}; uint64_t id = text_pool.acquire(t); text_pool.use(id); return id; } Vec2 Window::get_text_size(uint64_t text_id) { if (!text_pool.is_valid(text_id)) { fprintf(stderr, "(Size) Invalid text ID: %llu\n", (unsigned long long)text_id); return {0, 0}; throw std::runtime_error("Invalid text ID"); } return text_pool[text_id]->size; } bool Window::write(uint64_t text_id, Vec2 position, float z, float angle, Vec2 scale) { if (!text_pool.is_valid(text_id)) { fprintf(stderr, "(Write) Invalid text ID: %llu\n", (unsigned long long)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(uint64_t image_id, Vec2 position, float z, float angle, Vec2 scale) { if (!image_pool.is_valid(image_id)) { fprintf(stderr, "Invalid image ID: %llu\n", (unsigned long long)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, }} ); 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: { Text *text = text_pool[instruction.draw_text.text_id]; SDL_Texture *texture = text->texture; 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_text.position.x * scale + offset.x; dst_rect.y = instruction.draw_text.position.y * scale + offset.y; dst_rect.w = scale_x * text->size.x; dst_rect.h = scale_y * text->size.y; SDL_RenderTextureRotated( renderer, texture, nullptr, &dst_rect, instruction.angle, nullptr, flip ); } break; case RenderInstruction::DRAW_IMAGE: { Image *image = image_pool[instruction.draw_image.image_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; 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 * image->size.x; dst_rect.h = scale_y * image->size.y; SDL_RenderTextureRotated( renderer, image->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: event.type = Event::KEY_DOWN; event.key.key = sdl_event.key.scancode; break; case SDL_EVENT_KEY_UP: 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_WINDOW_MOUSE_LEAVE: event.type = Event::MOUSE_LEAVE; break; case SDL_EVENT_MOUSE_BUTTON_DOWN: { event.type = Event::MOUSE_BUTTON_DOWN; event.mouse_button.button = sdl_event.button.button; Vec2 virtual_position; if (mouse_in_window({sdl_event.button.x, sdl_event.button.y}, virtual_position)) event.mouse_button.position = virtual_position; else event.type = Event::MOUSE_LEAVE; } break; case SDL_EVENT_MOUSE_BUTTON_UP: { event.type = Event::MOUSE_BUTTON_UP; event.mouse_button.button = sdl_event.button.button; Vec2 virtual_position; if (mouse_in_window({sdl_event.button.x, sdl_event.button.y}, virtual_position)) event.mouse_button.position = virtual_position; else event.type = Event::MOUSE_LEAVE; } break; case SDL_EVENT_MOUSE_MOTION: { event.type = Event::MOUSE_MOVE; event.mouse_move.delta = {sdl_event.motion.xrel, sdl_event.motion.yrel}; Vec2 virtual_position; if (mouse_in_window({sdl_event.motion.x, sdl_event.motion.y}, virtual_position)) event.mouse_move.position = virtual_position; else event.type = Event::MOUSE_LEAVE; } 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 real_position, Vec2 &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; }