RyanHub – file viewer
filename: src/UI.cpp
branch: main
back to repo
#include <UI.h>
#include <string>
#include <algorithm>
#include <chrono>
#include <thread>
#include <vector>
#include "constants.h"
#include "renderer.h"

void UI::pushToRenderer(Renderer* renderer) {
    float leftX = xpos - 0.5 * (width / SCREEN_X);
    float rightX = xpos + 0.5 * (width / SCREEN_X);

    float topY = ypos + 0.5 * (height / SCREEN_Y);
    float bottomY = ypos - 0.5 * (height / SCREEN_Y);

    Renderer::QuadVertices quadVertices = { leftX, topY, rightX, topY, leftX, bottomY, rightX, bottomY };
    Renderer::ColorRGB color(R, G, B);

    renderer->drawQuad(quadVertices, color, texIndex, uvs);
}
bool UI::isHovered(GLFWwindow* window) {
    double x, y;
    glfwGetCursorPos(window, &x, &y);

    float xNDC = static_cast<float>(x) / SCREEN_X * 2.0f - 1.0f;
    float yNDC = 1.0f - static_cast<float>(y) / SCREEN_Y * 2.0f;

    float leftX = xpos - 0.5 * (width / SCREEN_X);
    float rightX = xpos + 0.5 * (width / SCREEN_X);

    float topY = ypos + 0.5 * (height / SCREEN_Y);
    float bottomY = ypos - 0.5 * (height / SCREEN_Y);

    return (xNDC >= leftX && xNDC <= rightX && yNDC >= bottomY && yNDC <= topY);
}
bool UI::isClicked(GLFWwindow* window) {
    return (isHovered(window) && (glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS));
}

Button::Button(float height, float width, float xpos, float ypos, int R, int G, int B, int texIndex = 0, const float* uvs = nullptr) {
    this->height = height;
    this->width = width;
    this->xpos = xpos;
    this->ypos = ypos;
    this->R = R;
    this->G = G;
    this->B = B;
    this->texIndex = texIndex;
    this->uvs = uvs;
}
Button::Button(float height, float width, float xpos, float ypos, int R, int G, int B, int texIndex, std::string message) {
    this->height = height;
    this->width = width;
    this->xpos = xpos;
    this->ypos = ypos;
    this->R = R;
    this->G = G;
    this->B = B;
    this->texIndex = texIndex;
    this->uvs = nullptr;
    this->message = message;
}

void Button::pushToRenderer(Renderer* renderer) {
    // Draw button background
    float leftX = xpos - 0.5f * (width / SCREEN_X);
    float rightX = xpos + 0.5f * (width / SCREEN_X);
    float topY = ypos + 0.5f * (height / SCREEN_Y);
    float bottomY = ypos - 0.5f * (height / SCREEN_Y);

    Renderer::QuadVertices quadVertices = { leftX, topY, rightX, topY, leftX, bottomY, rightX, bottomY };
    Renderer::ColorRGB color(R, G, B);

    if (message.empty()) {
        renderer->drawQuad(quadVertices, color, texIndex, uvs); // default rendering image if no text
    }
    else {
        renderer->drawQuad(quadVertices, color, 0, nullptr); // draws blank background if we are rendering text

        int charNum = static_cast<int>(message.length());
        float maxFontSizeByWidth = width / charNum;
        float fontSize = std::min(maxFontSizeByWidth, height * 0.8f); // .8 height for padding

        float charWidth = fontSize / SCREEN_X;
        float charHeight = fontSize / SCREEN_Y;
        float totalTextWidth = charWidth * charNum;
        float startX = xpos - (totalTextWidth) / 2.0f;

        for (int i = 0; i < charNum; i++) {
            char c = message[i];
            int charSetIndex = std::clamp((int)c - 32, 0, 95);
            int col = charSetIndex % 16;
            int row = charSetIndex / 16;

            float ndcStartX = startX + i * charWidth;
            float ndcEndX = ndcStartX + charWidth;
            float ndcStartY = ypos - charHeight * 0.5f;
            float ndcEndY = ypos + charHeight * 0.5f;

            Renderer::QuadVertices textVertices = {
                ndcStartX, ndcEndY,
                ndcEndX,   ndcEndY,
                ndcStartX, ndcStartY,
                ndcEndX,   ndcStartY
            };

            float uv_x = col * 0.0625f;
            float uv_y = row * 0.0625f;
            float uv_size = 0.0625f;

            float uvs[8] = {
                uv_x,            uv_y + uv_size,
                uv_x,            uv_y,
                uv_x + uv_size,  uv_y,
                uv_x + uv_size,  uv_y + uv_size
            };
            renderer->drawQuad(textVertices, color, texIndex, uvs);
        }
    }
}

Text::Text(float width, float xpos, float ypos, int texIndex, std::string message): UI() {
    this->width = width;
    this->height = width / message.length();
    this->xpos = xpos;
    this->ypos = ypos;
    this->texIndex = texIndex;
    this->message = message;
}

void Text::pushToRenderer(Renderer* renderer) {
    char charset[] = {
        ' ', '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/',
        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?',
        '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
        'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_'
    };

    int charNum = message.length();
    float maxCharSize = width / charNum;

    for (int i = 0; i < charNum; i++) {
        char c = message[i];
        int charSetIndex = std::clamp((int)c - 32, 0, 95);
        int col = charSetIndex % 16;
        int row = charSetIndex / 16;

        float charWidth = width / charNum / (SCREEN_X);
        float charHeight = height / (SCREEN_Y);

        float ndcStartX = xpos + (i * charWidth) - (width * 0.5) / SCREEN_X;
        float ndcEndX = ndcStartX + charWidth;

        float ndcStartY = ypos - charHeight * 0.5f;
        float ndcEndY = ypos + charHeight * 0.5f;

        Renderer::QuadVertices quadVertices = {
            ndcStartX, ndcEndY,   // BL  
            ndcEndX,   ndcEndY,   // BR  
            ndcStartX, ndcStartY, // TL  
            ndcEndX,   ndcStartY  // TR  
        };

        float uv_x = col * 0.0625f;
        float uv_y = row * 0.0625f;
        float uv_size = 0.0625f;

        float uvs[8] = {
            uv_x,            uv_y + uv_size, // Top-Left (x3)  
            uv_x,            uv_y,           // Bottom-Left (x1)  
            uv_x + uv_size,  uv_y,           // Bottom-Right (x2)  
            uv_x + uv_size,  uv_y + uv_size  // Top-Right (x4)  
        };

        Renderer::ColorRGB color(255, 255, 255);
        renderer->drawQuad(quadVertices, color, texIndex, uvs);
    }
}

TextBox::TextBox(float width, float fontSize, float xpos, float ypos, int texIndex, std::string defaultMessage)
    : Text(width, xpos, ypos, texIndex, defaultMessage) {
    this->fontSize = fontSize;
}
void TextBox::pushToRenderer(Renderer* renderer) {
    char charset[] = {
        ' ', '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/',
        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?',
        '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
        'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_'
    };

    int charNum = message.length();
    float charWidth = fontSize / SCREEN_X;
    float charHeight = fontSize / SCREEN_Y;
    float totalTextWidth = charWidth * charNum;

    float actualCharWidth = charWidth;
    if (totalTextWidth > width / SCREEN_X) {
        actualCharWidth = (width / SCREEN_X) / charNum;
    }
    else if (totalTextWidth < (width / SCREEN_X) * 0.1f) {
        actualCharWidth = (width / SCREEN_X) * 0.1f / charNum;
    }

    float startX = xpos - (actualCharWidth * charNum) / 2.0f;

    float leftX = xpos - 0.5f * (width / SCREEN_X);
    float rightX = xpos + 0.5f * (width / SCREEN_X);
    float topY = ypos + 0.5f * (fontSize / SCREEN_Y);
    float bottomY = ypos - 0.5f * (fontSize / SCREEN_Y);

    Renderer::QuadVertices backgroundVertices = {
        leftX, topY,
        rightX, topY,
        leftX, bottomY,
        rightX, bottomY
    };

    Renderer::ColorRGB color(255, 255, 255);
    renderer->drawQuad(backgroundVertices, color, 0, nullptr);


    for (int i = 0; i < charNum; i++) {
        char c = message[i];
        int charSetIndex = std::clamp((int)c - 32, 0, 95);
        int col = charSetIndex % 16;
        int row = charSetIndex / 16;

        float ndcStartX = startX + i * actualCharWidth;
        float ndcEndX = ndcStartX + actualCharWidth;

        float ndcStartY = ypos - charHeight * 0.5f;
        float ndcEndY = ypos + charHeight * 0.5f;

        Renderer::QuadVertices quadVertices = {
            ndcStartX, ndcEndY,   // BL  
            ndcEndX,   ndcEndY,   // BR  
            ndcStartX, ndcStartY, // TL  
            ndcEndX,   ndcStartY  // TR  
        };

        float uv_x = col * 0.0625f;
        float uv_y = row * 0.0625f;
        float uv_size = 0.0625f;

        float uvs[8] = {
            uv_x,            uv_y + uv_size,
            uv_x,            uv_y,  
            uv_x + uv_size,  uv_y,
            uv_x + uv_size,  uv_y + uv_size
        };

        renderer->drawQuad(quadVertices, color, texIndex, uvs);
    }
}
void TextBox::collectInput(GLFWwindow* window) {
    char allowedChars[] = {
        ' ', '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/',
        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?',
        '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
        'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_'
    };

    for (int key = GLFW_KEY_SPACE; key <= GLFW_KEY_Z; ++key) {
        int state = glfwGetKey(window, key);

        if (state == GLFW_PRESS && !keyHeld[key]) {
            char c = static_cast<char>(key);

            if (std::find(std::begin(allowedChars), std::end(allowedChars), c) != std::end(allowedChars)) {
                message += c;
            }

            keyHeld[key] = true;
        }
        else if (state == GLFW_RELEASE) {
            keyHeld[key] = false;
        }
    }

    // Backspace logic
    int backspaceState = glfwGetKey(window, GLFW_KEY_BACKSPACE);
    if (backspaceState == GLFW_PRESS && !keyHeld[GLFW_KEY_BACKSPACE]) {
        if (!message.empty()) {
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
            message.pop_back();
        }
    }

    if (message.empty()) {
        //message = " ";
    }
}

DrawingBoard::DrawingBoard(float height, float width, float xpos, float ypos, std::string saveAs)
//	DrawingBoard drawingBoard(300.0f, 600.0f, 0.0f, 0.2f, "DRAW CHARACTER", "playerTex.png");
    : clearBtn(*new Button(
        height * 0.2f,
        width * 0.5f,
        xpos + width * 0.25f / SCREEN_X,
        ypos - 0.7f * (height / SCREEN_Y) + height * 0.2f * 0.5f / SCREEN_Y,
        200, 200, 100, 4, std::string("CLEAR"))),
    saveBtn(*new Button(
        height * 0.2f,
        width * 0.5f,
        xpos - width * 0.25f / SCREEN_X,
        ypos - 0.7f * (height / SCREEN_Y) + height * 0.2f * 0.5f / SCREEN_Y,
        200, 100, 200, 4, std::string("SAVE"))) {
    this->height = height;
    this->width = width;
    this->xpos = xpos;
    this->ypos = ypos;
    this->saveAs = saveAs;
}

void DrawingBoard::pushToRenderer(Renderer* renderer) {

    float leftX = xpos - 0.5f * (width / SCREEN_X);
    float rightX = xpos + 0.5f * (width / SCREEN_X);
    float topY = ypos + 0.5f * (height / SCREEN_Y);
    float bottomY = ypos - 0.5f * (height / SCREEN_Y);

    Renderer::QuadVertices quadVertices = { leftX, topY, rightX, topY, leftX, bottomY, rightX, bottomY };
    Renderer::ColorRGB color(255, 255, 255);
    renderer->drawQuad(quadVertices, color, texIndex, nullptr);

    for (const auto& point : drawingPoints) {
        float pointSize = 10;
        float leftX = point.first - (pointSize / SCREEN_X);
        float rightX = point.first + (pointSize / SCREEN_X);
        float topY = point.second + (pointSize / SCREEN_Y);
        float bottomY = point.second - (pointSize / SCREEN_Y);

        Renderer::QuadVertices quadVertices = { leftX, topY, rightX, topY, leftX, bottomY, rightX, bottomY };
        Renderer::ColorRGB color(0, 0, 0);
        renderer->drawQuad(quadVertices, color, 0, nullptr);
    }
    clearBtn.pushToRenderer(renderer);
    saveBtn.pushToRenderer(renderer);

}
bool DrawingBoard::isClicked(GLFWwindow* window) {

    if (this->clearBtn.isClicked(window)) {
        this->drawingPoints.clear();
        saved = false;
        this->saveBtn.message = "SAVE";
    }
    else if (saveBtn.isClicked(window)) {

        if (saved || saveRequested) return false;
        
        saveRequested = true;
        this->saveBtn.message = "SAVED!";

    }
    else if (isHovered(window) && (glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS)) {
        double x, y;
        glfwGetCursorPos(window, &x, &y);

        float xNDC = static_cast<float>(x) / SCREEN_X * 2.0f - 1.0f;
        float yNDC = 1.0f - static_cast<float>(y) / SCREEN_Y * 2.0f;

        drawingPoints.push_back(std::make_pair(xNDC, yNDC));
        //std::cout << "Added point: " << xNDC << ", " << yNDC << std::endl;

        saved = false;
        this->saveBtn.message = "SAVE";

        return true;
    }
    return false;
}