Add support for 9-sliced backgrounds (#8600)
authorrubenwardy <rw@rubenwardy.com>
Sat, 22 Jun 2019 14:03:54 +0000 (15:03 +0100)
committerSmallJoker <SmallJoker@users.noreply.github.com>
Sat, 22 Jun 2019 14:03:54 +0000 (16:03 +0200)
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
src/client/guiscalingfilter.cpp
src/client/guiscalingfilter.h
src/gui/guiFormSpecMenu.cpp
src/gui/guiFormSpecMenu.h

index 352b04cb0d4347bd0387597f2d3003e8bd85aa64..155da14e34ae3de046163fd238aea4e57d270740 100644 (file)
@@ -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
index 312f939398e9f3dadab041590257d2ef5d5f9cc5..3490c47e8dfee544f024af86197fa7c786bfe82e 100644 (file)
@@ -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);
+               }
+       }
+}
index a5cd7851144a0cb6fb69a1809c7e86b7e01a5181..1810095511a65a7caf648cb3c1799524006ff284 100644 (file)
@@ -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);
index 0514ffbbe54676f9be2562343476323653dd7bc3..9240dc4c822d95203e64bdd21cc70f6bff848240 100644 (file)
@@ -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]);
@@ -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;
        }
@@ -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();
@@ -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;
index ccd9cb7533c3ce5c68db9d626ace96b67f7221a4..b1ca9a48a29bdff32c011565847a436139b942ff 100644 (file)
@@ -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),
@@ -191,6 +203,7 @@ class GUIFormSpecMenu : public GUIModalMenu
                gui::IGUIButton *parent_button;
                v2s32 pos;
                v2s32 geom;
+               core::rect<s32> middle;
                bool scale;
                bool clip;
        };