From 429a98964859b83016f2eb47a47a08ab8dc3c57e Mon Sep 17 00:00:00 2001 From: rubenwardy Date: Sat, 22 Jun 2019 15:03:54 +0100 Subject: [PATCH] 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 --- doc/lua_api.txt | 17 ++++++++-- src/client/guiscalingfilter.cpp | 59 +++++++++++++++++++++++++++++++++ src/client/guiscalingfilter.h | 6 ++++ src/gui/guiFormSpecMenu.cpp | 55 ++++++++++++++++++++++++------ src/gui/guiFormSpecMenu.h | 13 ++++++++ 5 files changed, 137 insertions(+), 13 deletions(-) diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 352b04cb0..155da14e3 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -2034,13 +2034,26 @@ Elements ### `background[,;,;]` -* 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[,;,;;]` -* 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[,;,;;;]` + +* 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 diff --git a/src/client/guiscalingfilter.cpp b/src/client/guiscalingfilter.cpp index 312f93939..3490c47e8 100644 --- a/src/client/guiscalingfilter.cpp +++ b/src/client/guiscalingfilter.cpp @@ -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 &rect, const core::rect &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 src({0, 0}, originalSize); + core::rect 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); + } + } +} diff --git a/src/client/guiscalingfilter.h b/src/client/guiscalingfilter.h index a5cd78511..181009551 100644 --- a/src/client/guiscalingfilter.h +++ b/src/client/guiscalingfilter.h @@ -48,3 +48,9 @@ void draw2DImageFilterScaled(video::IVideoDriver *driver, video::ITexture *txr, const core::rect &destrect, const core::rect &srcrect, const core::rect *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 &rect, const core::rect &middle); diff --git a/src/gui/guiFormSpecMenu.cpp b/src/gui/guiFormSpecMenu.cpp index 0514ffbbe..9240dc4c8 100644 --- a/src/gui/guiFormSpecMenu.cpp +++ b/src/gui/guiFormSpecMenu.cpp @@ -653,9 +653,8 @@ void GUIFormSpecMenu::parseBackground(parserData* data, const std::string &eleme { std::vector 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 v_pos = split(parts[0],','); std::vector v_geom = split(parts[1],','); 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; 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 middle; + if (parts.size() >= 5) { + std::vector 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; } @@ -2514,6 +2534,8 @@ void GUIFormSpecMenu::drawMenu() core::rect imgrect(0, 0, spec.geom.X, spec.geom.Y); // Image rectangle on screen core::rect rect = imgrect + spec.pos; + // Middle rect for 9-slicing + core::rect middle = spec.middle; if (spec.clip) { core::dimension2d absrec_size = AbsoluteRect.getSize(); @@ -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(core::position2d(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(core::position2d(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; diff --git a/src/gui/guiFormSpecMenu.h b/src/gui/guiFormSpecMenu.h index ccd9cb753..b1ca9a48a 100644 --- a/src/gui/guiFormSpecMenu.h +++ b/src/gui/guiFormSpecMenu.h @@ -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 &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), @@ -191,6 +203,7 @@ class GUIFormSpecMenu : public GUIModalMenu gui::IGUIButton *parent_button; v2s32 pos; v2s32 geom; + core::rect middle; bool scale; bool clip; };