Initial commit
This commit is contained in:
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user