Files
starruler-linux/source/game/gui/skin.cpp
T
2018-07-17 14:15:37 +02:00

481 lines
12 KiB
C++

#include "gui/skin.h"
#include "num_util.h"
#include "main/references.h"
#include "main/logging.h"
#include <map>
const gui::skin::Element* currentDrawnElement = 0;
vec2i eleDestSize;
void shader_skin_margin_src(float* margin,unsigned short,void*) {
if(currentDrawnElement) {
margin[0] = (float)currentDrawnElement->margin.topLeft.x;
margin[1] = (float)currentDrawnElement->margin.topLeft.y;
margin[2] = (float)currentDrawnElement->margin.botRight.x;
margin[3] = (float)currentDrawnElement->margin.botRight.y;
}
}
void shader_skin_margin_dest(float* margin,unsigned short,void*) {
if(currentDrawnElement) {
recti destMargin = currentDrawnElement->getDestinationMargin(eleDestSize);
margin[0] = (float)destMargin.topLeft.x;
margin[1] = (float)destMargin.topLeft.y;
margin[2] = (float)destMargin.botRight.x;
margin[3] = (float)destMargin.botRight.y;
}
}
void shader_skin_src_pos(float* pos,unsigned short,void*) {
if(currentDrawnElement) {
pos[0] = (float)currentDrawnElement->area.topLeft.x;
pos[1] = (float)currentDrawnElement->area.topLeft.y;
}
}
void shader_skin_src_size(float* size,unsigned short,void*) {
if(currentDrawnElement) {
size[0] = (float)currentDrawnElement->area.getWidth();
size[1] = (float)currentDrawnElement->area.getHeight();
}
}
void shader_skin_dst_size(float* size,unsigned short,void*) {
if(currentDrawnElement) {
size[0] = (float)eleDestSize.x;
size[1] = (float)eleDestSize.y;
}
}
void shader_skin_mode(float* modes,unsigned short,void*) {
if(currentDrawnElement) {
modes[0] = (float)currentDrawnElement->horizMode;
modes[1] = (float)currentDrawnElement->vertMode;
}
}
void shader_skin_gradientCount(float* count,unsigned short,void*) {
if(currentDrawnElement) {
*count = (float)currentDrawnElement->gradients.size();
}
}
void shader_skin_gradientMode(float* count,unsigned short,void*) {
if(currentDrawnElement) {
*count = (float)currentDrawnElement->gradMode;
}
}
void shader_skin_gradientRects(float* rects, unsigned short n,void*) {
if(currentDrawnElement) {
unsigned short cnt = (unsigned short)currentDrawnElement->gradients.size();
if(cnt < n)
n = cnt;
if(n == 0)
return;
recti baseRect(0,0, eleDestSize.width, eleDestSize.height);
for(unsigned short i = 0; i < n; ++i) {
auto& gradient = currentDrawnElement->gradients[i];
recti rect = gradient.area.evaluate(baseRect);
rects[i*4+0] = (float)rect.topLeft.x;
rects[i*4+1] = (float)rect.topLeft.y;
rects[i*4+2] = (float)rect.botRight.x;
rects[i*4+3] = (float)rect.botRight.y;
}
}
}
void shader_skin_gradientCols(float* cols, unsigned short n, void*) {
if(currentDrawnElement) {
n /= 4;
unsigned short cnt = (unsigned short)currentDrawnElement->gradients.size();
if(cnt < n)
n = cnt;
if(n == 0)
return;
Colorf* fcols = (Colorf*)cols;
for(unsigned short i = 0; i < n; ++i, fcols += 4) {
auto& gradient = currentDrawnElement->gradients[i];
new(fcols+0) Colorf(gradient.colors[0]);
new(fcols+1) Colorf(gradient.colors[1]);
new(fcols+2) Colorf(gradient.colors[2]);
new(fcols+3) Colorf(gradient.colors[3]);
}
}
}
namespace gui {
namespace skin {
std::map<std::string,unsigned> dynStyleIndices;
std::map<std::string,unsigned> dynElementFlags;
std::map<std::string,unsigned> dynFontIndices;
std::map<std::string,unsigned> dynColorIndices;
std::map<unsigned,std::string> dynFlagNames;
unsigned nextElementFlag = 1;
bool elementLower(Element* x, Element* y) {
return x->flags < y->flags;
}
int getDynamicIndex(std::map<std::string, unsigned>& dyn, const std::string& name, bool addIfMissing) {
auto index = dyn.find(name);
if(index == dyn.end()) {
if(addIfMissing) {
unsigned newIndex = (unsigned)dyn.size();
dyn[name] = newIndex;
return newIndex;
}
else {
return -1;
}
}
return index->second;
}
unsigned getStyleCount() {
return (unsigned)dynStyleIndices.size();
}
int getStyleIndex(const std::string& name, bool addIfMissing) {
return getDynamicIndex(dynStyleIndices, name, addIfMissing);
}
int getElementFlag(const std::string& name, bool addIfMissing) {
if(name == "Normal")
return 0;
auto index = dynElementFlags.find(name);
if(index == dynElementFlags.end()) {
if(addIfMissing) {
if(nextElementFlag == 0) {
nextElementFlag = 1;
error("Error: Skin exceeded limit of 32 unique element flags.");
}
unsigned flag = nextElementFlag;
dynElementFlags[name] = flag;
dynFlagNames[flag] = name;
if(flag == 0x100000000)
nextElementFlag = 0;
else
nextElementFlag = nextElementFlag << 1;
return flag;
}
else {
return 0;
}
}
return index->second;
}
std::string getElementFlagName(unsigned flag) {
if(flag == 0)
return "Normal";
unsigned check = 0x1;
unsigned cnt = 0;
std::string name;
for(unsigned i = 0; i < 32; ++i, check <<= 1) {
if((flag & check) != 0) {
if(cnt != 0)
name += ", ";
auto it = dynFlagNames.find(check);
if(it != dynFlagNames.end())
name += it->second;
else
name += "???";
++cnt;
}
}
return name;
}
int getColorIndex(const std::string& name, bool addIfMissing) {
return getDynamicIndex(dynColorIndices, name, addIfMissing);
}
int getFontIndex(const std::string& name, bool addIfMissing) {
return getDynamicIndex(dynFontIndices, name, addIfMissing);
}
void clearDynamicIndices() {
dynElementFlags.clear();
dynFlagNames.clear();
dynFontIndices.clear();
dynColorIndices.clear();
dynStyleIndices.clear();
nextElementFlag = 1;
}
void enumerateStyleIndices(std::function<void(const std::string&,unsigned)> callback) {
foreach(i, dynStyleIndices)
callback(i->first,i->second);
}
void enumerateElementFlags(void (*cb)(const std::string&,unsigned)) {
(*cb)("Normal", 0);
foreach(i, dynElementFlags)
(*cb)(i->first,i->second);
}
void enumerateColorIndices(void (*cb)(const std::string&,unsigned)) {
foreach(i, dynColorIndices)
(*cb)(i->first,i->second);
}
void enumerateFontIndices(void (*cb)(const std::string&,unsigned)) {
foreach(i, dynFontIndices)
(*cb)(i->first,i->second);
}
void Style::addElement(Element* ele) {
auto at = std::lower_bound(elements.begin(), elements.end(), ele, elementLower);
if(at == elements.end())
elements.push_back(ele);
else
elements.insert(at, ele);
}
Element* Style::getExactElement(unsigned flags) const {
foreach(it, elements) {
if((*it)->flags == flags)
return *it;
}
return nullptr;
}
const Element* Style::getElement(unsigned flags) const {
if(elements.empty())
return nullptr;
Element ele;
ele.flags = flags;
auto at = std::lower_bound(elements.begin(), elements.end(), &ele, elementLower);
size_t index;
if(at == elements.end())
index = elements.size() - 1;
else
index = at - elements.begin();
if(elements[index]->flags == flags)
return elements[index];
while(index > 0 && (~flags & elements[index]->flags))
--index;
return elements[index];
}
bool Skin::hasStyle(unsigned index) const {
if(index >= styles.size())
return false;
if(!styles[index])
return false;
return true;
}
const Style& Skin::getStyle(unsigned index) const {
if(index >= styles.size())
return *errorStyle;
if(!styles[index])
return *errorStyle;
return *styles[index];
}
const Element& Skin::getElement(unsigned index, unsigned flags) const {
if(index < styles.size() && styles[index]) {
const Element* ele = styles[index]->getElement(flags);
if(ele)
return *ele;
}
return *errorElement;
}
const render::Font& Skin::getFont(unsigned index) const {
if(index < (unsigned)fonts.size() && fonts[index])
return *fonts[index];
return *errorFont;
}
Color Skin::getColor(unsigned index) const {
if(index < (unsigned)colors.size())
return colors[index];
return Color();
}
void Skin::setStyle(unsigned index, Style* style) {
if(index >= (unsigned)styles.size())
styles.resize(index+1, 0);
styles[index] = style;
}
void Skin::setColor(unsigned index, Color color) {
if(index >= (unsigned)colors.size())
colors.resize(index+1);
colors[index] = color;
}
void Skin::setFont(unsigned index, const render::Font* font) {
if(index >= (unsigned)fonts.size())
fonts.resize(index+1, 0);
fonts[index] = font;
}
Skin::Skin() : fonts(), material(0) {
errorFont = render::Font::createDummyFont();
errorElement = new Element();
errorStyle = new Style();
}
void Gradient::draw(render::RenderDriver& driver, recti pos,
const recti* clip) const {
recti absPos = area.evaluate(pos);
driver.drawRectangle(absPos, 0, 0, colors, clip);
}
void Layer::draw(render::RenderDriver& driver, recti pos,
const recti* clip, Color color) const {
if(!ele)
return;
recti absPos = area.evaluate(pos);
ele->draw(driver, absPos, clip, hasOverride ? override : color);
}
void Element::clear() {
layers.clear();
gradients.clear();
margin = recti();
filled = true;
horizMode = DM_Uniform;
vertMode = DM_Uniform;
aspectMargin = AMM_None;
}
void Element::draw(render::RenderDriver& driver, recti pos,
const recti* clip, Color color) const {
Color colors[4] = {color, color, color, color};
foreach(it, layers)
(*it).draw(driver, pos, clip, color);
//TODO: Support no fill
if(!area.empty() || gradMode == GM_Overlay) {
currentDrawnElement = this;
eleDestSize = pos.getSize();
driver.drawRectangle(pos, material, 0, colors, clip);
currentDrawnElement = 0;
}
}
recti Element::getDestinationMargin(vec2i size) const {
recti destMargin;
switch(aspectMargin) {
case AMM_None:
destMargin.topLeft.x = margin.topLeft.x;
destMargin.topLeft.y = margin.topLeft.y;
destMargin.botRight.x = margin.botRight.x;
destMargin.botRight.y = margin.botRight.y;
break;
case AMM_Horizontal: {
float l_ratio = ((float)margin.topLeft.x) / ((float)area.getHeight());
float r_ratio = ((float)margin.botRight.x) / ((float)area.getHeight());
destMargin.topLeft.x = (int)(l_ratio * (float)size.height);
destMargin.topLeft.y = margin.topLeft.y;
destMargin.botRight.x = (int)(r_ratio * (float)size.height);
destMargin.botRight.y = margin.botRight.y;
} break;
case AMM_Vertical: {
float l_ratio = ((float)margin.topLeft.y) / ((float)area.getWidth());
float r_ratio = ((float)margin.botRight.y) / ((float)area.getWidth());
destMargin.topLeft.x = margin.topLeft.x;
destMargin.topLeft.y = (int)(l_ratio * (float)size.width);
destMargin.botRight.x = margin.botRight.x;
destMargin.botRight.y = (int)(r_ratio * (float)size.width);
} break;
}
return destMargin;
}
bool Element::isPixelActive(recti box, vec2i px) const {
if(!material)
return true;
auto* tex = material->textures[0];
if(!tex)
return true;
recti dmargin = getDestinationMargin(box.getSize());
int tx = 0;
switch(horizMode) {
case DM_Uniform:
tx = px.x + area.topLeft.x;
break;
case DM_Scaled:
case DM_Tiled:
if(px.x < dmargin.topLeft.x) {
tx = (int)((float)px.x * (float)margin.topLeft.x / (float)dmargin.topLeft.x) + area.topLeft.x;
}
else if(px.x >= box.getWidth() - dmargin.botRight.x) {
tx = area.botRight.x - (box.getWidth() -
(int)((float)px.x * (float)margin.botRight.x / (float)dmargin.botRight.x));
}
else {
tx = area.topLeft.x + margin.topLeft.x;
if(horizMode == DM_Scaled) {
tx += (int)(((double)(px.x - dmargin.topLeft.x)/
(double)(box.getWidth() - dmargin.topLeft.x - dmargin.botRight.x))
* (double)(area.getWidth() - margin.topLeft.x - margin.botRight.x));
}
else {
tx += (px.x - dmargin.topLeft.x) % (area.getWidth() - margin.topLeft.x - margin.botRight.x);
}
}
break;
}
int ty = 0;
switch(vertMode) {
case DM_Uniform:
ty = px.y + area.topLeft.y;
break;
case DM_Scaled:
case DM_Tiled:
if(px.y < dmargin.topLeft.y) {
ty = (int)((float)px.y * (float)margin.topLeft.y / (float)dmargin.topLeft.y) + area.topLeft.y;
}
else if(px.y >= box.getWidth() - dmargin.botRight.y) {
ty = area.botRight.y - (box.getHeight() -
(int)((float)px.y * (float)margin.botRight.y / (float)dmargin.botRight.y));
}
else {
ty = area.topLeft.y + margin.topLeft.y;
if(horizMode == DM_Scaled) {
ty += (int)(((double)(px.y - dmargin.topLeft.y)/
(double)(box.getHeight() - dmargin.topLeft.y - dmargin.botRight.y))
* (double)(area.getHeight() - margin.topLeft.y - margin.botRight.y));
}
else {
ty += (px.y - dmargin.topLeft.y) % (area.getHeight() - margin.topLeft.y - margin.botRight.y);
}
}
break;
}
return tex->isPixelActive(vec2i(tx, ty));
}
};
};