minetest/src/gui/StyleSpec.h
Vincent Robinson 009e39e73b
FormSpec: Add list spacing, slot size, and noclip (#10083)
* Add list spacing, slot size, and noclip

* Simplify StyleSpec

* Add test cases

Co-authored-by: rubenwardy <rw@rubenwardy.com>
2021-01-23 12:46:19 -08:00

477 lines
11 KiB
C++

/*
Minetest
Copyright (C) 2019 rubenwardy
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "client/tile.h" // ITextureSource
#include "client/fontengine.h"
#include "debug.h"
#include "irrlichttypes_extrabloated.h"
#include "util/string.h"
#include <algorithm>
#include <array>
#include <vector>
#pragma once
class StyleSpec
{
public:
enum Property
{
TEXTCOLOR,
BGCOLOR,
BGCOLOR_HOVERED, // Note: Deprecated property
BGCOLOR_PRESSED, // Note: Deprecated property
NOCLIP,
BORDER,
BGIMG,
BGIMG_HOVERED, // Note: Deprecated property
BGIMG_MIDDLE,
BGIMG_PRESSED, // Note: Deprecated property
FGIMG,
FGIMG_HOVERED, // Note: Deprecated property
FGIMG_PRESSED, // Note: Deprecated property
ALPHA,
CONTENT_OFFSET,
PADDING,
FONT,
FONT_SIZE,
COLORS,
BORDERCOLORS,
BORDERWIDTHS,
SOUND,
SPACING,
SIZE,
NUM_PROPERTIES,
NONE
};
enum State
{
STATE_DEFAULT = 0,
STATE_HOVERED = 1 << 0,
STATE_PRESSED = 1 << 1,
NUM_STATES = 1 << 2,
STATE_INVALID = 1 << 3,
};
private:
std::array<bool, NUM_PROPERTIES> property_set{};
std::array<std::string, NUM_PROPERTIES> properties;
State state_map = STATE_DEFAULT;
public:
static Property GetPropertyByName(const std::string &name)
{
if (name == "textcolor") {
return TEXTCOLOR;
} else if (name == "bgcolor") {
return BGCOLOR;
} else if (name == "bgcolor_hovered") {
return BGCOLOR_HOVERED;
} else if (name == "bgcolor_pressed") {
return BGCOLOR_PRESSED;
} else if (name == "noclip") {
return NOCLIP;
} else if (name == "border") {
return BORDER;
} else if (name == "bgimg") {
return BGIMG;
} else if (name == "bgimg_hovered") {
return BGIMG_HOVERED;
} else if (name == "bgimg_middle") {
return BGIMG_MIDDLE;
} else if (name == "bgimg_pressed") {
return BGIMG_PRESSED;
} else if (name == "fgimg") {
return FGIMG;
} else if (name == "fgimg_hovered") {
return FGIMG_HOVERED;
} else if (name == "fgimg_pressed") {
return FGIMG_PRESSED;
} else if (name == "alpha") {
return ALPHA;
} else if (name == "content_offset") {
return CONTENT_OFFSET;
} else if (name == "padding") {
return PADDING;
} else if (name == "font") {
return FONT;
} else if (name == "font_size") {
return FONT_SIZE;
} else if (name == "colors") {
return COLORS;
} else if (name == "bordercolors") {
return BORDERCOLORS;
} else if (name == "borderwidths") {
return BORDERWIDTHS;
} else if (name == "sound") {
return SOUND;
} else if (name == "spacing") {
return SPACING;
} else if (name == "size") {
return SIZE;
} else {
return NONE;
}
}
std::string get(Property prop, std::string def) const
{
const auto &val = properties[prop];
return val.empty() ? def : val;
}
void set(Property prop, const std::string &value)
{
properties[prop] = value;
property_set[prop] = true;
}
//! Parses a name and returns the corresponding state enum
static State getStateByName(const std::string &name)
{
if (name == "default") {
return STATE_DEFAULT;
} else if (name == "hovered") {
return STATE_HOVERED;
} else if (name == "pressed") {
return STATE_PRESSED;
} else {
return STATE_INVALID;
}
}
//! Gets the state that this style is intended for
State getState() const
{
return state_map;
}
//! Set the given state on this style
void addState(State state)
{
FATAL_ERROR_IF(state >= NUM_STATES, "Out-of-bounds state received");
state_map = static_cast<State>(state_map | state);
}
//! Using a list of styles mapped to state values, calculate the final
// combined style for a state by propagating values in its component states
static StyleSpec getStyleFromStatePropagation(const std::array<StyleSpec, NUM_STATES> &styles, State state)
{
StyleSpec temp = styles[StyleSpec::STATE_DEFAULT];
temp.state_map = state;
for (int i = StyleSpec::STATE_DEFAULT + 1; i <= state; i++) {
if ((state & i) != 0) {
temp = temp | styles[i];
}
}
return temp;
}
video::SColor getColor(Property prop, video::SColor def) const
{
const auto &val = properties[prop];
if (val.empty()) {
return def;
}
parseColorString(val, def, false, 0xFF);
return def;
}
video::SColor getColor(Property prop) const
{
const auto &val = properties[prop];
FATAL_ERROR_IF(val.empty(), "Unexpected missing property");
video::SColor color;
parseColorString(val, color, false, 0xFF);
return color;
}
std::array<video::SColor, 4> getColorArray(Property prop,
std::array<video::SColor, 4> def) const
{
const auto &val = properties[prop];
if (val.empty())
return def;
std::vector<std::string> strs;
if (!parseArray(val, strs))
return def;
for (size_t i = 0; i <= 3; i++) {
video::SColor color;
if (parseColorString(strs[i], color, false, 0xff))
def[i] = color;
}
return def;
}
std::array<s32, 4> getIntArray(Property prop, std::array<s32, 4> def) const
{
const auto &val = properties[prop];
if (val.empty())
return def;
std::vector<std::string> strs;
if (!parseArray(val, strs))
return def;
for (size_t i = 0; i <= 3; i++)
def[i] = stoi(strs[i]);
return def;
}
irr::core::rect<s32> getRect(Property prop, irr::core::rect<s32> def) const
{
const auto &val = properties[prop];
if (val.empty())
return def;
irr::core::rect<s32> rect;
if (!parseRect(val, &rect))
return def;
return rect;
}
irr::core::rect<s32> getRect(Property prop) const
{
const auto &val = properties[prop];
FATAL_ERROR_IF(val.empty(), "Unexpected missing property");
irr::core::rect<s32> rect;
parseRect(val, &rect);
return rect;
}
v2f32 getVector2f(Property prop, v2f32 def) const
{
const auto &val = properties[prop];
if (val.empty())
return def;
v2f32 vec;
if (!parseVector2f(val, &vec))
return def;
return vec;
}
v2s32 getVector2i(Property prop, v2s32 def) const
{
const auto &val = properties[prop];
if (val.empty())
return def;
v2f32 vec;
if (!parseVector2f(val, &vec))
return def;
return v2s32(vec.X, vec.Y);
}
v2s32 getVector2i(Property prop) const
{
const auto &val = properties[prop];
FATAL_ERROR_IF(val.empty(), "Unexpected missing property");
v2f32 vec;
parseVector2f(val, &vec);
return v2s32(vec.X, vec.Y);
}
gui::IGUIFont *getFont() const
{
FontSpec spec(FONT_SIZE_UNSPECIFIED, FM_Standard, false, false);
const std::string &font = properties[FONT];
const std::string &size = properties[FONT_SIZE];
if (font.empty() && size.empty())
return nullptr;
std::vector<std::string> modes = split(font, ',');
for (size_t i = 0; i < modes.size(); i++) {
if (modes[i] == "normal")
spec.mode = FM_Standard;
else if (modes[i] == "mono")
spec.mode = FM_Mono;
else if (modes[i] == "bold")
spec.bold = true;
else if (modes[i] == "italic")
spec.italic = true;
}
if (!size.empty()) {
int calc_size = 1;
if (size[0] == '*') {
std::string new_size = size.substr(1); // Remove '*' (invalid for stof)
calc_size = stof(new_size) * g_fontengine->getFontSize(spec.mode);
} else if (size[0] == '+' || size[0] == '-') {
calc_size = stoi(size) + g_fontengine->getFontSize(spec.mode);
} else {
calc_size = stoi(size);
}
spec.size = (unsigned)std::min(std::max(calc_size, 1), 999);
}
return g_fontengine->getFont(spec);
}
video::ITexture *getTexture(Property prop, ISimpleTextureSource *tsrc,
video::ITexture *def) const
{
const auto &val = properties[prop];
if (val.empty()) {
return def;
}
video::ITexture *texture = tsrc->getTexture(val);
return texture;
}
video::ITexture *getTexture(Property prop, ISimpleTextureSource *tsrc) const
{
const auto &val = properties[prop];
FATAL_ERROR_IF(val.empty(), "Unexpected missing property");
video::ITexture *texture = tsrc->getTexture(val);
return texture;
}
bool getBool(Property prop, bool def) const
{
const auto &val = properties[prop];
if (val.empty()) {
return def;
}
return is_yes(val);
}
inline bool isNotDefault(Property prop) const
{
return !properties[prop].empty();
}
inline bool hasProperty(Property prop) const { return property_set[prop]; }
StyleSpec &operator|=(const StyleSpec &other)
{
for (size_t i = 0; i < NUM_PROPERTIES; i++) {
auto prop = (Property)i;
if (other.hasProperty(prop)) {
set(prop, other.get(prop, ""));
}
}
return *this;
}
StyleSpec operator|(const StyleSpec &other) const
{
StyleSpec newspec = *this;
newspec |= other;
return newspec;
}
private:
bool parseArray(const std::string &value, std::vector<std::string> &arr) const
{
std::vector<std::string> strs = split(value, ',');
if (strs.size() == 1) {
arr = {strs[0], strs[0], strs[0], strs[0]};
} else if (strs.size() == 2) {
arr = {strs[0], strs[1], strs[0], strs[1]};
} else if (strs.size() == 4) {
arr = strs;
} else {
warningstream << "Invalid array size (" << strs.size()
<< " arguments): \"" << value << "\"" << std::endl;
return false;
}
return true;
}
bool parseRect(const std::string &value, irr::core::rect<s32> *parsed_rect) const
{
irr::core::rect<s32> rect;
std::vector<std::string> v_rect = split(value, ',');
if (v_rect.size() == 1) {
s32 x = stoi(v_rect[0]);
rect.UpperLeftCorner = irr::core::vector2di(x, x);
rect.LowerRightCorner = irr::core::vector2di(-x, -x);
} else if (v_rect.size() == 2) {
s32 x = stoi(v_rect[0]);
s32 y = stoi(v_rect[1]);
rect.UpperLeftCorner = irr::core::vector2di(x, y);
rect.LowerRightCorner = irr::core::vector2di(-x, -y);
// `-x` is interpreted as `w - x`
} else if (v_rect.size() == 4) {
rect.UpperLeftCorner = irr::core::vector2di(
stoi(v_rect[0]), stoi(v_rect[1]));
rect.LowerRightCorner = irr::core::vector2di(
stoi(v_rect[2]), stoi(v_rect[3]));
} else {
warningstream << "Invalid rectangle string format: \"" << value
<< "\"" << std::endl;
return false;
}
*parsed_rect = rect;
return true;
}
bool parseVector2f(const std::string &value, v2f32 *parsed_vec) const
{
v2f32 vec;
std::vector<std::string> v_vector = split(value, ',');
if (v_vector.size() == 1) {
f32 x = stof(v_vector[0]);
vec.X = x;
vec.Y = x;
} else if (v_vector.size() == 2) {
vec.X = stof(v_vector[0]);
vec.Y = stof(v_vector[1]);
} else {
warningstream << "Invalid 2d vector string format: \"" << value
<< "\"" << std::endl;
return false;
}
*parsed_vec = vec;
return true;
}
};