Add gradients and borders to FormSpec boxes (#8676)

This commit is contained in:
v-rob 2020-08-19 18:14:47 -07:00 committed by GitHub
parent 471497fa91
commit 83d0c360cc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 220 additions and 49 deletions

@ -2442,6 +2442,8 @@ Elements
* Simple colored box
* `color` is color specified as a `ColorString`.
If the alpha component is left blank, the box will be semitransparent.
If the color is not specified, the box will use the options specified by
its style. If the color is specified, all styling options will be ignored.
### `dropdown[<X>,<Y>;<W>;<name>;<item 1>,<item 2>, ...,<item n>;<selected idx>;<index event>]`
@ -2708,21 +2710,23 @@ Setting a property to nothing will reset it to the default value. For example:
Some types may inherit styles from parent types.
* animated_image, inherits from image
* box
* button
* button_exit, inherits from button
* checkbox
* scrollbar
* table
* textlist
* dropdown
* field
* pwdfield, inherits from field
* textarea
* label
* vertlabel, inherits from field
* image
* image_button
* item_image_button
* label
* pwdfield, inherits from field
* scrollbar
* tabheader
* table
* textarea
* textlist
* vertlabel, inherits from label
### Valid Properties
@ -2731,7 +2735,18 @@ Some types may inherit styles from parent types.
* noclip - boolean, set to true to allow the element to exceed formspec bounds.
* box
* noclip - boolean, set to true to allow the element to exceed formspec bounds.
* Default to false in formspec_version version 3 or higher
* Defaults to false in formspec_version version 3 or higher
* **Note**: `colors`, `bordercolors`, and `borderwidths` accept multiple input types:
* Single value (e.g. `#FF0`): All corners/borders.
* Two values (e.g. `red,#FFAAFF`): top-left and bottom-right,top-right and bottom-left/
top and bottom,left and right.
* Four values (e.g. `blue,#A0F,green,#FFFA`): top-left/top and rotates clockwise.
* These work similarly to CSS borders.
* colors - `ColorString`. Sets the color(s) of the box corners. Default `black`.
* bordercolors - `ColorString`. Sets the color(s) of the borders. Default `black`.
* borderwidths - Integer. Sets the width(s) of the borders in pixels. If the width is
negative, the border will extend inside the box, whereas positive extends outside
the box. A width of zero results in no border; this is default.
* button, button_exit, image_button, item_image_button
* alpha - boolean, whether to draw alpha in bgimg. Default true.
* bgcolor - color, sets button tint.
@ -2767,12 +2782,6 @@ Some types may inherit styles from parent types.
* textcolor - color, default white.
* checkbox
* noclip - boolean, set to true to allow the element to exceed formspec bounds.
* scrollbar
* noclip - boolean, set to true to allow the element to exceed formspec bounds.
* table, textlist
* font - Sets font type. See button `font` property for more information.
* font_size - Sets font size. See button `font_size` property for more information.
* noclip - boolean, set to true to allow the element to exceed formspec bounds.
* dropdown
* noclip - boolean, set to true to allow the element to exceed formspec bounds.
* field, pwdfield, textarea
@ -2797,9 +2806,15 @@ Some types may inherit styles from parent types.
* fgimg_pressed - image when pressed. Defaults to fgimg when not provided.
* This is deprecated, use states instead.
* NOTE: The parameters of any given image_button will take precedence over fgimg/fgimg_pressed
* scrollbar
* noclip - boolean, set to true to allow the element to exceed formspec bounds.
* tabheader
* noclip - boolean, set to true to allow the element to exceed formspec bounds.
* textcolor - color. Default white.
* table, textlist
* font - Sets font type. See button `font` property for more information.
* font_size - Sets font size. See button `font_size` property for more information.
* noclip - boolean, set to true to allow the element to exceed formspec bounds.
### Valid States

@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "util/string.h"
#include <algorithm>
#include <array>
#include <vector>
#pragma once
@ -50,6 +51,9 @@ public:
PADDING,
FONT,
FONT_SIZE,
COLORS,
BORDERCOLORS,
BORDERWIDTHS,
NUM_PROPERTIES,
NONE
};
@ -106,6 +110,12 @@ public:
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 {
return NONE;
}
@ -187,6 +197,42 @@ public:
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];
@ -334,6 +380,24 @@ public:
}
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;

@ -20,9 +20,14 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "guiBox.h"
GUIBox::GUIBox(gui::IGUIEnvironment *env, gui::IGUIElement *parent, s32 id,
const core::rect<s32> &rectangle, const video::SColor &color) :
const core::rect<s32> &rectangle,
const std::array<video::SColor, 4> &colors,
const std::array<video::SColor, 4> &bordercolors,
const std::array<s32, 4> &borderwidths) :
gui::IGUIElement(gui::EGUIET_ELEMENT, env, parent, id, rectangle),
m_color(color)
m_colors(colors),
m_bordercolors(bordercolors),
m_borderwidths(borderwidths)
{
}
@ -31,8 +36,81 @@ void GUIBox::draw()
if (!IsVisible)
return;
Environment->getVideoDriver()->draw2DRectangle(m_color, AbsoluteRect,
&AbsoluteClippingRect);
std::array<s32, 4> negative_borders = {0, 0, 0, 0};
std::array<s32, 4> positive_borders = {0, 0, 0, 0};
for (size_t i = 0; i <= 3; i++) {
if (m_borderwidths[i] > 0)
positive_borders[i] = m_borderwidths[i];
else
negative_borders[i] = m_borderwidths[i];
}
v2s32 upperleft = AbsoluteRect.UpperLeftCorner;
v2s32 lowerright = AbsoluteRect.LowerRightCorner;
v2s32 topleft_border = {
upperleft.X - positive_borders[3],
upperleft.Y - positive_borders[0]
};
v2s32 topleft_rect = {
upperleft.X - negative_borders[3],
upperleft.Y - negative_borders[0]
};
v2s32 lowerright_border = {
lowerright.X + positive_borders[1],
lowerright.Y + positive_borders[2]
};
v2s32 lowerright_rect = {
lowerright.X + negative_borders[1],
lowerright.Y + negative_borders[2]
};
core::rect<s32> main_rect(
topleft_rect.X,
topleft_rect.Y,
lowerright_rect.X,
lowerright_rect.Y
);
std::array<core::rect<s32>, 4> border_rects;
border_rects[0] = core::rect<s32>(
topleft_border.X,
topleft_border.Y,
lowerright_border.X,
topleft_rect.Y
);
border_rects[1] = core::rect<s32>(
lowerright_rect.X,
topleft_rect.Y,
lowerright_border.X,
lowerright_rect.Y
);
border_rects[2] = core::rect<s32>(
topleft_border.X,
lowerright_rect.Y,
lowerright_border.X,
lowerright_border.Y
);
border_rects[3] = core::rect<s32>(
topleft_border.X,
topleft_rect.Y,
topleft_rect.X,
lowerright_rect.Y
);
video::IVideoDriver *driver = Environment->getVideoDriver();
driver->draw2DRectangle(main_rect, m_colors[0], m_colors[1], m_colors[3],
m_colors[2], nullptr);
for (size_t i = 0; i <= 3; i++)
driver->draw2DRectangle(m_bordercolors[i], border_rects[i], nullptr);
IGUIElement::draw();
}

@ -19,16 +19,23 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#pragma once
#include <vector>
#include <array>
#include "irrlichttypes_extrabloated.h"
class GUIBox : public gui::IGUIElement
{
public:
GUIBox(gui::IGUIEnvironment *env, gui::IGUIElement *parent, s32 id,
const core::rect<s32> &rectangle, const video::SColor &color);
const core::rect<s32> &rectangle,
const std::array<video::SColor, 4> &colors,
const std::array<video::SColor, 4> &bordercolors,
const std::array<s32, 4> &borderwidths);
virtual void draw() override;
private:
video::SColor m_color;
std::array<video::SColor, 4> m_colors;
std::array<video::SColor, 4> m_bordercolors;
std::array<s32, 4> m_borderwidths;
};

@ -2211,16 +2211,16 @@ void GUIFormSpecMenu::parseItemImageButton(parserData* data, const std::string &
void GUIFormSpecMenu::parseBox(parserData* data, const std::string &element)
{
std::vector<std::string> parts = split(element,';');
std::vector<std::string> parts = split(element, ';');
if ((parts.size() == 3) ||
((parts.size() > 3) && (m_formspec_version > FORMSPEC_API_VERSION)))
{
std::vector<std::string> v_pos = split(parts[0],',');
std::vector<std::string> v_geom = split(parts[1],',');
std::vector<std::string> v_pos = split(parts[0], ',');
std::vector<std::string> v_geom = split(parts[1], ',');
MY_CHECKPOS("box",0);
MY_CHECKGEOM("box",1);
MY_CHECKPOS("box", 0);
MY_CHECKGEOM("box", 1);
v2s32 pos;
v2s32 geom;
@ -2234,36 +2234,43 @@ void GUIFormSpecMenu::parseBox(parserData* data, const std::string &element)
geom.Y = stof(v_geom[1]) * spacing.Y;
}
FieldSpec spec(
"",
L"",
L"",
258 + m_fields.size(),
-2
);
spec.ftype = f_Box;
auto style = getDefaultStyleForElement("box", spec.fname);
video::SColor tmp_color;
std::array<video::SColor, 4> colors;
std::array<video::SColor, 4> bordercolors = {0x0, 0x0, 0x0, 0x0};
std::array<s32, 4> borderwidths = {0, 0, 0, 0};
if (parseColorString(parts[2], tmp_color, false, 0x8C)) {
FieldSpec spec(
"",
L"",
L"",
258 + m_fields.size(),
-2
);
spec.ftype = f_Box;
core::rect<s32> rect(pos, pos + geom);
GUIBox *e = new GUIBox(Environment, data->current_parent, spec.fid,
rect, tmp_color);
auto style = getDefaultStyleForElement("box", spec.fname);
e->setNotClipped(style.getBool(StyleSpec::NOCLIP, m_formspec_version < 3));
e->drop();
m_fields.push_back(spec);
if (parseColorString(parts[2], tmp_color, true, 0x8C)) {
colors = {tmp_color, tmp_color, tmp_color, tmp_color};
} else {
errorstream<< "Invalid Box element(" << parts.size() << "): '" << element << "' INVALID COLOR" << std::endl;
colors = style.getColorArray(StyleSpec::COLORS, {0x0, 0x0, 0x0, 0x0});
bordercolors = style.getColorArray(StyleSpec::BORDERCOLORS,
{0x0, 0x0, 0x0, 0x0});
borderwidths = style.getIntArray(StyleSpec::BORDERWIDTHS, {0, 0, 0, 0});
}
core::rect<s32> rect(pos, pos + geom);
GUIBox *e = new GUIBox(Environment, data->current_parent, spec.fid, rect,
colors, bordercolors, borderwidths);
e->setNotClipped(style.getBool(StyleSpec::NOCLIP, m_formspec_version < 3));
e->drop();
m_fields.push_back(spec);
return;
}
errorstream<< "Invalid Box element(" << parts.size() << "): '" << element << "'" << std::endl;
errorstream << "Invalid Box element(" << parts.size() << "): '" << element
<< "'" << std::endl;
}
void GUIFormSpecMenu::parseBackgroundColor(parserData* data, const std::string &element)