#include "gui/skin.h" #include "num_util.h" #include "main/references.h" #include "main/logging.h" #include 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 dynStyleIndices; std::map dynElementFlags; std::map dynFontIndices; std::map dynColorIndices; std::map dynFlagNames; unsigned nextElementFlag = 1; bool elementLower(Element* x, Element* y) { return x->flags < y->flags; } int getDynamicIndex(std::map& 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 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)); } }; };