311 lines
7.4 KiB
C++
311 lines
7.4 KiB
C++
#include "ui.h"
|
|
|
|
#include <iostream>
|
|
#include <numeric>
|
|
#include <string_view>
|
|
#include <vector>
|
|
|
|
#include "input.h"
|
|
#include "internal.h"
|
|
#include "internal/ui.h"
|
|
#include "types.h"
|
|
|
|
namespace kek {
|
|
|
|
UIElement::UIElement(UIValue x, UIValue y)
|
|
: x(x),
|
|
y(y) {
|
|
}
|
|
|
|
UIElement::~UIElement() {
|
|
UI::unfocusElement(this);
|
|
}
|
|
|
|
// Returns the element's origin position relative to its parent in pixels (not offset by getBounds())
|
|
IntPoint2 UIElement::getOriginPosition() {
|
|
return IntPoint2(uiToScreen(x), uiToScreen(y));
|
|
}
|
|
|
|
// Returns the element's position relative to its parent in pixels (offset by getBounds(), aka. the position of the top-left of the element)
|
|
IntPoint2 UIElement::getPosition() {
|
|
UIBounds bounds = getBounds();
|
|
return IntPoint2(uiToScreen(x) + bounds.x, uiToScreen(y) + bounds.y);
|
|
}
|
|
|
|
// Returns the element's origin position on the screen in pixels (not offset by getBounds())
|
|
IntPoint2 UIElement::getScreenOriginPosition() {
|
|
IntPoint2 pos = getOriginPosition();
|
|
if(parent) {
|
|
IntPoint2 parentPos = parent->getScreenPosition();
|
|
pos.x += parentPos.x;
|
|
pos.y += parentPos.y;
|
|
}
|
|
|
|
return pos;
|
|
}
|
|
|
|
// Returns the element's position on the screen in pixels (offset by getBounds(), aka. the position of the top-left of the element)
|
|
IntPoint2 UIElement::getScreenPosition() {
|
|
IntPoint2 pos = getScreenOriginPosition();
|
|
UIBounds bounds = getBounds();
|
|
return IntPoint2(pos.x + bounds.x, pos.y + bounds.y);
|
|
}
|
|
|
|
// Returns the bounds of the element on the screen
|
|
UIBounds UIElement::getScreenBounds() {
|
|
IntPoint2 pos = getScreenOriginPosition();
|
|
UIBounds bounds = getBounds();
|
|
return UIBounds(pos.x + bounds.x, pos.y + bounds.y, bounds.w, bounds.h);
|
|
}
|
|
|
|
std::vector<UIElement *> UIElement::getChildren() {
|
|
return children;
|
|
}
|
|
|
|
void UIElement::addChild(UIElement *child) {
|
|
child->parent = this;
|
|
children.push_back(child);
|
|
}
|
|
|
|
void UIElement::removeChild(UIElement *child) {
|
|
child->parent = nullptr;
|
|
std::erase(children, child);
|
|
}
|
|
|
|
int UIElement::uiToScreen(UIValue val) {
|
|
float px = 0;
|
|
px += val.pixels;
|
|
if(parent) px += (int) (val.parentWidth * parent->getBounds().w);
|
|
px += val.screenWidth * kekData.screenWidth;
|
|
if(!parent) px += val.parentWidth * kekData.screenWidth;
|
|
if(parent) px += (int) (val.parentHeight * parent->getBounds().h);
|
|
px += val.screenHeight * kekData.screenHeight;
|
|
if(!parent) px += val.parentHeight * kekData.screenHeight;
|
|
return px;
|
|
}
|
|
|
|
int UIElement::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;
|
|
}
|
|
|
|
int UIElement::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;
|
|
}
|
|
|
|
UIBounds UIElement::offsetUIBounds(int w, int h, Origin origin) {
|
|
return UIBounds(offsetX(w, origin), offsetY(h, origin), w, h);
|
|
}
|
|
|
|
void UIElement::drawAll(IntPoint2 screenPos, glm::mat4 projection) {
|
|
if(!visible) return;
|
|
|
|
UIBounds bounds = getBounds();
|
|
|
|
if(enableClipping) {
|
|
glEnable(GL_SCISSOR_TEST);
|
|
glScissor(screenPos.x, kekData.screenHeight - (screenPos.y + bounds.h), bounds.w, bounds.h);
|
|
}
|
|
|
|
draw(screenPos, projection);
|
|
|
|
for(UIElement *child : children) {
|
|
IntPoint2 pos = child->getPosition();
|
|
child->drawAll(IntPoint2(screenPos.x + pos.x, screenPos.y + pos.y), projection);
|
|
}
|
|
|
|
if(enableClipping) {
|
|
glDisable(GL_SCISSOR_TEST);
|
|
}
|
|
}
|
|
|
|
static UIElement *findChildAtRelativePos(UIElement *element, IntPoint2 pos) {
|
|
UIElement *foundChild = nullptr;
|
|
|
|
for(UIElement *child : element->getChildren()) {
|
|
IntPoint2 childPos = child->getPosition();
|
|
IntPoint2 relativeChildPos = pos - childPos;
|
|
UIBounds b = child->getBounds();
|
|
if(!b.containsOffset(relativeChildPos)) continue;
|
|
foundChild = child;
|
|
}
|
|
|
|
return foundChild;
|
|
}
|
|
|
|
void UIElement::hoverAll(IntPoint2 pos, IntPoint2 screenPos) {
|
|
UIElement *hoveredChild = findChildAtRelativePos(this, pos);
|
|
|
|
for(UIElement *child : children) {
|
|
if(child != hoveredChild && child->hovering) child->hoverExitAll();
|
|
}
|
|
|
|
if(hoveredChild) {
|
|
IntPoint2 childPos = hoveredChild->getPosition();
|
|
hoveredChild->hoverAll(pos - childPos, screenPos);
|
|
}
|
|
|
|
if(!hovering) hoverEnter(pos, screenPos);
|
|
hovering = true;
|
|
hover(pos, screenPos);
|
|
}
|
|
|
|
void UIElement::hoverExitAll() {
|
|
if(!hovering) return;
|
|
hovering = false;
|
|
for(UIElement *child : children) {
|
|
if(child->hovering) child->hoverExitAll();
|
|
}
|
|
hoverExit();
|
|
}
|
|
|
|
bool UIElement::clickAll(IntPoint2 pos, IntPoint2 screenPos, GLFWMouseButton button) {
|
|
UIElement *clickedChild = findChildAtRelativePos(this, pos);
|
|
|
|
if(clickedChild != nullptr) {
|
|
IntPoint2 childPos = clickedChild->getPosition();
|
|
if(clickedChild->clickAll(pos - childPos, screenPos, button)) return true;
|
|
}
|
|
|
|
if(clickable) {
|
|
click(pos, screenPos, button);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool UIElement::focusEnterAll(IntPoint2 pos, IntPoint2 screenPos) {
|
|
UIElement *focusedChild = findChildAtRelativePos(this, pos);
|
|
|
|
if(focusedChild != nullptr) {
|
|
IntPoint2 childPos = focusedChild->getPosition();
|
|
if(focusedChild->focusEnterAll(pos - childPos, screenPos)) return true;
|
|
}
|
|
|
|
if(focusable) {
|
|
UI::focusElement(this);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
UIElement *UIElement::dragEnterAll(IntPoint2 pos, IntPoint2 screenPos) {
|
|
UIElement *draggedChild = findChildAtRelativePos(this, pos);
|
|
|
|
if(draggedChild) {
|
|
IntPoint2 childPos = draggedChild->getPosition();
|
|
UIElement *dragged = draggedChild->dragEnterAll(pos - childPos, screenPos);
|
|
if(dragged) return dragged;
|
|
}
|
|
|
|
if(draggable) {
|
|
// TODO: start drag
|
|
return this;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void UI::init() {
|
|
kekData.ui = new UIData();
|
|
kekData.ui->defaultFont = Font::load(KEK_DEFAULT_FONT);
|
|
kekData.ui->rectangleShader = new Shader("shader/rectangle/vertex.glsl", "shader/rectangle/fragment.glsl");
|
|
}
|
|
|
|
void UI::destroy() {
|
|
kekData.ui->defaultFont = nullptr;
|
|
delete kekData.ui->rectangleShader;
|
|
delete kekData.ui;
|
|
}
|
|
|
|
void UI::addElement(UIElement *element) {
|
|
kekData.ui->elements.push_back(element);
|
|
}
|
|
|
|
void UI::removeElement(UIElement *element) {
|
|
for(auto it = kekData.ui->elements.begin(); it < kekData.ui->elements.end(); it++) {
|
|
if(*it == element) {
|
|
kekData.ui->elements.erase(it);
|
|
delete element;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
std::vector<UIElement *> UI::getElements() {
|
|
return kekData.ui->elements;
|
|
}
|
|
|
|
void UI::focusElement(UIElement *element) {
|
|
if(kekData.ui->focusedElement != element) {
|
|
if(kekData.ui->focusedElement != nullptr) {
|
|
unfocusElement(kekData.ui->focusedElement);
|
|
}
|
|
|
|
kekData.ui->focusedElement = element;
|
|
if(element != nullptr) {
|
|
element->focused = true;
|
|
element->focusEnter();
|
|
}
|
|
}
|
|
}
|
|
|
|
void UI::unfocusElement(UIElement *element) {
|
|
if(kekData.ui->focusedElement == nullptr || element != kekData.ui->focusedElement) return;
|
|
|
|
UIElement *oldFocus = kekData.ui->focusedElement;
|
|
kekData.ui->focusedElement = nullptr;
|
|
oldFocus->focused = false;
|
|
oldFocus->focusExit();
|
|
}
|
|
|
|
UIElement *UI::findElementAt(IntPoint2 screenPos) {
|
|
UIElement *foundElement = nullptr;
|
|
|
|
for(UIElement *element : kekData.ui->elements) {
|
|
UIBounds bounds = element->getScreenBounds();
|
|
if(!bounds.contains(screenPos)) continue;
|
|
|
|
foundElement = element;
|
|
}
|
|
|
|
return foundElement;
|
|
}
|
|
|
|
}
|