2019-03-15 20:03:12 +01:00
|
|
|
/*
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
|
2019-12-09 21:06:51 +01:00
|
|
|
#include "client/tile.h" // ITextureSource
|
2020-04-11 22:39:30 +02:00
|
|
|
#include "debug.h"
|
2019-03-15 20:03:12 +01:00
|
|
|
#include "irrlichttypes_extrabloated.h"
|
2019-12-09 21:06:51 +01:00
|
|
|
#include "util/string.h"
|
2019-03-16 22:38:36 +01:00
|
|
|
#include <array>
|
2019-03-15 20:03:12 +01:00
|
|
|
|
|
|
|
#pragma once
|
|
|
|
|
|
|
|
class StyleSpec
|
|
|
|
{
|
|
|
|
public:
|
2019-03-16 22:38:36 +01:00
|
|
|
enum Property
|
|
|
|
{
|
2019-03-15 20:03:12 +01:00
|
|
|
TEXTCOLOR,
|
|
|
|
BGCOLOR,
|
2020-04-11 22:39:30 +02:00
|
|
|
BGCOLOR_HOVERED, // Note: Deprecated property
|
|
|
|
BGCOLOR_PRESSED, // Note: Deprecated property
|
2019-03-16 22:38:36 +01:00
|
|
|
NOCLIP,
|
|
|
|
BORDER,
|
|
|
|
BGIMG,
|
2020-04-11 22:39:30 +02:00
|
|
|
BGIMG_HOVERED, // Note: Deprecated property
|
2020-01-26 20:35:26 +01:00
|
|
|
BGIMG_MIDDLE,
|
2020-04-11 22:39:30 +02:00
|
|
|
BGIMG_PRESSED, // Note: Deprecated property
|
2019-12-09 21:06:51 +01:00
|
|
|
FGIMG,
|
2020-04-11 22:39:30 +02:00
|
|
|
FGIMG_HOVERED, // Note: Deprecated property
|
|
|
|
FGIMG_PRESSED, // Note: Deprecated property
|
2019-03-16 22:38:36 +01:00
|
|
|
ALPHA,
|
2020-05-02 13:32:02 +02:00
|
|
|
CONTENT_OFFSET,
|
|
|
|
PADDING,
|
2019-03-16 22:38:36 +01:00
|
|
|
NUM_PROPERTIES,
|
|
|
|
NONE
|
2019-03-15 20:03:12 +01:00
|
|
|
};
|
2020-04-11 22:39:30 +02:00
|
|
|
enum State
|
|
|
|
{
|
|
|
|
STATE_DEFAULT = 0,
|
|
|
|
STATE_HOVERED = 1 << 0,
|
|
|
|
STATE_PRESSED = 1 << 1,
|
|
|
|
NUM_STATES = 1 << 2,
|
|
|
|
STATE_INVALID = 1 << 3,
|
|
|
|
};
|
2019-03-15 20:03:12 +01:00
|
|
|
|
|
|
|
private:
|
2019-12-15 00:03:33 +01:00
|
|
|
std::array<bool, NUM_PROPERTIES> property_set{};
|
2019-03-16 22:38:36 +01:00
|
|
|
std::array<std::string, NUM_PROPERTIES> properties;
|
2020-04-11 22:39:30 +02:00
|
|
|
State state_map = STATE_DEFAULT;
|
2019-03-15 20:03:12 +01:00
|
|
|
|
|
|
|
public:
|
2019-03-16 22:38:36 +01:00
|
|
|
static Property GetPropertyByName(const std::string &name)
|
|
|
|
{
|
2019-03-15 20:03:12 +01:00
|
|
|
if (name == "textcolor") {
|
|
|
|
return TEXTCOLOR;
|
|
|
|
} else if (name == "bgcolor") {
|
|
|
|
return BGCOLOR;
|
2019-10-12 18:44:23 +02:00
|
|
|
} else if (name == "bgcolor_hovered") {
|
|
|
|
return BGCOLOR_HOVERED;
|
|
|
|
} else if (name == "bgcolor_pressed") {
|
|
|
|
return BGCOLOR_PRESSED;
|
2019-03-16 22:38:36 +01:00
|
|
|
} else if (name == "noclip") {
|
|
|
|
return NOCLIP;
|
|
|
|
} else if (name == "border") {
|
|
|
|
return BORDER;
|
|
|
|
} else if (name == "bgimg") {
|
|
|
|
return BGIMG;
|
2019-10-12 18:44:23 +02:00
|
|
|
} else if (name == "bgimg_hovered") {
|
|
|
|
return BGIMG_HOVERED;
|
2020-01-26 20:35:26 +01:00
|
|
|
} else if (name == "bgimg_middle") {
|
|
|
|
return BGIMG_MIDDLE;
|
2019-03-16 22:38:36 +01:00
|
|
|
} else if (name == "bgimg_pressed") {
|
|
|
|
return BGIMG_PRESSED;
|
2019-12-09 21:06:51 +01:00
|
|
|
} else if (name == "fgimg") {
|
|
|
|
return FGIMG;
|
|
|
|
} else if (name == "fgimg_hovered") {
|
|
|
|
return FGIMG_HOVERED;
|
|
|
|
} else if (name == "fgimg_pressed") {
|
|
|
|
return FGIMG_PRESSED;
|
2019-03-16 22:38:36 +01:00
|
|
|
} else if (name == "alpha") {
|
|
|
|
return ALPHA;
|
2020-05-02 13:32:02 +02:00
|
|
|
} else if (name == "content_offset") {
|
|
|
|
return CONTENT_OFFSET;
|
|
|
|
} else if (name == "padding") {
|
|
|
|
return PADDING;
|
2019-03-15 20:03:12 +01:00
|
|
|
} else {
|
|
|
|
return NONE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-16 22:38:36 +01:00
|
|
|
std::string get(Property prop, std::string def) const
|
|
|
|
{
|
|
|
|
const auto &val = properties[prop];
|
|
|
|
return val.empty() ? def : val;
|
2019-03-15 20:03:12 +01:00
|
|
|
}
|
|
|
|
|
2019-03-16 22:38:36 +01:00
|
|
|
void set(Property prop, const std::string &value)
|
|
|
|
{
|
|
|
|
properties[prop] = value;
|
|
|
|
property_set[prop] = true;
|
2019-03-15 20:03:12 +01:00
|
|
|
}
|
|
|
|
|
2020-04-11 22:39:30 +02:00
|
|
|
//! 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;
|
|
|
|
}
|
|
|
|
|
2019-03-16 22:38:36 +01:00
|
|
|
video::SColor getColor(Property prop, video::SColor def) const
|
|
|
|
{
|
|
|
|
const auto &val = properties[prop];
|
|
|
|
if (val.empty()) {
|
2019-03-15 20:03:12 +01:00
|
|
|
return def;
|
|
|
|
}
|
|
|
|
|
2019-03-16 22:38:36 +01:00
|
|
|
parseColorString(val, def, false, 0xFF);
|
2019-03-15 20:03:12 +01:00
|
|
|
return def;
|
|
|
|
}
|
|
|
|
|
2019-03-16 22:38:36 +01:00
|
|
|
video::SColor getColor(Property prop) const
|
|
|
|
{
|
|
|
|
const auto &val = properties[prop];
|
|
|
|
FATAL_ERROR_IF(val.empty(), "Unexpected missing property");
|
2019-03-15 20:03:12 +01:00
|
|
|
|
|
|
|
video::SColor color;
|
2019-03-16 22:38:36 +01:00
|
|
|
parseColorString(val, color, false, 0xFF);
|
2019-03-15 20:03:12 +01:00
|
|
|
return color;
|
|
|
|
}
|
|
|
|
|
2020-01-26 20:35:26 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2020-05-02 13:32:02 +02:00
|
|
|
irr::core::vector2d<s32> getVector2i(Property prop, irr::core::vector2d<s32> def) const
|
|
|
|
{
|
|
|
|
const auto &val = properties[prop];
|
|
|
|
if (val.empty())
|
|
|
|
return def;
|
|
|
|
|
|
|
|
irr::core::vector2d<s32> vec;
|
|
|
|
if (!parseVector2i(val, &vec))
|
|
|
|
return def;
|
|
|
|
|
|
|
|
return vec;
|
|
|
|
}
|
|
|
|
|
|
|
|
irr::core::vector2d<s32> getVector2i(Property prop) const
|
|
|
|
{
|
|
|
|
const auto &val = properties[prop];
|
|
|
|
FATAL_ERROR_IF(val.empty(), "Unexpected missing property");
|
|
|
|
|
|
|
|
irr::core::vector2d<s32> vec;
|
|
|
|
parseVector2i(val, &vec);
|
|
|
|
return vec;
|
|
|
|
}
|
|
|
|
|
2019-12-09 21:06:51 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2019-03-16 22:38:36 +01:00
|
|
|
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();
|
2019-03-15 20:03:12 +01:00
|
|
|
}
|
|
|
|
|
2019-03-16 22:38:36 +01:00
|
|
|
inline bool hasProperty(Property prop) const { return property_set[prop]; }
|
|
|
|
|
|
|
|
StyleSpec &operator|=(const StyleSpec &other)
|
|
|
|
{
|
|
|
|
for (size_t i = 0; i < NUM_PROPERTIES; i++) {
|
2019-03-15 20:03:12 +01:00
|
|
|
auto prop = (Property)i;
|
|
|
|
if (other.hasProperty(prop)) {
|
2019-03-16 22:38:36 +01:00
|
|
|
set(prop, other.get(prop, ""));
|
2019-03-15 20:03:12 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
2019-03-16 22:38:36 +01:00
|
|
|
StyleSpec operator|(const StyleSpec &other) const
|
|
|
|
{
|
2019-03-15 20:03:12 +01:00
|
|
|
StyleSpec newspec = *this;
|
|
|
|
newspec |= other;
|
|
|
|
return newspec;
|
|
|
|
}
|
2020-01-26 20:35:26 +01:00
|
|
|
|
|
|
|
private:
|
|
|
|
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;
|
|
|
|
}
|
2020-05-02 13:32:02 +02:00
|
|
|
|
|
|
|
bool parseVector2i(const std::string &value, irr::core::vector2d<s32> *parsed_vec) const
|
|
|
|
{
|
|
|
|
irr::core::vector2d<s32> vec;
|
|
|
|
std::vector<std::string> v_vector = split(value, ',');
|
|
|
|
|
|
|
|
if (v_vector.size() == 1) {
|
|
|
|
s32 x = stoi(v_vector[0]);
|
|
|
|
vec.X = x;
|
|
|
|
vec.Y = x;
|
|
|
|
} else if (v_vector.size() == 2) {
|
|
|
|
s32 x = stoi(v_vector[0]);
|
|
|
|
s32 y = stoi(v_vector[1]);
|
|
|
|
vec.X = x;
|
|
|
|
vec.Y = y;
|
|
|
|
} else {
|
|
|
|
warningstream << "Invalid vector2d string format: \"" << value
|
|
|
|
<< "\"" << std::endl;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
*parsed_vec = vec;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
2019-03-15 20:03:12 +01:00
|
|
|
};
|