From 7ce21788f86d489d6dc08d9b2d3f6e3f8495b64e Mon Sep 17 00:00:00 2001 From: Hugues Ross Date: Sat, 15 Feb 2020 10:33:18 -0500 Subject: [PATCH] Add animated_image[] formspec element (#9258) --- build/android/jni/Android.mk | 1 + doc/lua_api.txt | 11 +++ games/minimal/mods/test/formspec.lua | 20 ++++- .../mods/test/textures/test_animation.jpg | Bin 0 -> 3696 bytes .../mods/test/textures/test_animation.png | Bin 0 -> 1048 bytes src/gui/CMakeLists.txt | 1 + src/gui/guiAnimatedImage.cpp | 83 ++++++++++++++++++ src/gui/guiAnimatedImage.h | 26 ++++++ src/gui/guiFormSpecMenu.cpp | 58 ++++++++++++ src/gui/guiFormSpecMenu.h | 2 + util/travis/clang-format-whitelist.txt | 2 + 11 files changed, 200 insertions(+), 4 deletions(-) create mode 100644 games/minimal/mods/test/textures/test_animation.jpg create mode 100644 games/minimal/mods/test/textures/test_animation.png create mode 100644 src/gui/guiAnimatedImage.cpp create mode 100644 src/gui/guiAnimatedImage.h diff --git a/build/android/jni/Android.mk b/build/android/jni/Android.mk index 6d21544bf..72b0daab6 100644 --- a/build/android/jni/Android.mk +++ b/build/android/jni/Android.mk @@ -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 \ diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 75a083bdd..fe5b1a626 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -2133,6 +2133,15 @@ Elements * Show an image +### `animated_image[,;,;:,]` + +* 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 +* `` is the image to use +* `` is the number of frames animating the image +* `` is in milliseconds + ### `item_image[,;,;]` * 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 diff --git a/games/minimal/mods/test/formspec.lua b/games/minimal/mods/test/formspec.lua index 67aad3b20..50c506899 100644 --- a/games/minimal/mods/test/formspec.lua +++ b/games/minimal/mods/test/formspec.lua @@ -12,6 +12,7 @@ local clip_fs = [[ style_type[dropdown;noclip=%c] style_type[scrollbar;noclip=%c] style_type[table;noclip=%c] + style_type[animated_image;noclip=%c] label[0,0;A clipping test] button[0,1;3,0.8;x;A clipping test] @@ -25,6 +26,7 @@ local clip_fs = [[ scrollbar[0,9;3,0.8;horizontal;x9;3] tablecolumns[text;text] table[0,10;3,1;x10;one,two,three,four;1] + animated_image[0,11;3,1;test_animation.png:4,100] ]] @@ -119,8 +121,8 @@ local style_fs = [[ local pages = { [[ + formspec_version[3] size[12,12] - real_coordinates[true] image_button[0,0;1,1;logo.png;;1x1] image_button[1,0;2,2;logo.png;;2x2] button[0,2;1,1;;1x1] @@ -157,7 +159,7 @@ local pages = { tabheader[6.5,0;6,0.65;name;Tab 1,Tab 2,Tab 3,Secrets;1;false;false] ]], - "size[12,12]real_coordinates[true]" .. + "formspec_version[3]size[12,12]" .. ("label[0.375,0.375;Styled - %s %s]"):format( color("#F00", "red text"), color("#77FF00CC", "green text")) .. @@ -170,17 +172,27 @@ local pages = { style_fs:gsub("one_", "two_"):gsub("style%[[^%]]+%]", ""):gsub("style_type%[[^%]]+%]", "") .. "container_end[]", - "size[12,12]real_coordinates[true]" .. + "formspec_version[3]size[12,13]" .. "label[0.1,0.5;Clip]" .. "container[-2.5,1]" .. clip_fs:gsub("%%c", "false") .. "container_end[]" .. "label[11,0.5;Noclip]" .. "container[11.5,1]" .. clip_fs:gsub("%%c", "true") .. "container_end[]", + + [[ + formspec_version[3] + size[12,12] + animated_image[0.5,0.5;1,1;test_animation.png:4,100] + animated_image[1.75,0.5;1,1;test_animation.png:100,100] + animated_image[0.5,1.75;1,1;test_animation.jpg:4,100] + animated_image[3,0.5;5,2;test_animation.png:4,100] + animated_image[3,2.75;5,2;test_animation.jpg:4,100] + ]] } local function show_test_formspec(pname, page_id) page_id = page_id or 2 - local fs = pages[page_id] .. "tabheader[0,0;6,0.65;maintabs;Real Coord,Styles,Noclip;" .. page_id .. ";false;false]" + local fs = pages[page_id] .. "tabheader[0,0;6,0.65;maintabs;Real Coord,Styles,Noclip,MiscEle;" .. page_id .. ";false;false]" minetest.show_formspec(pname, "test:formspec", fs) end diff --git a/games/minimal/mods/test/textures/test_animation.jpg b/games/minimal/mods/test/textures/test_animation.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c4f125d5ae3f4dfc728f0c2f9f75dd1010788f2f GIT binary patch literal 3696 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 Ta4qMSurD(|B~@^_0&V>_5@Vjw literal 0 HcmV?d00001 diff --git a/games/minimal/mods/test/textures/test_animation.png b/games/minimal/mods/test/textures/test_animation.png new file mode 100644 index 0000000000000000000000000000000000000000..b58715bf82d95737273e50efa1dce20a364afe56 GIT binary patch literal 1048 zcmZXSUuYav6vjWARby=F1`4e*(kTR4B+y0oVbP_!nlV}E#Ek9I4!ERCol2D-<2QZIrb&@akw=jaZir{64Ag)+&;nL~bzw*%DNU(7 zX%?APvg_n6k&kQ^x(>^NZNqiZY$6QN>F~en6qP8d61fz$hytQjeyg0tppLW!#fIjh z(8TEwr4Fx`RHKwjT7%3MRRi*_lfTBx$qgBZl@Y5U#tr$m4M_u(%8*tdvrN@@$ZL?l zOaoApFtVZ|j*bNbfrSol0|w&hnb}Iu{8oAbk5+xkK^@1*5?*}^=Rd%QSMcdIe7lDA zANOwjbnusr;op88|MQQRH~*^LjOw?@Cm9$7(y>@>XK#K_yeRLp5(Aa=psl1GU2T|! z7Apl#wY}hVL!Z{$vW!dynhHbjEgL&)<{qysH7g09rI%}JI8$7$m(~_)>r3^`l_h?j zc+vkCm(QO4?;`Ip(=>T6arXCrbBXKSQ_AtFBNL~lUMWr*7@jPiI`hz+cjN`WHF9!t z^m#t+NIoX`_|t-80fy9HCs*g_3&h9w&{S9?0-v98;$!Whb zcTck?x_ke-_YOZhvj4R^U%cai#n`R;FAx5(a_Cy;{k<1|k9|`9qWsA6A=+&dtj UeR3ju;UK>cIc0Qc{)rRpANKmdrvLx| literal 0 HcmV?d00001 diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index a9df7848d..110a00595 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -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 index 000000000..822304087 --- /dev/null +++ b/src/gui/guiAnimatedImage.cpp @@ -0,0 +1,83 @@ +#include "guiAnimatedImage.h" + +#include "client/guiscalingfilter.h" +#include "client/tile.h" // ITextureSource +#include "log.h" +#include "porting.h" +#include + +GUIAnimatedImage::GUIAnimatedImage(gui::IGUIEnvironment *env, gui::IGUIElement *parent, + s32 id, const core::rect &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 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 size = m_texture->getOriginalSize(); + size.Height /= m_frame_count; + + draw2DImageFilterScaled( driver, m_texture, AbsoluteRect, + core::rect(core::position2d(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 index 000000000..8fb2977f2 --- /dev/null +++ b/src/gui/guiAnimatedImage.h @@ -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 &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; +}; diff --git a/src/gui/guiFormSpecMenu.cpp b/src/gui/guiFormSpecMenu.cpp index 98f4368f4..3d473550c 100644 --- a/src/gui/guiFormSpecMenu.cpp +++ b/src/gui/guiFormSpecMenu.cpp @@ -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 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 v_pos = split(parts[0], ','); + std::vector 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 rect = core::rect(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 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; diff --git a/src/gui/guiFormSpecMenu.h b/src/gui/guiFormSpecMenu.h index 67be4268a..7c52336c9 100644 --- a/src/gui/guiFormSpecMenu.h +++ b/src/gui/guiFormSpecMenu.h @@ -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); diff --git a/util/travis/clang-format-whitelist.txt b/util/travis/clang-format-whitelist.txt index a2559194a..05b4a96c4 100644 --- a/util/travis/clang-format-whitelist.txt +++ b/util/travis/clang-format-whitelist.txt @@ -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