#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(); SDL_StartTextInput(window); 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, bool pixel_art) { SDL_Surface *surface = IMG_Load(path); if (!surface) { fprintf(stderr, "Failed to load image: %s\n", SDL_GetError()); return UINT64_MAX; } SDL_Surface *rgba = SDL_ConvertSurface( surface, SDL_PIXELFORMAT_RGBA8888 ); SDL_DestroySurface(surface); if (!rgba) { fprintf(stderr, "Failed to convert image: %s\n", SDL_GetError()); return UINT64_MAX; } SDL_Texture *texture = SDL_CreateTexture( renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STREAMING, rgba->w, rgba->h ); if (!texture) { SDL_DestroySurface(rgba); fprintf(stderr, "Failed to create texture: %s\n", SDL_GetError()); return UINT64_MAX; } SDL_UpdateTexture( texture, nullptr, rgba->pixels, rgba->pitch ); SDL_DestroySurface(rgba); if (pixel_art) { SDL_SetTextureScaleMode(texture, SDL_SCALEMODE_NEAREST); } 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; } std::vector Window::load_image_set(const char *path, Vec2 tile_size, Vec2 padding, Vec2 offset, bool pixel_art) { SDL_Surface *surface = IMG_Load(path); if (!surface) { fprintf(stderr, "Failed to load image set: %s\n", SDL_GetError()); return {}; } std::vector image_ids; int columns = (int)((surface->w - offset.x + padding.x) / (tile_size.x + padding.x)); int rows = (int)((surface->h - offset.y + padding.y) / (tile_size.y + padding.y)); for (int y = 0; y < rows; y++) { for (int x = 0; x < columns; x++) { SDL_Rect src_rect = { (int)(offset.x + x * (tile_size.x + padding.x)), (int)(offset.y + y * (tile_size.y + padding.y)), (int)tile_size.x, (int)tile_size.y }; SDL_Surface *tile_surface = SDL_CreateSurface(src_rect.w, src_rect.h, SDL_PIXELFORMAT_RGBA8888); SDL_BlitSurface(surface, &src_rect, tile_surface, nullptr); SDL_Texture *texture = SDL_CreateTexture( renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STREAMING, tile_surface->w, tile_surface->h ); if (!texture) { fprintf(stderr, "Failed to create texture for tile: %s\n", SDL_GetError()); SDL_DestroySurface(tile_surface); continue; } SDL_UpdateTexture( texture, nullptr, tile_surface->pixels, tile_surface->pitch ); if (pixel_art) { SDL_SetTextureScaleMode(texture, SDL_SCALEMODE_NEAREST); } Image *img = new Image{texture, {(float)tile_surface->w, (float)tile_surface->h}}; uint64_t id = image_pool.acquire(img); image_pool.use(id); image_ids.push_back(id); SDL_DestroySurface(tile_surface); } } SDL_DestroySurface(surface); return image_ids; } uint64_t Window::create_image(Vec2 size, bool pixel_art) { SDL_Texture *texture = SDL_CreateTexture( renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STREAMING, (int)size.x, (int)size.y ); if (!texture) { fprintf(stderr, "Failed to create scripted texture: %s\n", SDL_GetError()); return UINT64_MAX; } SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND); if (pixel_art) SDL_SetTextureScaleMode(texture, SDL_SCALEMODE_NEAREST); Image *img = new Image{texture, size}; uint64_t id = image_pool.acquire(img); image_pool.use(id); char *buffer = (char *)malloc(size.x * size.y * 4 * sizeof(char)); for (unsigned int i = 0; i < (unsigned int)(size.x * size.y * 4); i += 4) { buffer[i] = 0xFF; buffer[i + 1] = 0xFF; buffer[i + 2] = 0xFF; buffer[i + 3] = 0xFF; } SDL_UpdateTexture( img->texture, nullptr, buffer, (int)(size.x * 4) ); free(buffer); return id; } bool Window::update_image(uint64_t image_id, Rect content_rect, const char *new_content, uint32_t length) { if (!image_pool.is_valid(image_id)) { fprintf(stderr, "Invalid image ID: %llu\n", (unsigned long long)image_id); return false; } Image *img = image_pool[image_id]; SDL_Rect sdl_rect = { (int)content_rect.x(), (int)content_rect.y(), (int)content_rect.w(), (int)content_rect.h() }; SDL_UpdateTexture( img->texture, &sdl_rect, new_content, (int)(content_rect.w() * 4) ); return true; }; 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, 255}; 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 pivot, Vec2 scale, float alpha) { 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, .pivot = pivot, .scale = scale, .alpha = alpha, .draw_text = { .text_id = text_id, .position = position, }} ); return true; }; bool Window::draw(uint64_t image_id, Vec2 position, float z, float angle, Vec2 pivot, Vec2 scale, float alpha) { 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, .pivot = pivot, .scale = scale, .alpha = alpha, .draw_image = { .image_id = image_id, .position = position, }} ); return true; }; bool Window::draw_rect(Rect rect, Color color, float z, float angle, Vec2 pivot, Vec2 scale, float alpha) { render_instructions.push_back( {.type = RenderInstruction::DRAW_RECT, .z = z, .angle = angle, .pivot = pivot, .scale = scale, .alpha = alpha, .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_SetTextureAlphaModFloat(texture, instruction.alpha); SDL_FPoint pivot_point = { instruction.pivot.x * scale_x, instruction.pivot.y * scale_y }; SDL_RenderTextureRotated( renderer, texture, nullptr, &dst_rect, instruction.angle, &pivot_point, 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_SetTextureAlphaModFloat(image->texture, instruction.alpha); SDL_FPoint pivot_point = { instruction.pivot.x * scale_x, instruction.pivot.y * scale_y }; SDL_RenderTextureRotated( renderer, image->texture, nullptr, &dst_rect, instruction.angle, &pivot_point, 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 * instruction.scale.x; dst_rect.h = instruction.draw_rect.rect.h() * scale * instruction.scale.y; SDL_SetTextureColorMod( white_tex, instruction.draw_rect.color.r, instruction.draw_rect.color.g, instruction.draw_rect.color.b ); SDL_SetTextureAlphaModFloat(white_tex, instruction.alpha); SDL_FPoint pivot_point = { instruction.pivot.x * dst_rect.w, instruction.pivot.y * dst_rect.h }; SDL_RenderTextureRotated( renderer, white_tex, nullptr, &dst_rect, instruction.angle, &pivot_point, 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 = (Key)sdl_event.key.scancode; break; case SDL_EVENT_KEY_UP: event.type = Event::KEY_UP; event.key.key = (Key)sdl_event.key.scancode; break; case SDL_EVENT_TEXT_INPUT: event.type = Event::TEXT_INPUT; event.text_input = new std::string(sdl_event.text.text); 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 = (Key)(sdl_event.button.button + 512); 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 = (Key)(sdl_event.button.button + 512); 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; } 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_GetTicksNS(); } 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; }