diff --git a/CMakeLists.txt b/CMakeLists.txt index 14b7a40..68c4ad2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,7 +65,7 @@ target_link_libraries(kekengine PUBLIC microtar_static) find_package(PkgConfig REQUIRED) pkg_check_modules(FREETYPE REQUIRED freetype2) target_link_libraries(kekengine PUBLIC ${FREETYPE_LIBRARIES}) -target_include_directories(kekengine PRIVATE ${FREETYPE_INCLUDE_DIRS}) +target_include_directories(kekengine PUBLIC ${FREETYPE_INCLUDE_DIRS}) if(UNIX) target_link_libraries(kekengine PUBLIC glfw GLEW GL) diff --git a/src/kekengine/cpp/engine.cpp b/src/kekengine/cpp/engine.cpp index bfb72b3..7bfc84b 100644 --- a/src/kekengine/cpp/engine.cpp +++ b/src/kekengine/cpp/engine.cpp @@ -22,12 +22,14 @@ #include "gameobject.h" #include "scene.h" #include "defaults.h" +#include "ui.h" +#include "uielements.h" kek::KekData kek::kekData; namespace kek::Engine { -static TextObject *fpsText; +static TextElement *fpsText; static void framebufferSizeCallback(GLFWwindow *window, int w, int h) { glViewport(0, 0, w, h); @@ -170,8 +172,11 @@ int init() { return KEK_ERROR; } + UIElement::init(); + Defaults::init(); - fpsText = new TextObject(new Font("font/MaredivRegular-yeg3.ttf"), "HELLO WORLD!"); + fpsText = new TextElement(px(0), px(0)); + //fpsText = new TextObject(new Font(KEK_DEFAULT_FONT), "HELLO WORLD!"); return KEK_SUCCESS; } @@ -280,10 +285,12 @@ int start() { glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glm::mat4 uiProjection = glm::ortho(0.0f, (float) kekData.screenWidth, (float) kekData.screenHeight, 0.0f); + int time = (int) (glfwGetTime() * 10); - if(time != prevTime) fpsText->setText(std::to_string((int) floor(1.0f / kekData.lastFrameTime)) + " (" + std::to_string(kekData.lastFrameTime * 1000) + ")"); + if(time != prevTime) fpsText->setText("FPS: " + std::to_string((int) floor(1.0f / kekData.lastFrameTime)) + " (" + std::to_string(kekData.lastFrameTime * 1000) + " ms)"); prevTime = time; - fpsText->getFont()->drawText(fpsText, 0, fpsText->getMetrics(24).height, 24, Colors::RED); + fpsText->draw(UIPoint(0, 0), uiProjection); glDisable(GL_BLEND); glEnable(GL_DEPTH_TEST); @@ -295,8 +302,6 @@ int start() { auto end = std::chrono::high_resolution_clock::now(); std::chrono::duration secsTaken = end - start; kekData.lastFrameTime = secsTaken.count(); - //std::cout << "FT: " << kekData.lastFrameTime << '\n'; - //std::cout << "FR: " << (1.0f / kekData.lastFrameTime) << '\n'; } return KEK_SUCCESS; diff --git a/src/kekengine/cpp/ui.cpp b/src/kekengine/cpp/ui.cpp index f495be3..60e6e8c 100644 --- a/src/kekengine/cpp/ui.cpp +++ b/src/kekengine/cpp/ui.cpp @@ -6,6 +6,8 @@ namespace kek { +Font *UIElement::defaultFont = nullptr; + UIElement::UIElement(UIValue x, UIValue y): x(x), y(y) { } @@ -50,30 +52,30 @@ inline int UIElement::uiToScreen(UIValue val) { return px; } -/*void UIElement::drawAll(int x, int y, glm::mat4 projection) { - if(!isVisible()) return; - draw(x, y, projection); +void UIElement::drawAll(UIPoint screenPos, glm::mat4 projection) { + if(!visible) return; + draw(screenPos, projection); for(UIElement *child : children) { UIPoint pos = child->getPosition(); - child->drawAll(x + pos.x, y + pos.y, projection); + child->drawAll(UIPoint(screenPos.x + pos.x, screenPos.y + pos.y), projection); } } -void UIElement::hoverAll(int x, int y, int absX, int absY) { +void UIElement::hoverAll(UIPoint pos, UIPoint screenPos) { UIBounds bounds = getBounds(); - if(x < bounds.x || y < bounds.y || x >= bounds.x + bounds.w || y >= bounds.y + bounds.h) { // Only used by topmost parent UIElement + if(!bounds.contains(pos)) { // Only used by topmost parent UIElement if(hovering) hoverExitAll(); return; } UIElement *hoveredChild = NULL; for(UIElement *child : children) { - UIPoint pos = child->getPosition(); - int relX = x - pos.x; - int relY = y - pos.y; + UIPoint childPos = child->getPosition(); + int relX = pos.x - childPos.x; + int relY = pos.y - childPos.y; UIBounds b = child->getBounds(); - if(relX < b.x || relY < b.y || relX > b.x + b.w || relY > b.y + b.h) continue; + if(!b.contains(UIPoint(relX, relY))) continue; hoveredChild = child; } @@ -82,13 +84,13 @@ void UIElement::hoverAll(int x, int y, int absX, int absY) { } if(hoveredChild) { - UIPoint pos = hoveredChild->getPosition(); - hoveredChild->hoverAll(x - pos.x, y - pos.y, absX, absY); + UIPoint childPos = hoveredChild->getPosition(); + hoveredChild->hoverAll(UIPoint(pos.x - childPos.x, pos.y - childPos.y), screenPos); } - if(!hovering) hoverEnter(); + if(!hovering) hoverEnter(pos, screenPos); hovering = true; - hover(x, y, absX, absY); + hover(pos, screenPos); } void UIElement::hoverExitAll() { @@ -100,32 +102,32 @@ void UIElement::hoverExitAll() { hoverExit(); } -void UIElement::clickAll(int x, int y, int absX, int absY, MouseButton button) { +void UIElement::clickAll(UIPoint pos, UIPoint screenPos, GLFWMouseButton button) { UIBounds bounds = getBounds(); - if(x < bounds.x || y < bounds.y || x >= bounds.x + bounds.w || y >= bounds.y + bounds.h) return; // Only used by topmost parent UIElement + if(!bounds.contains(pos)) return; // Only used by topmost parent UIElement UIElement *clickedChild = NULL; for(UIElement *child : children) { if(!child->clickable) continue; - UIPoint pos = child->getPosition(); - int relX = x - pos.x; - int relY = y - pos.y; + UIPoint childPos = child->getPosition(); + int relX = pos.x - childPos.x; + int relY = pos.y - childPos.y; UIBounds b = child->getBounds(); - if(relX < b.x || relY < b.y || relX > b.x + b.w || relY > b.y + b.h) continue; + if(!b.contains(UIPoint(relX, relY))) continue; clickedChild = child; } if(clickedChild != NULL) { - UIPoint pos = clickedChild->getPosition(); - clickedChild->clickAll(x - pos.x, y - pos.y, absX, absY, button); + UIPoint childPos = clickedChild->getPosition(); + clickedChild->clickAll(UIPoint(pos.x - childPos.x, pos.y - childPos.y), screenPos, button); }else { - click(x, y, absX, absY, button); + click(pos, screenPos, button); } } bool UIElement::focusEnterAll(UIPoint pos, UIPoint screenPos) { UIBounds bounds = getBounds(); - if(pos.x < bounds.x || pos.y < bounds.y || pos.x >= bounds.x + bounds.w || pos.y >= bounds.y + bounds.h) return false; + if(!bounds.contains(pos)) return false; UIElement *focusedChild = nullptr; for(UIElement *child : children) { @@ -133,13 +135,13 @@ bool UIElement::focusEnterAll(UIPoint pos, UIPoint screenPos) { int relX = pos.x - childPos.x; int relY = pos.y - childPos.y; UIBounds b = child->getBounds(); - if(relX < b.x || relY < b.y || relX > b.x + b.w || relY > b.y + b.h) continue; + if(!b.contains(UIPoint(relX, relY))) continue; focusedChild = child; } if(focusedChild) { UIPoint childPos = focusedChild->getPosition(); - bool focusable = focusedChild->focusEnterAll(pos.x - childPos.x, pos.y - childPos.y); + bool focusable = focusedChild->focusEnterAll(UIPoint(pos.x - childPos.x, pos.y - childPos.y), screenPos); if(focusable) return true; } @@ -153,21 +155,21 @@ bool UIElement::focusEnterAll(UIPoint pos, UIPoint screenPos) { UIElement *UIElement::dragEnterAll(UIPoint pos, UIPoint screenPos) { UIBounds bounds = getBounds(); - if(x < bounds.x || y < bounds.y || x >= bounds.x + bounds.w || y >= bounds.y + bounds.h) return nullptr; // Only used by topmost parent UIElement + if(!bounds.contains(pos)) return nullptr; // Only used by topmost parent UIElement UIElement *draggedChild = nullptr; for(UIElement *child : children) { - UIPoint pos = child->getPosition(); - int relX = x - pos.x; - int relY = y - pos.y; + UIPoint childPos = child->getPosition(); + int relX = pos.x - childPos.x; + int relY = pos.y - childPos.y; UIBounds b = child->getBounds(); - if(relX < b.x || relY < b.y || relX > b.x + b.w || relY > b.y + b.h) continue; + if(!b.contains(UIPoint(relX, relY))) continue; draggedChild = child; } if(draggedChild) { - UIPoint pos = draggedChild->getPosition(); - UIElement *dragged = draggedChild->dragEnterAll(x - pos.x, y - pos.y, absX, absY); + UIPoint childPos = draggedChild->getPosition(); + UIElement *dragged = draggedChild->dragEnterAll(UIPoint(pos.x - childPos.x, pos.y - childPos.y), screenPos); if(dragged) return dragged; } @@ -177,8 +179,7 @@ UIElement *UIElement::dragEnterAll(UIPoint pos, UIPoint screenPos) { } void UIElement::init() { - defaultFont = new Font("fonts/DejaVuSans.ttf"); + defaultFont = new Font(KEK_DEFAULT_FONT); } -*/ } diff --git a/src/kekengine/cpp/uielements.cpp b/src/kekengine/cpp/uielements.cpp new file mode 100644 index 0000000..f6ad967 --- /dev/null +++ b/src/kekengine/cpp/uielements.cpp @@ -0,0 +1,140 @@ +#include "uielements.h" + +namespace kek { + +static inline int offsetX(int w, Origin origin) { + switch(origin) { + case Origin::TOP_LEFT: + case Origin::BOTTOM_LEFT: + case Origin::LEFT_CENTER: + return 0; + + case Origin::TOP_RIGHT: + case Origin::BOTTOM_RIGHT: + case Origin::RIGHT_CENTER: + return -w; + + case Origin::TOP_CENTER: + case Origin::BOTTOM_CENTER: + case Origin::CENTER: + return -w / 2; + } + + return 0; +} + +static inline int offsetY(int h, Origin origin) { + switch(origin) { + case Origin::TOP_LEFT: + case Origin::TOP_CENTER: + case Origin::TOP_RIGHT: + return 0; + + case Origin::BOTTOM_LEFT: + case Origin::BOTTOM_CENTER: + case Origin::BOTTOM_RIGHT: + return -h; + + case Origin::LEFT_CENTER: + case Origin::RIGHT_CENTER: + case Origin::CENTER: + return -h / 2; + } + + return 0; +} + +static inline UIBounds offsetUIBounds(int w, int h, Origin origin) { + return UIBounds(offsetX(w, origin), offsetY(h, origin), w, h); +} + +TextElement::TextElement(UIValue x, UIValue y, Font *font): UIElement(x, y) { + this->text = new TextObject(font, "Text"); + this->sizePixels = KEK_DEFAULT_FONT_SIZE_PIXELS; + this->color = Colors::WHITE; + this->textMode = TextMode::ORIGIN; + this->textBounds = TextBounds::SMALLEST; +} + +TextElement::TextElement(UIValue x, UIValue y): TextElement(x, y, UIElement::defaultFont) {} + +TextElement::~TextElement() { + delete text; +} + +UIElementType TextElement::getType() { + return UIElementType::TEXT; +} + +UIBounds TextElement::getBounds() { + TextMetrics metrics = text->getMetrics(sizePixels); + + int w = metrics.width, h, offsetY = 0; + switch(textBounds) { + case TextBounds::SMALLEST: + default: + h = metrics.height; + offsetY = metrics.offsetY; + break; + case TextBounds::LINE: + FontMetrics metrics = text->getFont()->getMetrics(sizePixels); + h = metrics.lineHeight; + offsetY = metrics.ascender; + break; + } + + switch(textMode) { + case TextMode::BASELINE: + return UIBounds(offsetX(w, origin), -offsetY, w, h); + case TextMode::ORIGIN: + default: + return offsetUIBounds(w, h, origin); + } + +} + +void TextElement::setText(std::string text) { + this->text->setText(text); +} + +void TextElement::setSizePixels(int sizePixels) { + this->sizePixels = sizePixels; +} + +void TextElement::setColor(Color color) { + this->color = color; +} + +void TextElement::setTextMode(TextMode textMode) { + this->textMode = textMode; +} + +void TextElement::setTextBounds(TextBounds textBounds) { + this->textBounds = textBounds; +} + +void TextElement::draw(UIPoint screenPos, glm::mat4 projection) { + UIBounds offset = getBounds(); + + switch(textMode) { + case TextMode::BASELINE: + text->getFont()->drawText(text, projection, offset.x + screenPos.x, screenPos.y, sizePixels, color); + break; + case TextMode::ORIGIN: + switch(textBounds) { + case TextBounds::SMALLEST: + default: + text->getFont()->drawTextFromOrigin(text, projection, offset.x + screenPos.x, offset.y + screenPos.y, sizePixels, color); + break; + case TextBounds::LINE: + FontMetrics metrics = text->getFont()->getMetrics(sizePixels); + int offsetY = metrics.ascender; + text->getFont()->drawText(text, projection, offset.x + screenPos.x, offset.y + screenPos.y + offsetY, sizePixels, color); + break; + } + + break; + } +} + +} diff --git a/src/kekengine/include/constants.h b/src/kekengine/include/constants.h index 7eaf920..48dc7a1 100644 --- a/src/kekengine/include/constants.h +++ b/src/kekengine/include/constants.h @@ -35,3 +35,6 @@ #define KEK_FONT_BITMAP_CHAR_BITS 8 // = log2(KEK_FONT_BITMAP_WIDTH_BLOCKS * KEK_FONT_BITMAP_HEIGHT_BLOCKS) #define KEK_FONT_BITMAP_CHAR_MASK 0xFF // = KEK_FONT_BITMAP_CHAR_BITS 1s in binary #define KEK_TEXT_BLOCK_SIZE 8 + +#define KEK_DEFAULT_FONT "font/MaredivRegular-yeg3.ttf" +#define KEK_DEFAULT_FONT_SIZE_PIXELS 24 diff --git a/src/kekengine/include/kekengine.h b/src/kekengine/include/kekengine.h index d6cd7ec..4081c97 100644 --- a/src/kekengine/include/kekengine.h +++ b/src/kekengine/include/kekengine.h @@ -4,6 +4,7 @@ #include "constants.h" #include "engine.h" #include "errordialog.h" +#include "fonts.h" #include "gameobject.h" #include "mesh.h" #include "object.h" @@ -13,3 +14,5 @@ #include "shader.h" #include "types.h" #include "utils.h" +#include "ui.h" +#include "uielements.h" diff --git a/src/kekengine/include/ui.h b/src/kekengine/include/ui.h index 760f4f0..15d8842 100644 --- a/src/kekengine/include/ui.h +++ b/src/kekengine/include/ui.h @@ -14,21 +14,26 @@ namespace kek { +struct UIPoint { + int x, y; + UIPoint(int x, int y) { + this->x = x; + this->y = y; + } +}; + struct UIBounds { int x, y, w, h; + UIBounds(int x, int y, int w, int h) { this->x = x; this->y = y; this->w = w; this->h = h; } -}; -struct UIPoint { - int x, y; - UIPoint(int x, int y) { - this->x = x; - this->y = y; + inline bool contains(UIPoint pos) { + return pos.x < x || pos.y < y || pos.x >= x + w || pos.y >= y + h; } }; diff --git a/src/kekengine/include/uielements.h b/src/kekengine/include/uielements.h new file mode 100644 index 0000000..cdcae9c --- /dev/null +++ b/src/kekengine/include/uielements.h @@ -0,0 +1,41 @@ +#pragma once + +#include "ui.h" + +namespace kek { + +class TextElement: public UIElement { + +protected: + TextObject *text; + int sizePixels; + Color color; + TextMode textMode; + TextBounds textBounds; + +public: + TextElement(UIValue x, UIValue y, Font *font); + + TextElement(UIValue x, UIValue y); + + virtual ~TextElement(); + + virtual UIElementType getType(); + + virtual UIBounds getBounds(); + + void setText(std::string text); + + void setSizePixels(int sizePixels); + + void setColor(Color color); + + void setTextMode(TextMode textMode); + + void setTextBounds(TextBounds textBounds); + + virtual void draw(UIPoint screenPos, glm::mat4 projection); + +}; + +}