Files
openglplayground/Widget.cc
2015-03-10 00:48:25 +01:00

356 lines
9.4 KiB
C++

#include <vector>
#define GLM_FORCE_RADIANS
#include <glm/gtc/packing.hpp>
#include "Widget.hh"
#include "texture.hh"
#include "shaders.hh"
Widget::Widget(int width, int height, std::string name)
: name_(std::move(name)), width_(width), height_(height),
background_(nullptr),
bg_(SDL_Color{0, 0, 0, 0}), fg_(SDL_Color{255, 255, 255, 255}),
padLeft_(0), padTop_(0), padRight_(0), padBottom_(0),
alignHoriz_(ALIGN_LEFT), alignVert_(ALIGN_TOP),
alignContHoriz_(ALIGN_LEFT), alignContVert_(ALIGN_TOP),
parent_(nullptr),
renderTop_(0), renderLeft_(0),
renderTexValid_(false), renderAttribsValid_(false),
renderTex_(nullptr), vaID_(0)
{
if ((width_ == WRAP_CONTENT) ||
(width_ == MATCH_PARENT) ||
(width_ == WRAP_CONTENT_FILL))
realWidth_ = 0;
else
realWidth_ = width_;
if ((height_ == WRAP_CONTENT) ||
(height_ == MATCH_PARENT) ||
(height_ == WRAP_CONTENT_FILL))
realHeight_ = 0;
else
realHeight_ = height_;
}
Widget::~Widget()
{
glDeleteVertexArrays(1, &vaID_);
}
void Widget::setSize(int width, int height)
{
if ((width_ != width) || (height_ != height)) {
width_ = width;
height_ = height;
if (width_ == MATCH_PARENT) {
if (parent_)
realWidth_ = parent_->realWidth_ - parent_->padLeft_ - parent_->padRight_;
else
realWidth_ = 0;
} else if ((width_ == WRAP_CONTENT) ||
(width_ == WRAP_CONTENT_FILL))
realWidth_ = 0;
else
realWidth_ = width_;
if (height_ == MATCH_PARENT) {
if (parent_)
realHeight_ = parent_->realHeight_ - parent_->padTop_ - parent_->padBottom_;
else
realHeight_ = 0;
} else if ((height_ == WRAP_CONTENT) ||
(height_ == WRAP_CONTENT_FILL))
realHeight_ = 0;
else
realHeight_ = height_;
if (parent_)
parent_->layout();
else
layout();
}
}
void Widget::setBackground(SDL_Surface* surf)
{
if (background_ != surf) {
background_ = surf;
invalidateGL();
}
}
void Widget::setBackgroundColor(SDL_Color bg)
{
if (bg_ != bg) {
bg_ = bg;
invalidateGL();
}
}
void Widget::setForegroundColor(SDL_Color fg)
{
if (fg_ != fg) {
fg_ = fg;
invalidateGL();
}
}
void Widget::setPadding(int left, int top, int right, int bottom)
{
if ((padLeft_ != left) ||
(padTop_ != top) ||
(padRight_ != right) ||
(padBottom_ != bottom)) {
padLeft_ = left;
padTop_ = top;
padRight_ = right;
padBottom_ = bottom;
layout();
}
}
void Widget::setAlignment(int horiz, int vert) {
if ((alignHoriz_ != horiz) ||
(alignVert_ != vert)) {
alignHoriz_ = horiz;
alignVert_ = vert;
invalidateGL();
}
}
void Widget::setContainerAlignment(int horiz, int vert) {
if ((alignContHoriz_ != horiz) ||
(alignContVert_ != vert)) {
alignContHoriz_ = horiz;
alignContVert_ = vert;
if (parent_)
parent_->layout();
}
}
void Widget::setParent(Widget *parent)
{
parent_ = parent;
if (width_ == MATCH_PARENT) {
if (parent_ && (parent_->realWidth_ > 0))
realWidth_ = parent_->realWidth_ - parent_->padLeft_ - parent_->padRight_;
else
realWidth_ = 0;
}
if (height_ == MATCH_PARENT) {
if (parent_ && (parent_->realHeight_ > 0))
realHeight_ = parent_->realHeight_ - parent_->padTop_ - parent_->padBottom_;
else
realHeight_ = 0;
}
}
void Widget::setGLRenderPos(int left, int top)
{
if ((top != renderTop_) ||
(left != renderLeft_)) {
renderAttribsValid_ = false;
renderTop_ = top;
renderLeft_ = left;
}
}
void Widget::render(SDL_Surface *dst, SDL_Rect *dstRect) const
{
if (background_) {
if (SDL_BlitScaled(background_, nullptr, dst, dstRect) != 0)
throw SDLException{};
} else {
// Skip completely transparent background
if (bg_.a == 0)
return;
// Simply write completely intransparent bg
if (bg_.a == 255) {
SDLSurfaceScopedLock lock{dst};
if (SDL_FillRect(dst, dstRect,
SDL_MapRGBA(dst->format, bg_.r, bg_.g, bg_.b, bg_.a)) != 0)
throw SDLException{};
return;
}
// Do proper blending blit for all other bgs
int w = dstRect?dstRect->w:dst->w;
int h = dstRect?dstRect->h:dst->h;
uint32_t rmask, gmask, bmask, amask;
int bpp;
if (!SDL_PixelFormatEnumToMasks(dst->format->format,
&bpp, &rmask, &gmask, &bmask, &amask))
throw SDLException{};
SDLSurfaceUPtr tmp{SDL_CreateRGBSurface(0, w, h, bpp,
rmask, gmask, bmask, amask)};
if (!tmp)
throw SDLException{};
{
SDLSurfaceScopedLock lock{tmp};
if (SDL_FillRect(tmp.get(), nullptr,
SDL_MapRGBA(tmp->format, bg_.r, bg_.g, bg_.b, bg_.a)) != 0)
throw SDLException{};
}
if (SDL_BlitSurface(tmp.get(), nullptr, dst, dstRect) != 0)
throw SDLException{};
}
}
SDL_Rect Widget::calcContentRect(SDL_Surface *dst, SDL_Rect *dstRect, SDL_Rect contentRect) const
{
SDL_Rect rect;
if (dstRect) {
rect.x = dstRect->x + padLeft_;
rect.y = dstRect->y + padTop_;
rect.w = dstRect->w - (padLeft_ + padRight_);
rect.h = dstRect->h - (padTop_ + padBottom_);
} else {
rect.x = padLeft_;
rect.y = padTop_;
rect.w = dst->w - padLeft_ - padRight_;
rect.h = dst->h - padTop_ - padBottom_;
}
if (contentRect.w < rect.w) {
auto space = rect.w - contentRect.w;
if (alignHoriz_ == ALIGN_RIGHT) {
rect.x += space;
rect.w -= space;
} else if (alignHoriz_ == ALIGN_CENTER) {
rect.x += space/2;
rect.w -= space/2;
}
}
if (contentRect.h < rect.h) {
auto space = rect.h - contentRect.h;
if (alignHoriz_ == ALIGN_BOTTOM) {
rect.y += space;
rect.h -= space;
} else if (alignHoriz_ == ALIGN_CENTER) {
rect.y += space/2;
rect.h -= space/2;
}
}
return rect;
}
std::tuple<int, int> Widget::baseLayout(Widget *caller)
{
int width = realWidth_, height = realHeight_;
if ((width_ == MATCH_PARENT) && parent_) {
if ((parent_->getRealWidth() <= 0) && (parent_ != caller))
parent_->layout();
width = parent_->getRealWidth() - parent_->getLeftPadding() - parent_->getRightPadding();
} else if ((width_ == WRAP_CONTENT_FILL) && parent_ && (parent_->getRealWidth() > 0)) {
width = parent_->getRealWidth() - parent_->getLeftPadding() - parent_->getRightPadding();
} else if ((width_ == WRAP_CONTENT) || (width_ == WRAP_CONTENT_FILL)) {
width = 0;
}
if ((height_ == MATCH_PARENT) && parent_) {
if ((parent_->getRealHeight() <= 0) && (parent_ != caller))
parent_->layout();
height = parent_->getRealHeight() - parent_->getTopPadding() - parent_->getBottomPadding();
} else if ((height_ == WRAP_CONTENT_FILL) && parent_ && (parent_->getRealHeight() > 0)) {
height = parent_->getRealHeight() - parent_->getTopPadding() - parent_->getBottomPadding();
} else if ((height_ == WRAP_CONTENT) || (height_ == WRAP_CONTENT_FILL)) {
height = 0;
}
printf("baseLayout: %s: %d, %d\n", name_.c_str(), width, height);
return std::make_tuple(width, height);
}
void Widget::invalidateGL()
{
renderTexValid_ = false;
if (parent_)
parent_->invalidateGL();
}
void Widget::renderToGL() const
{
const size_t NUM_TRIANGLES = 6;
if (!renderTexValid_) {
printf("Widget::renderToGL: redraw texture for %s\n", name_.c_str());
uint32_t rmask, gmask, bmask, amask;
int bpp;
if (!SDL_PixelFormatEnumToMasks(SDL_PIXELFORMAT_ABGR8888,
&bpp, &rmask, &gmask, &bmask, &amask))
throw SDLException{};
SDLSurfaceUPtr tmp(SDL_CreateRGBSurface(0, realWidth_, realHeight_, bpp,
rmask, gmask, bmask, amask));
render(tmp.get(), nullptr);
if (!renderTex_ || (renderTex_->getWidth() != realWidth_) ||
(renderTex_->getHeight() != realHeight_)) {
renderTex_ = make_unique<Texture2D>(tmp.get());
// Texture resize invalidates vertex attributes
renderAttribsValid_ = false;
} else {
renderTex_->copyFromSurface(tmp.get());
}
renderTexValid_ = true;
} else
renderTex_->bind();
if (!renderAttribsValid_) {
printf("Widget::renderToGL: regen attribs for %s\n", name_.c_str());
short int t = renderTop_, l = renderLeft_, b = renderTop_+realHeight_,
r = renderLeft_+realWidth_;
std::vector<VertexAttribs> vertexAttribs{
{{l, t}, {0, 0}},
{{r, b}, {65535u, 65535u}},
{{r, t}, {65535u, 0}},
{{l, t}, {0, 0}},
{{l, b}, {0, 65535u}},
{{r, b}, {65535u, 65535u}}};
if (!vbo_) {
vbo_ = VBOManager::getInstance().alloc(sizeof(VertexAttribs)*NUM_TRIANGLES);
glBindBuffer(GL_ARRAY_BUFFER, vbo_.getVBOId());
glGenVertexArrays(1, &vaID_);
glBindVertexArray(vaID_);
glEnableVertexAttribArray(ATTRIB_VERTEX_POS);
glVertexAttribPointer(ATTRIB_VERTEX_POS, 2, GL_SHORT, GL_FALSE, sizeof(VertexAttribs),
(void*)(vbo_.getOfs()+offsetof(VertexAttribs, vertex)));
glEnableVertexAttribArray(ATTRIB_VERTEX_TC);
glVertexAttribPointer(ATTRIB_VERTEX_TC, 2, GL_UNSIGNED_SHORT, GL_TRUE,
sizeof(VertexAttribs),
(void*)(vbo_.getOfs()+offsetof(VertexAttribs, texCoords)));
} else {
glBindBuffer(GL_ARRAY_BUFFER, vbo_.getVBOId());
glBindVertexArray(vaID_);
}
glBufferSubData(GL_ARRAY_BUFFER, vbo_.getOfs(),
sizeof(VertexAttribs)*NUM_TRIANGLES,
static_cast<void const*>(vertexAttribs.data()));
renderAttribsValid_ = true;
} else
glBindVertexArray(vaID_);
glDrawArrays(GL_TRIANGLES, 0, NUM_TRIANGLES);
}