forked from Mirrorlandia_minetest/minetest
Add support for 9-sliced backgrounds (#8600)
9-slice textures are commonly used in GUIs to allow scaling them to match any resolution without distortion. https://en.wikipedia.org/wiki/9-slice_scaling
This commit is contained in:
parent
4e3c1916f7
commit
429a989648
@ -2034,13 +2034,26 @@ Elements
|
|||||||
|
|
||||||
### `background[<X>,<Y>;<W>,<H>;<texture name>]`
|
### `background[<X>,<Y>;<W>,<H>;<texture name>]`
|
||||||
|
|
||||||
* Use a background. Inventory rectangles are not drawn then.
|
|
||||||
* Example for formspec 8x4 in 16x resolution: image shall be sized
|
* Example for formspec 8x4 in 16x resolution: image shall be sized
|
||||||
8 times 16px times 4 times 16px.
|
8 times 16px times 4 times 16px.
|
||||||
|
|
||||||
### `background[<X>,<Y>;<W>,<H>;<texture name>;<auto_clip>]`
|
### `background[<X>,<Y>;<W>,<H>;<texture name>;<auto_clip>]`
|
||||||
|
|
||||||
* Use a background. Inventory rectangles are not drawn then.
|
* Example for formspec 8x4 in 16x resolution:
|
||||||
|
image shall be sized 8 times 16px times 4 times 16px
|
||||||
|
* If `auto_clip` is `true`, the background is clipped to the formspec size
|
||||||
|
(`x` and `y` are used as offset values, `w` and `h` are ignored)
|
||||||
|
|
||||||
|
### `background[<X>,<Y>;<W>,<H>;<texture name>;<auto_clip>;<middle>]`
|
||||||
|
|
||||||
|
* 9-sliced background. See https://en.wikipedia.org/wiki/9-slice_scaling
|
||||||
|
* Middle is a rect which defines the middle of the 9-slice.
|
||||||
|
* `x` - The middle will be x pixels from all sides.
|
||||||
|
* `x,y` - The middle will be x pixels from the horizontal and y from the vertical.
|
||||||
|
* `x,y,x2,y2` - The middle will start at x,y, and end at x2, y2. Negative x2 and y2 values
|
||||||
|
will be added to the width and height of the texture, allowing it to be used as the
|
||||||
|
distance from the far end.
|
||||||
|
* All numbers in middle are integers.
|
||||||
* Example for formspec 8x4 in 16x resolution:
|
* Example for formspec 8x4 in 16x resolution:
|
||||||
image shall be sized 8 times 16px times 4 times 16px
|
image shall be sized 8 times 16px times 4 times 16px
|
||||||
* If `auto_clip` is `true`, the background is clipped to the formspec size
|
* If `auto_clip` is `true`, the background is clipped to the formspec size
|
||||||
|
@ -167,3 +167,62 @@ void draw2DImageFilterScaled(video::IVideoDriver *driver, video::ITexture *txr,
|
|||||||
|
|
||||||
driver->draw2DImage(scaled, destrect, mysrcrect, cliprect, colors, usealpha);
|
driver->draw2DImage(scaled, destrect, mysrcrect, cliprect, colors, usealpha);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void draw2DImage9Slice(video::IVideoDriver *driver, video::ITexture *texture,
|
||||||
|
const core::rect<s32> &rect, const core::rect<s32> &middle)
|
||||||
|
{
|
||||||
|
const video::SColor color(255,255,255,255);
|
||||||
|
const video::SColor colors[] = {color,color,color,color};
|
||||||
|
|
||||||
|
auto originalSize = texture->getOriginalSize();
|
||||||
|
core::vector2di lowerRightOffset = core::vector2di(originalSize.Width, originalSize.Height) - middle.LowerRightCorner;
|
||||||
|
|
||||||
|
for (int y = 0; y < 3; ++y) {
|
||||||
|
for (int x = 0; x < 3; ++x) {
|
||||||
|
core::rect<s32> src({0, 0}, originalSize);
|
||||||
|
core::rect<s32> dest = rect;
|
||||||
|
|
||||||
|
switch (x) {
|
||||||
|
case 0:
|
||||||
|
dest.LowerRightCorner.X = rect.UpperLeftCorner.X + middle.UpperLeftCorner.X;
|
||||||
|
src.LowerRightCorner.X = middle.UpperLeftCorner.X;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
dest.UpperLeftCorner.X += middle.UpperLeftCorner.X;
|
||||||
|
dest.LowerRightCorner.X -= lowerRightOffset.X;
|
||||||
|
src.UpperLeftCorner.X = middle.UpperLeftCorner.X;
|
||||||
|
src.LowerRightCorner.X = middle.LowerRightCorner.X;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
dest.UpperLeftCorner.X = rect.LowerRightCorner.X - lowerRightOffset.X;
|
||||||
|
src.UpperLeftCorner.X = middle.LowerRightCorner.X;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (y) {
|
||||||
|
case 0:
|
||||||
|
dest.LowerRightCorner.Y = rect.UpperLeftCorner.Y + middle.UpperLeftCorner.Y;
|
||||||
|
src.LowerRightCorner.Y = middle.UpperLeftCorner.Y;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
dest.UpperLeftCorner.Y += middle.UpperLeftCorner.Y;
|
||||||
|
dest.LowerRightCorner.Y -= lowerRightOffset.Y;
|
||||||
|
src.UpperLeftCorner.Y = middle.UpperLeftCorner.Y;
|
||||||
|
src.LowerRightCorner.Y = middle.LowerRightCorner.Y;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
dest.UpperLeftCorner.Y = rect.LowerRightCorner.Y - lowerRightOffset.Y;
|
||||||
|
src.UpperLeftCorner.Y = middle.LowerRightCorner.Y;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
draw2DImageFilterScaled(driver, texture, dest,
|
||||||
|
src,
|
||||||
|
NULL/*&AbsoluteClippingRect*/, colors, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -48,3 +48,9 @@ void draw2DImageFilterScaled(video::IVideoDriver *driver, video::ITexture *txr,
|
|||||||
const core::rect<s32> &destrect, const core::rect<s32> &srcrect,
|
const core::rect<s32> &destrect, const core::rect<s32> &srcrect,
|
||||||
const core::rect<s32> *cliprect = 0, const video::SColor *const colors = 0,
|
const core::rect<s32> *cliprect = 0, const video::SColor *const colors = 0,
|
||||||
bool usealpha = false);
|
bool usealpha = false);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 9-slice / segment drawing
|
||||||
|
*/
|
||||||
|
void draw2DImage9Slice(video::IVideoDriver *driver, video::ITexture *texture,
|
||||||
|
const core::rect<s32> &rect, const core::rect<s32> &middle);
|
||||||
|
@ -653,9 +653,8 @@ void GUIFormSpecMenu::parseBackground(parserData* data, const std::string &eleme
|
|||||||
{
|
{
|
||||||
std::vector<std::string> parts = split(element,';');
|
std::vector<std::string> parts = split(element,';');
|
||||||
|
|
||||||
if (((parts.size() == 3) || (parts.size() == 4)) ||
|
if ((parts.size() >= 3 && parts.size() <= 5) ||
|
||||||
((parts.size() > 4) && (m_formspec_version > FORMSPEC_API_VERSION)))
|
(parts.size() > 5 && m_formspec_version > FORMSPEC_API_VERSION)) {
|
||||||
{
|
|
||||||
std::vector<std::string> v_pos = split(parts[0],',');
|
std::vector<std::string> v_pos = split(parts[0],',');
|
||||||
std::vector<std::string> v_geom = split(parts[1],',');
|
std::vector<std::string> v_geom = split(parts[1],',');
|
||||||
std::string name = unescape_string(parts[2]);
|
std::string name = unescape_string(parts[2]);
|
||||||
@ -672,16 +671,37 @@ void GUIFormSpecMenu::parseBackground(parserData* data, const std::string &eleme
|
|||||||
geom.Y = stof(v_geom[1]) * spacing.Y;
|
geom.Y = stof(v_geom[1]) * spacing.Y;
|
||||||
|
|
||||||
bool clip = false;
|
bool clip = false;
|
||||||
if (parts.size() == 4 && is_yes(parts[3])) {
|
if (parts.size() >= 4 && is_yes(parts[3])) {
|
||||||
pos.X = stoi(v_pos[0]); //acts as offset
|
pos.X = stoi(v_pos[0]); //acts as offset
|
||||||
pos.Y = stoi(v_pos[1]); //acts as offset
|
pos.Y = stoi(v_pos[1]); //acts as offset
|
||||||
clip = true;
|
clip = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
core::rect<s32> middle;
|
||||||
|
if (parts.size() >= 5) {
|
||||||
|
std::vector<std::string> v_middle = split(parts[4], ',');
|
||||||
|
if (v_middle.size() == 1) {
|
||||||
|
s32 x = stoi(v_middle[0]);
|
||||||
|
middle.UpperLeftCorner = core::vector2di(x, x);
|
||||||
|
middle.LowerRightCorner = core::vector2di(-x, -x);
|
||||||
|
} else if (v_middle.size() == 2) {
|
||||||
|
s32 x = stoi(v_middle[0]);
|
||||||
|
s32 y = stoi(v_middle[1]);
|
||||||
|
middle.UpperLeftCorner = core::vector2di(x, y);
|
||||||
|
middle.LowerRightCorner = core::vector2di(-x, -y);
|
||||||
|
// `-x` is interpreted as `w - x`
|
||||||
|
} else if (v_middle.size() == 4) {
|
||||||
|
middle.UpperLeftCorner = core::vector2di(stoi(v_middle[0]), stoi(v_middle[1]));
|
||||||
|
middle.LowerRightCorner = core::vector2di(stoi(v_middle[2]), stoi(v_middle[3]));
|
||||||
|
} else {
|
||||||
|
warningstream << "Invalid rectangle given to middle param of background[] element" << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!data->explicit_size && !clip)
|
if (!data->explicit_size && !clip)
|
||||||
warningstream << "invalid use of unclipped background without a size[] element" << std::endl;
|
warningstream << "invalid use of unclipped background without a size[] element" << std::endl;
|
||||||
|
|
||||||
m_backgrounds.emplace_back(name, pos, geom, clip);
|
m_backgrounds.emplace_back(name, pos, geom, middle, clip);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -2514,6 +2534,8 @@ void GUIFormSpecMenu::drawMenu()
|
|||||||
core::rect<s32> imgrect(0, 0, spec.geom.X, spec.geom.Y);
|
core::rect<s32> imgrect(0, 0, spec.geom.X, spec.geom.Y);
|
||||||
// Image rectangle on screen
|
// Image rectangle on screen
|
||||||
core::rect<s32> rect = imgrect + spec.pos;
|
core::rect<s32> rect = imgrect + spec.pos;
|
||||||
|
// Middle rect for 9-slicing
|
||||||
|
core::rect<s32> middle = spec.middle;
|
||||||
|
|
||||||
if (spec.clip) {
|
if (spec.clip) {
|
||||||
core::dimension2d<s32> absrec_size = AbsoluteRect.getSize();
|
core::dimension2d<s32> absrec_size = AbsoluteRect.getSize();
|
||||||
@ -2523,12 +2545,23 @@ void GUIFormSpecMenu::drawMenu()
|
|||||||
AbsoluteRect.UpperLeftCorner.Y + absrec_size.Height + spec.pos.Y);
|
AbsoluteRect.UpperLeftCorner.Y + absrec_size.Height + spec.pos.Y);
|
||||||
}
|
}
|
||||||
|
|
||||||
const video::SColor color(255,255,255,255);
|
if (middle.getArea() == 0) {
|
||||||
const video::SColor colors[] = {color,color,color,color};
|
const video::SColor color(255, 255, 255, 255);
|
||||||
draw2DImageFilterScaled(driver, texture, rect,
|
const video::SColor colors[] = {color, color, color, color};
|
||||||
core::rect<s32>(core::position2d<s32>(0,0),
|
draw2DImageFilterScaled(driver, texture, rect,
|
||||||
core::dimension2di(texture->getOriginalSize())),
|
core::rect<s32>(core::position2d<s32>(0, 0),
|
||||||
NULL/*&AbsoluteClippingRect*/, colors, true);
|
core::dimension2di(texture->getOriginalSize())),
|
||||||
|
NULL/*&AbsoluteClippingRect*/, colors, true);
|
||||||
|
} else {
|
||||||
|
// `-x` is interpreted as `w - x`
|
||||||
|
if (middle.LowerRightCorner.X < 0) {
|
||||||
|
middle.LowerRightCorner.X += texture->getOriginalSize().Width;
|
||||||
|
}
|
||||||
|
if (middle.LowerRightCorner.Y < 0) {
|
||||||
|
middle.LowerRightCorner.Y += texture->getOriginalSize().Height;
|
||||||
|
}
|
||||||
|
draw2DImage9Slice(driver, texture, rect, middle);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
errorstream << "GUIFormSpecMenu::drawMenu() Draw backgrounds unable to load texture:" << std::endl;
|
errorstream << "GUIFormSpecMenu::drawMenu() Draw backgrounds unable to load texture:" << std::endl;
|
||||||
errorstream << "\t" << spec.name << std::endl;
|
errorstream << "\t" << spec.name << std::endl;
|
||||||
|
@ -176,6 +176,18 @@ class GUIFormSpecMenu : public GUIModalMenu
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImageDrawSpec(const std::string &a_name,
|
||||||
|
const v2s32 &a_pos, const v2s32 &a_geom, const core::rect<s32> &middle, bool clip=false):
|
||||||
|
name(a_name),
|
||||||
|
parent_button(NULL),
|
||||||
|
pos(a_pos),
|
||||||
|
geom(a_geom),
|
||||||
|
middle(middle),
|
||||||
|
scale(true),
|
||||||
|
clip(clip)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
ImageDrawSpec(const std::string &a_name,
|
ImageDrawSpec(const std::string &a_name,
|
||||||
const v2s32 &a_pos):
|
const v2s32 &a_pos):
|
||||||
name(a_name),
|
name(a_name),
|
||||||
@ -191,6 +203,7 @@ class GUIFormSpecMenu : public GUIModalMenu
|
|||||||
gui::IGUIButton *parent_button;
|
gui::IGUIButton *parent_button;
|
||||||
v2s32 pos;
|
v2s32 pos;
|
||||||
v2s32 geom;
|
v2s32 geom;
|
||||||
|
core::rect<s32> middle;
|
||||||
bool scale;
|
bool scale;
|
||||||
bool clip;
|
bool clip;
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user