Add animated_image[] formspec element (#9258)
authorHugues Ross <hugues.ross@gmail.com>
Sat, 15 Feb 2020 15:33:18 +0000 (10:33 -0500)
committerGitHub <noreply@github.com>
Sat, 15 Feb 2020 15:33:18 +0000 (15:33 +0000)
build/android/jni/Android.mk
doc/lua_api.txt
games/minimal/mods/test/formspec.lua
games/minimal/mods/test/textures/test_animation.jpg [new file with mode: 0644]
games/minimal/mods/test/textures/test_animation.png [new file with mode: 0644]
src/gui/CMakeLists.txt
src/gui/guiAnimatedImage.cpp [new file with mode: 0644]
src/gui/guiAnimatedImage.h [new file with mode: 0644]
src/gui/guiFormSpecMenu.cpp
src/gui/guiFormSpecMenu.h
util/travis/clang-format-whitelist.txt

index 6d21544bfbc8154be3bf81cd24fc3d03956817c6..72b0daab69387dee4c4b8dfc450e61cb15523c0b 100644 (file)
@@ -178,6 +178,7 @@ LOCAL_SRC_FILES := \
                jni/src/filesys.cpp                       \
                jni/src/genericobject.cpp                 \
                jni/src/gettext.cpp                       \
+               jni/src/gui/guiAnimatedImage.cpp          \
                jni/src/gui/guiBackgroundImage.cpp        \
                jni/src/gui/guiBox.cpp                    \
                jni/src/gui/guiButton.cpp                 \
index 75a083bdd4817370d7db5bc6398d113af0afa9ee..fe5b1a6261256da632e2dd7a175bf1c7ac5a9478 100644 (file)
@@ -2133,6 +2133,15 @@ Elements
 
 * Show an image
 
+### `animated_image[<X>,<Y>;<W>,<H>;<texture name>:<frame count>,<frame duration>]`
+
+* Show an animated image. The image is drawn like a "vertical_frames" tile
+    animation (See Tile animation definition), but uses a frame count/duration
+    for simplicity
+* `<texture name>` is the image to use
+* `<frame count>` is the number of frames animating the image
+* `<frame duration>` is in milliseconds
+
 ### `item_image[<X>,<Y>;<W>,<H>;<item name>]`
 
 * Show an inventory image of registered item/node
@@ -2580,6 +2589,8 @@ Some types may inherit styles from parent types.
 
 ### Valid Properties
 
+* animated_image
+    * 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
index 67aad3b204ec3b7cb3a17b47883090992241a44f..50c5068998d940420aa9f94297ef83704b4a61ee 100644 (file)
@@ -12,6 +12,7 @@ local clip_fs = [[
        style_type[dropdown;noclip=%c]\r
        style_type[scrollbar;noclip=%c]\r
        style_type[table;noclip=%c]\r
+       style_type[animated_image;noclip=%c]\r
 \r
        label[0,0;A clipping test]\r
        button[0,1;3,0.8;x;A clipping test]\r
@@ -25,6 +26,7 @@ local clip_fs = [[
        scrollbar[0,9;3,0.8;horizontal;x9;3]\r
        tablecolumns[text;text]\r
        table[0,10;3,1;x10;one,two,three,four;1]\r
+       animated_image[0,11;3,1;test_animation.png:4,100]\r
 ]]\r
 \r
 \r
@@ -119,8 +121,8 @@ local style_fs = [[
 \r
 local pages = {\r
        [[\r
+               formspec_version[3]\r
                size[12,12]\r
-               real_coordinates[true]\r
                image_button[0,0;1,1;logo.png;;1x1]\r
                image_button[1,0;2,2;logo.png;;2x2]\r
                button[0,2;1,1;;1x1]\r
@@ -157,7 +159,7 @@ local pages = {
                tabheader[6.5,0;6,0.65;name;Tab 1,Tab 2,Tab 3,Secrets;1;false;false]\r
        ]],\r
 \r
-               "size[12,12]real_coordinates[true]" ..\r
+               "formspec_version[3]size[12,12]" ..\r
                ("label[0.375,0.375;Styled - %s %s]"):format(\r
                        color("#F00", "red text"),\r
                        color("#77FF00CC", "green text")) ..\r
@@ -170,17 +172,27 @@ local pages = {
                style_fs:gsub("one_", "two_"):gsub("style%[[^%]]+%]", ""):gsub("style_type%[[^%]]+%]", "") ..\r
                "container_end[]",\r
 \r
-               "size[12,12]real_coordinates[true]" ..\r
+               "formspec_version[3]size[12,13]" ..\r
                "label[0.1,0.5;Clip]" ..\r
                "container[-2.5,1]" .. clip_fs:gsub("%%c", "false") .. "container_end[]" ..\r
                "label[11,0.5;Noclip]" ..\r
                "container[11.5,1]" .. clip_fs:gsub("%%c", "true") .. "container_end[]",\r
+\r
+               [[\r
+                       formspec_version[3]\r
+                       size[12,12]\r
+                       animated_image[0.5,0.5;1,1;test_animation.png:4,100]\r
+                       animated_image[1.75,0.5;1,1;test_animation.png:100,100]\r
+                       animated_image[0.5,1.75;1,1;test_animation.jpg:4,100]\r
+                       animated_image[3,0.5;5,2;test_animation.png:4,100]\r
+                       animated_image[3,2.75;5,2;test_animation.jpg:4,100]\r
+               ]]\r
 }\r
 \r
 local function show_test_formspec(pname, page_id)\r
        page_id = page_id or 2\r
 \r
-       local fs = pages[page_id] .. "tabheader[0,0;6,0.65;maintabs;Real Coord,Styles,Noclip;" .. page_id .. ";false;false]"\r
+       local fs = pages[page_id] .. "tabheader[0,0;6,0.65;maintabs;Real Coord,Styles,Noclip,MiscEle;" .. page_id .. ";false;false]"\r
 \r
        minetest.show_formspec(pname, "test:formspec", fs)\r
 end\r
diff --git a/games/minimal/mods/test/textures/test_animation.jpg b/games/minimal/mods/test/textures/test_animation.jpg
new file mode 100644 (file)
index 0000000..c4f125d
Binary files /dev/null and b/games/minimal/mods/test/textures/test_animation.jpg differ
diff --git a/games/minimal/mods/test/textures/test_animation.png b/games/minimal/mods/test/textures/test_animation.png
new file mode 100644 (file)
index 0000000..b58715b
Binary files /dev/null and b/games/minimal/mods/test/textures/test_animation.png differ
index a9df7848de85d33a13dade023f04ee9eeff605f1..110a0059581cd4f9cd8b0a909da41351fc5c378d 100644 (file)
@@ -1,4 +1,5 @@
 set(gui_SRCS
+       ${CMAKE_CURRENT_SOURCE_DIR}/guiAnimatedImage.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/guiBackgroundImage.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/guiBox.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/guiButton.cpp
diff --git a/src/gui/guiAnimatedImage.cpp b/src/gui/guiAnimatedImage.cpp
new file mode 100644 (file)
index 0000000..8223040
--- /dev/null
@@ -0,0 +1,83 @@
+#include "guiAnimatedImage.h"
+
+#include "client/guiscalingfilter.h"
+#include "client/tile.h" // ITextureSource
+#include "log.h"
+#include "porting.h"
+#include <string>
+
+GUIAnimatedImage::GUIAnimatedImage(gui::IGUIEnvironment *env, gui::IGUIElement *parent,
+               s32 id, const core::rect<s32> &rectangle, const std::string &name,
+               ISimpleTextureSource *tsrc) :
+               gui::IGUIElement(gui::EGUIET_ELEMENT, env, parent, id, rectangle),
+               m_name(name), m_tsrc(tsrc), m_texture(nullptr), m_global_time(0),
+               m_frame_idx(0), m_frame_count(1), m_frame_duration(1), m_frame_time(0)
+{
+       // Expected format: "texture_name:frame_count,frame_duration"
+       // If this format is not met, the string will be loaded as a normal texture
+
+       std::string::size_type colon_position = name.find(':', 0);
+       std::string::size_type comma_position = name.find(',', 0);
+
+       if (comma_position != std::string::npos &&
+                       colon_position != std::string::npos &&
+                       comma_position < name.size()) {
+               m_texture = m_tsrc->getTexture(name.substr(0, colon_position));
+
+               m_frame_count = std::max(stoi(name.substr(
+                                       colon_position + 1, comma_position - colon_position - 1)), 1);
+
+               m_frame_duration = std::max(stoi(name.substr(comma_position + 1)), 1);
+       } else {
+               // Leave the count/duration and display a static image
+               m_texture = m_tsrc->getTexture(name);
+               errorstream << "animated_image[]: Invalid texture format " << name <<
+                       ". Expected format: texture_name:frame_count,frame_duration" << std::endl;
+       }
+
+       if (m_texture != nullptr) {
+               core::dimension2d<u32> size = m_texture->getOriginalSize();
+               if (size.Height < (u64)m_frame_count) {
+                       m_frame_count = size.Height;
+               }
+       } else {
+               // No need to step an animation if we have nothing to draw
+               m_frame_count = 1;
+       }
+}
+
+void GUIAnimatedImage::draw()
+{
+       // Render the current frame
+       if (m_texture != nullptr) {
+               video::IVideoDriver *driver = Environment->getVideoDriver();
+
+               const video::SColor color(255, 255, 255, 255);
+               const video::SColor colors[] = {color, color, color, color};
+
+               core::dimension2d<u32> size = m_texture->getOriginalSize();
+               size.Height /= m_frame_count;
+
+               draw2DImageFilterScaled( driver, m_texture, AbsoluteRect,
+                               core::rect<s32>(core::position2d<s32>(0, size.Height * m_frame_idx), size),
+                               NoClip ? nullptr : &AbsoluteClippingRect, colors, true);
+       }
+
+       // Step the animation
+       if (m_frame_count > 1) {
+               // Determine the delta time to step
+               u64 new_global_time = porting::getTimeMs();
+               if (m_global_time > 0)
+                       m_frame_time += new_global_time - m_global_time;
+
+               m_global_time = new_global_time;
+
+               // Advance by the number of elapsed frames, looping if necessary
+               m_frame_idx += u32(m_frame_time / m_frame_duration);
+               m_frame_idx %= m_frame_count;
+
+               // If 1 or more frames have elapsed, reset the frame time counter with
+               // the remainder
+               m_frame_time %= m_frame_duration;
+       }
+}
diff --git a/src/gui/guiAnimatedImage.h b/src/gui/guiAnimatedImage.h
new file mode 100644 (file)
index 0000000..8fb2977
--- /dev/null
@@ -0,0 +1,26 @@
+#pragma once
+
+#include "irrlichttypes_extrabloated.h"
+#include "util/string.h"
+
+class ISimpleTextureSource;
+
+class GUIAnimatedImage : public gui::IGUIElement {
+public:
+       GUIAnimatedImage(gui::IGUIEnvironment *env, gui::IGUIElement *parent, s32 id,
+                       const core::rect<s32> &rectangle, const std::string &name,
+                       ISimpleTextureSource *tsrc);
+
+       virtual void draw() override;
+
+private:
+       std::string m_name;
+       ISimpleTextureSource *m_tsrc;
+
+       video::ITexture *m_texture;
+       u64 m_global_time;
+       s32 m_frame_idx;
+       s32 m_frame_count;
+       u64 m_frame_duration;
+       u64 m_frame_time;
+};
index 98f4368f451316a864d8e534fe0ebeb629198180..3d473550ce8fd351c3cd2f2611f3fc1e290ed330 100644 (file)
@@ -55,6 +55,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "util/string.h" // for parseColorString()
 #include "irrlicht_changes/static_text.h"
 #include "client/guiscalingfilter.h"
+#include "guiAnimatedImage.h"
 #include "guiBackgroundImage.h"
 #include "guiBox.h"
 #include "guiButton.h"
@@ -779,6 +780,58 @@ void GUIFormSpecMenu::parseImage(parserData* data, const std::string &element)
        errorstream<< "Invalid image element(" << parts.size() << "): '" << element << "'"  << std::endl;
 }
 
+void GUIFormSpecMenu::parseAnimatedImage(parserData *data, const std::string &element)
+{
+       std::vector<std::string> parts = split(element, ';');
+
+       if (parts.size() != 3 &&
+                       !(parts.size() > 3 && m_formspec_version > FORMSPEC_API_VERSION)) {
+               errorstream << "Invalid animated image element(" << parts.size()
+                               << "): '" << element << "'"  << std::endl;
+               return;
+       }
+
+       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]);
+
+       MY_CHECKPOS("animated_image", 0);
+       MY_CHECKGEOM("animated_image", 1);
+
+       v2s32 pos;
+       v2s32 geom;
+
+       if (data->real_coordinates) {
+               pos = getRealCoordinateBasePos(v_pos);
+               geom = getRealCoordinateGeometry(v_geom);
+       } else {
+               pos = getElementBasePos(&v_pos);
+               geom.X = stof(v_geom[0]) * (float)imgsize.X;
+               geom.Y = stof(v_geom[1]) * (float)imgsize.Y;
+       }
+
+       if (!data->explicit_size)
+               warningstream << "invalid use of animated_image without a size[] element" << std::endl;
+
+       FieldSpec spec(
+                       "",
+                       L"",
+                       L"",
+                       258 + m_fields.size()
+       );
+
+       core::rect<s32> rect = core::rect<s32>(pos, pos + geom);
+
+       gui::IGUIElement *e = new GUIAnimatedImage(Environment, this, spec.fid,
+                       rect, name, m_tsrc);
+
+       auto style = getStyleForElement("animated_image", spec.fname);
+       e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
+       e->drop();
+
+       m_fields.push_back(spec);
+}
+
 void GUIFormSpecMenu::parseItemImage(parserData* data, const std::string &element)
 {
        std::vector<std::string> parts = split(element,';');
@@ -2500,6 +2553,11 @@ void GUIFormSpecMenu::parseElement(parserData* data, const std::string &element)
                return;
        }
 
+       if (type == "animated_image") {
+               parseAnimatedImage(data, description);
+               return;
+       }
+
        if (type == "item_image") {
                parseItemImage(data, description);
                return;
index 67be4268a89a6b6701f54762b121d7e161173b44..7c52336c93c2eacd3b674a4d67cf61ff9257ac24 100644 (file)
@@ -38,6 +38,7 @@ class InventoryManager;
 class ISimpleTextureSource;
 class Client;
 class GUIScrollBar;
+class TexturePool;
 
 typedef enum {
        f_Button,
@@ -388,6 +389,7 @@ private:
        void parseListRing(parserData* data, const std::string &element);
        void parseCheckbox(parserData* data, const std::string &element);
        void parseImage(parserData* data, const std::string &element);
+       void parseAnimatedImage(parserData *data, const std::string &element);
        void parseItemImage(parserData* data, const std::string &element);
        void parseButton(parserData* data, const std::string &element,
                        const std::string &typ);
index a2559194ae4208504e3133d9e1a3135712002410..05b4a96c417813cd5901acfe135513e03734cf94 100644 (file)
@@ -155,6 +155,8 @@ src/genericobject.cpp
 src/genericobject.h
 src/gettext.cpp
 src/gettext.h
+src/gui/guiAnimatedImage.cpp
+src/gui/guiAnimatedImage.h
 src/gui/guiBackgroundImage.cpp
 src/gui/guiBackgroundImage.h
 src/gui/guiBox.cpp