Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
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
  • Loading branch information
rubenwardy authored and SmallJoker committed Jun 22, 2019
1 parent 4e3c191 commit 429a989
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 13 deletions.
17 changes: 15 additions & 2 deletions doc/lua_api.txt
Expand Up @@ -2034,13 +2034,26 @@ Elements

### `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
8 times 16px times 4 times 16px.

### `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:
image shall be sized 8 times 16px times 4 times 16px
* If `auto_clip` is `true`, the background is clipped to the formspec size
Expand Down
59 changes: 59 additions & 0 deletions src/client/guiscalingfilter.cpp
Expand Up @@ -167,3 +167,62 @@ void draw2DImageFilterScaled(video::IVideoDriver *driver, video::ITexture *txr,

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);
}
}
}
6 changes: 6 additions & 0 deletions src/client/guiscalingfilter.h
Expand Up @@ -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> *cliprect = 0, const video::SColor *const colors = 0,
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);
55 changes: 44 additions & 11 deletions src/gui/guiFormSpecMenu.cpp
Expand Up @@ -653,9 +653,8 @@ void GUIFormSpecMenu::parseBackground(parserData* data, const std::string &eleme
{
std::vector<std::string> parts = split(element,';');

if (((parts.size() == 3) || (parts.size() == 4)) ||
((parts.size() > 4) && (m_formspec_version > FORMSPEC_API_VERSION)))
{
if ((parts.size() >= 3 && parts.size() <= 5) ||
(parts.size() > 5 && 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::string name = unescape_string(parts[2]);
Expand All @@ -672,16 +671,37 @@ void GUIFormSpecMenu::parseBackground(parserData* data, const std::string &eleme
geom.Y = stof(v_geom[1]) * spacing.Y;

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.Y = stoi(v_pos[1]); //acts as offset
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)
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;
}
Expand Down Expand Up @@ -2514,6 +2534,8 @@ void GUIFormSpecMenu::drawMenu()
core::rect<s32> imgrect(0, 0, spec.geom.X, spec.geom.Y);
// Image rectangle on screen
core::rect<s32> rect = imgrect + spec.pos;
// Middle rect for 9-slicing
core::rect<s32> middle = spec.middle;

if (spec.clip) {
core::dimension2d<s32> absrec_size = AbsoluteRect.getSize();
Expand All @@ -2523,12 +2545,23 @@ void GUIFormSpecMenu::drawMenu()
AbsoluteRect.UpperLeftCorner.Y + absrec_size.Height + spec.pos.Y);
}

const video::SColor color(255,255,255,255);
const video::SColor colors[] = {color,color,color,color};
draw2DImageFilterScaled(driver, texture, rect,
core::rect<s32>(core::position2d<s32>(0,0),
core::dimension2di(texture->getOriginalSize())),
NULL/*&AbsoluteClippingRect*/, colors, true);
if (middle.getArea() == 0) {
const video::SColor color(255, 255, 255, 255);
const video::SColor colors[] = {color, color, color, color};
draw2DImageFilterScaled(driver, texture, rect,
core::rect<s32>(core::position2d<s32>(0, 0),
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 {
errorstream << "GUIFormSpecMenu::drawMenu() Draw backgrounds unable to load texture:" << std::endl;
errorstream << "\t" << spec.name << std::endl;
Expand Down
13 changes: 13 additions & 0 deletions src/gui/guiFormSpecMenu.h
Expand Up @@ -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,
const v2s32 &a_pos):
name(a_name),
Expand All @@ -191,6 +203,7 @@ class GUIFormSpecMenu : public GUIModalMenu
gui::IGUIButton *parent_button;
v2s32 pos;
v2s32 geom;
core::rect<s32> middle;
bool scale;
bool clip;
};
Expand Down

0 comments on commit 429a989

Please sign in to comment.