Files
project_misth/src/window/window.cc
T

499 lines
13 KiB
C++

#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();
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<float> 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<float> 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<float> 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<float> position, float z, float angle, Vec2<float> 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<float> position, float z, float angle, Vec2<float> 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<float> 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<float> 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<float> 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<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;
}