aboutsummaryrefslogtreecommitdiff
path: root/src/client/tile.cpp
Commit message (Collapse)AuthorAge
* Correction for alpha blending issues in texture mod compositing (#9029)Warr10242019-10-18
|
* Textures: Load base pack only as last fallback (#8974)SmallJoker2019-09-29
|
* Unify GLES support in gui scaling filtersfan52019-08-04
|
* Unify OpenGL ES supportsfan52019-08-04
|
* Optimize string (mis)handling (#8128)Jozef Behran2019-05-18
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | * Optimize statbar drawing The texture name of the statbar is a string passed by value. That slows down the client and creates litter in the heap as the content of the string is allocated there. Convert the offending parameter to a const reference to avoid the performance hit. * Optimize texture cache There is an unnecessary temporary created when the texture path is being generated. This slows down the cache each time a new texture is encountered and it needs to be loaded into the cache. Additionally, the heap litter created by this unnecessary temporary is particularly troublesome here as the following code then piles another string (the resulting full path of the texture) on top of it, followed by the texture itself, which both are quite long term objects as they are subsequently inserted into the cache where they can remain for quite a while (especially if the texture turns out to be a common one like dirt, grass or stone). Use std::string.append to get rid of the temporary which solves both issues (speed and heap fragmentation). * Optimize animations in client Each time an animated node is updated, an unnecessary copy of the texture name is created, littering the heap with lots of fragments. This can be specifically troublesome when looking at oceans or large lava lakes as both of these nodes are usually animated (the lava animation is pretty visible). Convert the parameter of GenericCAO::updateTextures to a const reference to get rid of the unnecessary copy. There is a comment stating "std::string copy is mandatory as mod can be a class member and there is a swap on those class members ... do NOT pass by reference", reinforcing the belief that the unnecessary copy is in fact necessary. However one of the first things the code of the method does is to assign the parameter to its class member, creating another copy. By rearranging the code a little bit this "another copy" can then be used by the subsequent code, getting rid of the need to pass the parameter by value and thus saving that copying effort. * Optimize chat console history handling The GUIChatConsole::replaceAndAddToHistory was getting the line to work on by value which turns out to be unnecessary. Get rid of that unnecessary copy by converting the parameter to a const reference. * Optimize gui texture setting The code used to set the texture for GUI components was getting the name of the texture by value, creating unnecessary performance bottleneck for mods/games with heavily textured GUIs. Get rid of the bottleneck by passing the texture name as a const reference. * Optimize sound playing code in GUIEngine The GUIEngine's code receives the specification of the sound to be played by value, which turns out to be most likely a mistake as the underlying sound manager interface receives the same thing by reference. Convert the offending parameter to a const reference to get rid of the rather bulky copying effort and the associated performance hit. * Silence CLANG TIDY warnings for unit tests Change "std::string" to "const std::string &" to avoid an unnecessary local value copy, silencing the CLANG TIDY process. * Optimize formspec handling The "formspec prepend" parameter was passed to the formspec handling code by value, creating unnecessary copy of std::string and slowing down the game if mods add things like textured backgrounds for the player inventory and/or other forms. Get rid of that performance bottleneck by converting the parameter to a const reference. * Optimize hotbar image handling The code that sets the background images for the hotbar is getting the name of the image by value, creating an unnecessary std::string copying effort. Fix that by converting the relevant parameters to const references. * Optimize inventory deserialization The inventory manager deserialization code gets the serialized version of the inventory by value, slowing the server and the client down when there are inventory updates. This can get particularly troublesome with pipeworks which adds nodes that can mess around with inventories automatically or with mods that have mobs with inventories that actively use them. * Optimize texture scaling cache There is an io::path parameter passed by value in the procedure used to add images converted from textures, leading to slowdown when the image is not yet created and the conversion is thus needed. The performance hit is quite significant as io::path is similar to std::string so convert the parameter to a const reference to get rid of it. * Optimize translation file loader Use "std::string::append" when calculating the final index for the translation table to avoid unnecessary temporary strings. This speeds the translation file loader up significantly as std::string uses heap allocation which tends to be rather slow. Additionally, the heap is no longer being littered by these unnecessary string temporaries, increasing performance of code that gets executed after the translation file loader finishes. * Optimize server map saving When the directory structure for the world data is created during server map saving, an unnecessary value passing of the directory name slows things down. Remove that overhead by converting the offending parameter to a const reference.
* Import strstr function from FreeBSD 11 libcLoic Blot2019-01-10
|
* Android build fixesLoïc Blot2019-01-09
| | | | This fixes #8079
* Fix a crash on Android with Align2Npot2 (#8070)Loïc Blot2019-01-07
| | | | | | | | * Fix a crash on Android with Align2Npot2 glGetString can be NULL. If stored in a string it triggers a SIGSEGV. Instead do a basic strstr and verify the pointer * Better Align2Npot2 check (+ performance)
* Software inventorycube (#7651)Vitaliy2018-09-29
| | | Fixes missing/upside-down images on Android.
* Android build fixes for c++11stujones112018-03-11
|
* Drop texture file list cache (#6660)Vitaliy2018-03-10
|
* Load files from subfolders in texturepacksnumber Zero2017-11-17
| | | | Updated and rebased version of a PR by red-001
* Do not scale texture unless necessary.Lars Hofhansl2017-11-04
| | | | | This avoids scaling textures to 'texture_min_size' unless it is actually required (because either auto-scaling or bi/trilinear filtering is enabled)
* Real global textures (#6105)Vitaliy2017-10-15
| | | | | | | | * Real global textures * Add world-aligned textures * Update minimal to support world-aligned tiles * Update minimal
* Code modernization: subfolders (#6283)Loïc Blot2017-08-19
| | | | | | | | | | | | | * Code modernization: subfolders Modernize various code on subfolders client, network, script, threading, unittests, util * empty function * default constructor/destructor * for range-based loops * use emplace_back instead of push_back * C++ STL header style * Make connection.cpp readable in a pointed place + typo
* Modernize client code (#6250)Loïc Blot2017-08-15
| | | | | | | | * Various code style fixes * Use range based for loops * Use empty instead of empty objects * Use C++11 default keyword for trivial constructors and destructors * Drop some useless casts * Use emplace_back instead of push_back to improve performance of some vectors push
* Isolate irrlicht references and use a singleton (#6041)Loïc Blot2017-06-26
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | * Add Device3D class which will contain IrrlichtDevice interface move getSupportedVideoDrivers to Device3D Add Device3D singleton & use it in various places Rename Device3D to Rendering engine & add helper functions to various device pointers More singleton work RenderingEngine owns draw_load_screen move draw functions to RenderingEngine Reduce IrrlichtDevice exposure and guienvironment RenderingEngine: Expose get_timer_time() to remove device from guiEngine Make irrlichtdevice & scene manager less exposed * Code style fixes * Move porting::getVideoDriverName, getVideoDriverFriendlyName, getDisplayDensity, getDisplaySize to RenderingEngine Fix XORG_USED macro -> RenderingEngine + create_engine_device from RenderingEngine constructor directly * enum paralax => enum parallax
* Remove threads.h and replace its definitions with their C++11 equivalents ↵ShadowNinja2017-06-11
| | | | | | (#5957) This also changes threadProc's signature, since C++11 supports arbitrary thread function signatures.
* Use C++11 mutexes only (remove compat code) (#5922)Loïc Blot2017-06-06
| | | | * Fix event LINT & remove default constructor/destructors * remove compat code & modernize autolock header
* C++11 patchset 2: remove util/cpp11.h and util/cpp11_container.h (#5821)Loïc Blot2017-06-04
|
* Do not create dummy normalmaps (#4180)you2017-05-19
| | | fixes #1811
* Remove an unused variable in Android BuildLoic Blot2017-04-17
|
* Android progressbar fix (#5601)Loïc Blot2017-04-16
| | | | | | | | | | | | | | | | | * Fix progressbar for Android Fixes #5599 Fixed #5403 * draw_load_screen: use texturesource this permits to unify texture loading code * scale progress bar * Add gl version check for GL_OES_texture_npot. This fixed the texture on loading screen * Remove two sanity checks pointed by @celeron55 * sfan5 comments + android ratio fixes
* Hardware coloring for itemstacksDániel Juhász2017-04-08
| | | | | | | | | | Adds the possibility to colorize item stacks based on their metadata. In the item/node definition you can specify palette (an image file) and color (fallback color if the item has no palette or metadata). Then you can add palette_index to the metadata. Dropped itemstacks with different colors do not merge.
* Tile.cpp: Fix MSVC build broken by 072bbbaSmallJoker2017-03-24
|
* Some performance optimizations (#5424)Loïc Blot2017-03-22
| | | | | | | | | | | | | | | | | | | * Some performance optimizations This is globally removing some memory useless copy * use a const ref return on std::string Settings::get to prevent data copy on getters which doesn't need to copy it * pass some stack created strings to static const as they are not modified anywhere * Camera: return nametags per const ref instead of a list pointer, we only need to read it * INodeDefManager: getAll should be a result ref writer instead of a return copy * INodeDefManager: getAlias should return a const std::string ref * Minimap: unroll a Scolor creation in blitMinimapPixersToImageRadar to prvent many variable construct/destruct which are unneeded (we rewrite the content in the loop) * CNodeDefManager::updateAliases: prevent a idef getall copy * Profiler: constness * rollback_interface: create real_name later, and use const ref * MapBlockMesh updateFastFaceRow: unroll TileSpec next_tile, which has a cost of 1.8% CPU due to variable allocation/destruction, * MapBlockMesh updateFastFaceRow: copy next_tile to tile only if it's a different tilespec * MapBlockMesh updateFastFaceRow: use memcpy to copy next_lights to lights to do it in a single cpu operation
* Prevent SIGFPE on entity tile loading issue. (#5178)Auke Kok2017-02-05
| | | | | | | | | | | | | | | | | | | | | | | | | | While experimenting with entities I ran into this unresolvable error where the server is sending some texture that the client crashes on. The crash prevents the client from ever reconnecting, resulting in a server that has to use clearobjects. We shouldn't crash but just ignore the object and move on. ``` 0x00000000004dc0de in TextureSource::generateImagePart (this=this@entry=0x7118eb0, part_of_name="[applyfiltersformesh", baseimg=@0x7fffffffbe98: 0x9f1b010) at /home/sofar/git/minetest/src/client/tile.cpp:1744 1744 u32 xscale = scaleto / dim.Width; (gdb) bt #0 0x00000000004dc0de in TextureSource::generateImagePart (this=this@entry=0x7118eb0, part_of_name="[applyfiltersformesh", baseimg=@0x7fffffffbe98: 0x9f1b010) at /home/sofar/git/minetest/src/client/tile.cpp:1744 ``` After reconnecting, the client now can connect without issues and displays an error message: ``` ERROR[Main]: generateImagePart(): Illegal 0 dimension for part_of_name="[applyfiltersformesh", cancelling. ERROR[Main]: generateImage(): Failed to generate "[applyfiltersformesh" ERROR[Main]: Irrlicht: Invalid size of image for OpenGL Texture. ```
* Add multiply texture modifiersapier2017-01-30
| | | | Allows colorizing of textures using a color multiplication method.
* Add hardware node coloring. Includes:Dániel Juhász2017-01-23
| | | | | | - Increase ContentFeatures serialization version - Color property and palettes for nodes - paramtype2 = "color", "colored facedir" or "colored wallmounted"
* Added "[sheet" to the texture special commands.Luke Puchner-Hardman2017-01-02
| | | | | | | | "[sheet:WxH:X,Y" assumes the base image is a tilesheet with W*H tiles on it and crops to the tile at position X,Y. Basically it works like "[verticalframe" but in 2D. For testing, I combined the four default_chest images into one.
* Irrlicht 1.9 supportsfan52016-12-26
|
* Add an [invert:<mode> texture modifierThomas--S2016-09-15
| | | | | | Inverts the given channels of the base image. Mode may contain the characters "r", "g", "b", "a". Only the channels that are mentioned in the mode string will be inverted.
* Allow escaping of texture names when passed as an argument to a modifiersfan52016-09-14
|
* Add an [opacity:<r> texture modifier. Makes the base image transparent ↵Thomas--S2016-08-12
| | | | according to the given ratio. r must be between 0 and 255. 0 means totally transparent. 255 means totally opaque. Useful for texture overlaying.
* Add [resize texture modifier Resizes the texture to the given dimensions.SmallJoker2016-05-09
|
* tile.cpp: Automatically upscale lower resolution textureSmallJoker2016-04-25
|
* Re-add and disable blit_with_interpolate_overlaykwolekr2016-04-07
|
* Fix compiler warnings from "Add an option to colorize to respect the ↵Samuel Sieb2016-04-06
| | | | | | | destination alpha" Fix warnings added by commit 01ae43c48009f816f4649fae2f7f6997452aa6cf Fixes #3952
* Add an option to colorize to respect the destination alphaSamuel Sieb2016-04-03
| | | | Also, rework the colorizing code to be more efficient.
* Clean up StrfndShadowNinja2016-03-19
| | | | | | | | | | | Changes: * Fix indentation. * Pass strings by const reference. * Merge Strfnd and WStrfnd into one class instead of copying them. * Remove trailing spaces. * Fix variable names. * Move to util. * Other miscellaneous style fixes.
* Refactor thread utility interfacekwolekr2015-10-16
| | | | | - Add "thr_" prefix to thread utility functions - Compare threadid_ts in a portable manner, where possible
* Change i++ to ++iDavid Jones2015-08-25
|
* Clean up threadingShadowNinja2015-08-23
| | | | | | | | | | | | | | | | | | | | * Rename everything. * Strip J prefix. * Change UpperCamelCase functions to lowerCamelCase. * Remove global (!) semaphore count mutex on OSX. * Remove semaphore count getter (unused, unsafe, depended on internal API functions on Windows, and used a hack on OSX). * Add `Atomic<type>`. * Make `Thread` handle thread names. * Add support for C++11 multi-threading. * Combine pthread and win32 sources. * Remove `ThreadStarted` (unused, unneeded). * Move some includes from the headers to the sources. * Move all of `Event` into its header (allows inlining with no new includes). * Make `Event` use `Semaphore` (except on Windows). * Move some porting functions into `Thread`. * Integrate logging with `Thread`. * Add threading test.
* Remove use of engine sent texture tiling flags - theyre no longer neededRealBadAngel2015-08-20
|
* src/client/tile.cpp: Fix reference countingBřetislav Štec2015-08-02
|
* Add wielded (and CAOs) shaderRealBadAngel2015-07-21
|
* Fix relief mapping issuesRealBadAngel2015-07-16
|
* Add minimap featureRealBadAngel2015-06-27
|
* Fix fast leaves with texture_clean_transparent enabled.Aaron Suen2015-04-26
|
* Move globals from main.cpp to more sane locationsCraig Robbins2015-04-01
| | | | | | | | | | | | Move debug streams to log.cpp|h Move GUI-related globals to clientlauncher Move g_settings and g_settings_path to settings.cpp|h Move g_menuclouds to clouds.cpp|h Move g_profiler to profiler.cpp|h
">(spec); if (!this->font) printf("No font found ! Size=%d, mode=%d, bold=%s, italic=%s\n", font_size, font_mode, style["bold"].c_str(), style["italic"].c_str()); } void ParsedText::Paragraph::setStyle(StyleList &style) { if (style["halign"] == "center") this->halign = HALIGN_CENTER; else if (style["halign"] == "right") this->halign = HALIGN_RIGHT; else if (style["halign"] == "justify") this->halign = HALIGN_JUSTIFY; else this->halign = HALIGN_LEFT; } ParsedText::ParsedText(const wchar_t *text) { // Default style m_root_tag.name = "root"; m_root_tag.style["fontsize"] = "16"; m_root_tag.style["fontstyle"] = "normal"; m_root_tag.style["bold"] = "false"; m_root_tag.style["italic"] = "false"; m_root_tag.style["underline"] = "false"; m_root_tag.style["halign"] = "left"; m_root_tag.style["color"] = "#EEEEEE"; m_root_tag.style["hovercolor"] = "#FF0000"; m_active_tags.push_front(&m_root_tag); m_style = m_root_tag.style; // Default simple tags definitions StyleList style; style["color"] = "#0000FF"; style["underline"] = "true"; m_elementtags["action"] = style; style.clear(); style["bold"] = "true"; m_elementtags["b"] = style; style.clear(); style["italic"] = "true"; m_elementtags["i"] = style; style.clear(); style["underline"] = "true"; m_elementtags["u"] = style; style.clear(); style["fontstyle"] = "mono"; m_elementtags["mono"] = style; style.clear(); style["fontsize"] = m_root_tag.style["fontsize"]; m_elementtags["normal"] = style; style.clear(); style["fontsize"] = "24"; m_elementtags["big"] = style; style.clear(); style["fontsize"] = "36"; m_elementtags["bigger"] = style; style.clear(); style["halign"] = "center"; m_paragraphtags["center"] = style; style.clear(); style["halign"] = "justify"; m_paragraphtags["justify"] = style; style.clear(); style["halign"] = "left"; m_paragraphtags["left"] = style; style.clear(); style["halign"] = "right"; m_paragraphtags["right"] = style; style.clear(); m_element = NULL; m_paragraph = NULL; m_end_paragraph_reason = ER_NONE; parse(text); } ParsedText::~ParsedText() { for (auto &tag : m_not_root_tags) delete tag; } void ParsedText::parse(const wchar_t *text) { wchar_t c; u32 cursor = 0; bool escape = false; while ((c = text[cursor]) != L'\0') { cursor++; if (c == L'\r') { // Mac or Windows breaks if (text[cursor] == L'\n') cursor++; // If text has begun, don't skip empty line if (m_paragraph) { endParagraph(ER_NEWLINE); enterElement(ELEMENT_SEPARATOR); } escape = false; continue; } if (c == L'\n') { // Unix breaks // If text has begun, don't skip empty line if (m_paragraph) { endParagraph(ER_NEWLINE); enterElement(ELEMENT_SEPARATOR); } escape = false; continue; } if (escape) { escape = false; pushChar(c); continue; } if (c == L'\\') { escape = true; continue; } // Tag check if (c == L'<') { u32 newcursor = parseTag(text, cursor); if (newcursor > 0) { cursor = newcursor; continue; } } // Default behavior pushChar(c); } endParagraph(ER_NONE); } void ParsedText::endElement() { m_element = NULL; } void ParsedText::endParagraph(EndReason reason) { if (!m_paragraph) return; EndReason previous = m_end_paragraph_reason; m_end_paragraph_reason = reason; if (m_empty_paragraph && (reason == ER_TAG || (reason == ER_NEWLINE && previous == ER_TAG))) { // Ignore last empty paragraph m_paragraph = nullptr; m_paragraphs.pop_back(); return; } endElement(); m_paragraph = NULL; } void ParsedText::enterParagraph() { if (!m_paragraph) { m_paragraphs.emplace_back(); m_paragraph = &m_paragraphs.back(); m_paragraph->setStyle(m_style); m_empty_paragraph = true; } } void ParsedText::enterElement(ElementType type) { enterParagraph(); if (!m_element || m_element->type != type) { m_paragraph->elements.emplace_back(); m_element = &m_paragraph->elements.back(); m_element->type = type; m_element->tags = m_active_tags; m_element->setStyle(m_style); } } void ParsedText::pushChar(wchar_t c) { // New word if needed if (c == L' ' || c == L'\t') { if (!m_empty_paragraph) enterElement(ELEMENT_SEPARATOR); else return; } else { m_empty_paragraph = false; enterElement(ELEMENT_TEXT); } m_element->text += c; } ParsedText::Tag *ParsedText::newTag(const std::string &name, const AttrsList &attrs) { endElement(); Tag *newtag = new Tag(); newtag->name = name; newtag->attrs = attrs; m_not_root_tags.push_back(newtag); return newtag; } ParsedText::Tag *ParsedText::openTag(const std::string &name, const AttrsList &attrs) { Tag *newtag = newTag(name, attrs); m_active_tags.push_front(newtag); return newtag; } bool ParsedText::closeTag(const std::string &name) { bool found = false; for (auto id = m_active_tags.begin(); id != m_active_tags.end(); ++id) if ((*id)->name == name) { m_active_tags.erase(id); found = true; break; } return found; } void ParsedText::parseGenericStyleAttr( const std::string &name, const std::string &value, StyleList &style) { // Color styles if (name == "color" || name == "hovercolor") { if (check_color(value)) style[name] = value; // Boolean styles } else if (name == "bold" || name == "italic" || name == "underline") { style[name] = is_yes(value); } else if (name == "size") { if (check_integer(value)) style["fontsize"] = value; } else if (name == "font") { if (value == "mono" || value == "normal") style["fontstyle"] = value; } } void ParsedText::parseStyles(const AttrsList &attrs, StyleList &style) { for (auto const &attr : attrs) parseGenericStyleAttr(attr.first, attr.second, style); } void ParsedText::globalTag(const AttrsList &attrs) { for (const auto &attr : attrs) { // Only page level style if (attr.first == "margin") { if (check_integer(attr.second)) margin = stoi(attr.second.c_str()); } else if (attr.first == "valign") { if (attr.second == "top") valign = ParsedText::VALIGN_TOP; else if (attr.second == "bottom") valign = ParsedText::VALIGN_BOTTOM; else if (attr.second == "middle") valign = ParsedText::VALIGN_MIDDLE; } else if (attr.first == "background") { irr::video::SColor color; if (attr.second == "none") { background_type = BACKGROUND_NONE; } else if (parseColorString(attr.second, color, false)) { background_type = BACKGROUND_COLOR; background_color = color; } // Inheriting styles } else if (attr.first == "halign") { if (attr.second == "left" || attr.second == "center" || attr.second == "right" || attr.second == "justify") m_root_tag.style["halign"] = attr.second; // Generic default styles } else { parseGenericStyleAttr(attr.first, attr.second, m_root_tag.style); } } } u32 ParsedText::parseTag(const wchar_t *text, u32 cursor) { // Tag name bool end = false; std::string name = ""; wchar_t c = text[cursor]; if (c == L'/') { end = true; c = text[++cursor]; if (c == L'\0') return 0; } while (c != ' ' && c != '>') { name += c; c = text[++cursor]; if (c == L'\0') return 0; } // Tag attributes AttrsList attrs; while (c != L'>') { std::string attr_name = ""; core::stringw attr_val = L""; while (c == ' ') { c = text[++cursor]; if (c == L'\0' || c == L'=') return 0; } while (c != L' ' && c != L'=') { attr_name += (char)c; c = text[++cursor]; if (c == L'\0' || c == L'>') return 0; } while (c == L' ') { c = text[++cursor]; if (c == L'\0' || c == L'>') return 0; } if (c != L'=') return 0; c = text[++cursor]; if (c == L'\0') return 0; while (c != L'>' && c != L' ') { attr_val += c; c = text[++cursor]; if (c == L'\0') return 0; } attrs[attr_name] = stringw_to_utf8(attr_val); } ++cursor; // Last ">" // Tag specific processing StyleList style; if (name == "global") { if (end) return 0; globalTag(attrs); } else if (name == "style") { if (end) { closeTag(name); } else { parseStyles(attrs, style); openTag(name, attrs)->style = style; } endElement(); } else if (name == "img" || name == "item") { if (end) return 0; // Name is a required attribute if (!attrs.count("name")) return 0; // Rotate attribute is only for <item> if (attrs.count("rotate") && name != "item") return 0; // Angle attribute is only for <item> if (attrs.count("angle") && name != "item") return 0; // Ok, element can be created newTag(name, attrs); if (name == "img") enterElement(ELEMENT_IMAGE); else enterElement(ELEMENT_ITEM); m_element->text = utf8_to_stringw(attrs["name"]); if (attrs.count("float")) { if (attrs["float"] == "left") m_element->floating = FLOAT_LEFT; if (attrs["float"] == "right") m_element->floating = FLOAT_RIGHT; } if (attrs.count("width")) { int width = stoi(attrs["width"]); if (width > 0) m_element->dim.Width = width; } if (attrs.count("height")) { int height = stoi(attrs["height"]); if (height > 0) m_element->dim.Height = height; } if (attrs.count("angle")) { std::string str = attrs["angle"]; std::vector<std::string> parts = split(str, ','); if (parts.size() == 3) { m_element->angle = v3s16( rangelim(stoi(parts[0]), -180, 180), rangelim(stoi(parts[1]), -180, 180), rangelim(stoi(parts[2]), -180, 180)); m_element->rotation = v3s16(0, 0, 0); } } if (attrs.count("rotate")) { if (attrs["rotate"] == "yes") { m_element->rotation = v3s16(0, 100, 0); } else { std::string str = attrs["rotate"]; std::vector<std::string> parts = split(str, ','); if (parts.size() == 3) { m_element->rotation = v3s16 ( rangelim(stoi(parts[0]), -1000, 1000), rangelim(stoi(parts[1]), -1000, 1000), rangelim(stoi(parts[2]), -1000, 1000)); } } } endElement(); } else if (name == "tag") { // Required attributes if (!attrs.count("name")) return 0; StyleList tagstyle; parseStyles(attrs, tagstyle); if (is_yes(attrs["paragraph"])) m_paragraphtags[attrs["name"]] = tagstyle; else m_elementtags[attrs["name"]] = tagstyle; } else if (name == "action") { if (end) { closeTag(name); } else { if (!attrs.count("name")) return 0; openTag(name, attrs)->style = m_elementtags["action"]; } } else if (m_elementtags.count(name)) { if (end) { closeTag(name); } else { openTag(name, attrs)->style = m_elementtags[name]; } endElement(); } else if (m_paragraphtags.count(name)) { if (end) { closeTag(name); } else { openTag(name, attrs)->style = m_paragraphtags[name]; } endParagraph(ER_TAG); } else return 0; // Unknown tag // Update styles accordingly m_style.clear(); for (auto tag = m_active_tags.crbegin(); tag != m_active_tags.crend(); ++tag) for (const auto &prop : (*tag)->style) m_style[prop.first] = prop.second; return cursor; } // ----------------------------------------------------------------------------- // Text Drawer TextDrawer::TextDrawer(const wchar_t *text, Client *client, gui::IGUIEnvironment *environment, ISimpleTextureSource *tsrc) : m_text(text), m_client(client), m_environment(environment) { // Size all elements for (auto &p : m_text.m_paragraphs) { for (auto &e : p.elements) { switch (e.type) { case ParsedText::ELEMENT_SEPARATOR: case ParsedText::ELEMENT_TEXT: if (e.font) { e.dim.Width = e.font->getDimension(e.text.c_str()).Width; e.dim.Height = e.font->getDimension(L"Yy").Height; #if USE_FREETYPE if (e.font->getType() == irr::gui::EGFT_CUSTOM) { e.baseline = e.dim.Height - 1 - ((irr::gui::CGUITTFont *)e.font)->getAscender() / 64; } #endif } else { e.dim = {0, 0}; } break; case ParsedText::ELEMENT_IMAGE: case ParsedText::ELEMENT_ITEM: // Resize only non sized items if (e.dim.Height != 0 && e.dim.Width != 0) break; // Default image and item size core::dimension2d<u32> dim(80, 80); if (e.type == ParsedText::ELEMENT_IMAGE) { video::ITexture *texture = m_client->getTextureSource()-> getTexture(stringw_to_utf8(e.text)); if (texture) dim = texture->getOriginalSize(); } if (e.dim.Height == 0) if (e.dim.Width == 0) e.dim = dim; else e.dim.Height = dim.Height * e.dim.Width / dim.Width; else e.dim.Width = dim.Width * e.dim.Height / dim.Height; break; } } } } // Get element at given coordinates. Coordinates are inner coordinates (starting // at 0,0). ParsedText::Element *TextDrawer::getElementAt(core::position2d<s32> pos) { pos.Y -= m_voffset; for (auto &p : m_text.m_paragraphs) { for (auto &el : p.elements) { core::rect<s32> rect(el.pos, el.dim); if (rect.isPointInside(pos)) return &el; } } return 0; } /* This function places all elements according to given width. Elements have been previously sized by constructor and will be later drawed by draw. It may be called each time width changes and resulting height can be retrieved using getHeight. See GUIHyperText constructor, it uses it once to test if text fits in window and eventually another time if width is reduced m_floating because of scrollbar added. */ void TextDrawer::place(const core::rect<s32> &dest_rect) { m_floating.clear(); s32 y = 0; s32 ymargin = m_text.margin; // Iterator used : // p - Current paragraph, walked only once // el - Current element, walked only once // e and f - local element and floating operators for (auto &p : m_text.m_paragraphs) { // Find and place floating stuff in paragraph for (auto e = p.elements.begin(); e != p.elements.end(); ++e) { if (e->floating != ParsedText::FLOAT_NONE) { if (y) e->pos.Y = y + std::max(ymargin, e->margin); else e->pos.Y = ymargin; if (e->floating == ParsedText::FLOAT_LEFT) e->pos.X = m_text.margin; if (e->floating == ParsedText::FLOAT_RIGHT) e->pos.X = dest_rect.getWidth() - e->dim.Width - m_text.margin; RectWithMargin floating; floating.rect = core::rect<s32>(e->pos, e->dim); floating.margin = e->margin; m_floating.push_back(floating); } } if (y) y = y + std::max(ymargin, p.margin); ymargin = p.margin; // Place non floating stuff std::vector<ParsedText::Element>::iterator el = p.elements.begin(); while (el != p.elements.end()) { // Determine line width and y pos s32 left, right; s32 nexty = y; do { y = nexty; nexty = 0; // Inner left & right left = m_text.margin; right = dest_rect.getWidth() - m_text.margin; for (const auto &f : m_floating) { // Does floating rect intersect paragraph y line? if (f.rect.UpperLeftCorner.Y - f.margin <= y && f.rect.LowerRightCorner.Y + f.margin >= y) { // Next Y to try if no room left if (!nexty || f.rect.LowerRightCorner.Y + std::max(f.margin, p.margin) < nexty) { nexty = f.rect.LowerRightCorner.Y + std::max(f.margin, p.margin) + 1; } if (f.rect.UpperLeftCorner.X - f.margin <= left && f.rect.LowerRightCorner.X + f.margin < right) { // float on left if (f.rect.LowerRightCorner.X + std::max(f.margin, p.margin) > left) { left = f.rect.LowerRightCorner.X + std::max(f.margin, p.margin); } } else if (f.rect.LowerRightCorner.X + f.margin >= right && f.rect.UpperLeftCorner.X - f.margin > left) { // float on right if (f.rect.UpperLeftCorner.X - std::max(f.margin, p.margin) < right) right = f.rect.UpperLeftCorner.X - std::max(f.margin, p.margin); } else if (f.rect.UpperLeftCorner.X - f.margin <= left && f.rect.LowerRightCorner.X + f.margin >= right) { // float taking all space left = right; } else { // float in the middle -- should not occure yet, see that later } } } } while (nexty && right <= left); u32 linewidth = right - left; float x = left; u32 charsheight = 0; u32 charswidth = 0; u32 wordcount = 0; // Skip begining of line separators but include them in height // computation. while (el != p.elements.end() && el->type == ParsedText::ELEMENT_SEPARATOR) { if (el->floating == ParsedText::FLOAT_NONE) { el->drawwidth = 0; if (charsheight < el->dim.Height) charsheight = el->dim.Height; } el++; } std::vector<ParsedText::Element>::iterator linestart = el; std::vector<ParsedText::Element>::iterator lineend = p.elements.end(); // First pass, find elements fitting into line // (or at least one element) while (el != p.elements.end() && (charswidth == 0 || charswidth + el->dim.Width <= linewidth)) { if (el->floating == ParsedText::FLOAT_NONE) { if (el->type != ParsedText::ELEMENT_SEPARATOR) { lineend = el; wordcount++; } charswidth += el->dim.Width; if (charsheight < el->dim.Height) charsheight = el->dim.Height; } el++; } // Empty line, nothing to place only go down line height if (lineend == p.elements.end()) { y += charsheight; continue; } // Point to the first position outside line (may be end()) lineend++; // Second pass, compute printable line width and adjustments charswidth = 0; s32 top = 0; s32 bottom = 0; for (auto e = linestart; e != lineend; ++e) { if (e->floating == ParsedText::FLOAT_NONE) { charswidth += e->dim.Width; if (top < (s32)e->dim.Height - e->baseline) top = e->dim.Height - e->baseline; if (bottom < e->baseline) bottom = e->baseline; } } float extraspace = 0.f; switch (p.halign) { case ParsedText::HALIGN_CENTER: x += (linewidth - charswidth) / 2.f; break; case ParsedText::HALIGN_JUSTIFY: if (wordcount > 1 && // Justification only if at least two words !(lineend == p.elements.end())) // Don't justify last line extraspace = ((float)(linewidth - charswidth)) / (wordcount - 1); break; case ParsedText::HALIGN_RIGHT: x += linewidth - charswidth; break; case ParsedText::HALIGN_LEFT: break; } // Third pass, actually place everything for (auto e = linestart; e != lineend; ++e) { if (e->floating != ParsedText::FLOAT_NONE) continue; e->pos.X = x; e->pos.Y = y; switch (e->type) { case ParsedText::ELEMENT_TEXT: case ParsedText::ELEMENT_SEPARATOR: e->pos.X = x; // Align char baselines e->pos.Y = y + top + e->baseline - e->dim.Height; x += e->dim.Width; if (e->type == ParsedText::ELEMENT_SEPARATOR) x += extraspace; break; case ParsedText::ELEMENT_IMAGE: case ParsedText::ELEMENT_ITEM: x += e->dim.Width; break; } // Draw width for separator can be different than element // width. This will be important for char effects like // underline. e->drawwidth = x - e->pos.X; } y += charsheight; } // Elements (actually lines) } // Paragraph // Check if float goes under paragraph for (const auto &f : m_floating) { if (f.rect.LowerRightCorner.Y >= y) y = f.rect.LowerRightCorner.Y; } m_height = y + m_text.margin; // Compute vertical offset according to vertical alignment if (m_height < dest_rect.getHeight()) switch (m_text.valign) { case ParsedText::VALIGN_BOTTOM: m_voffset = dest_rect.getHeight() - m_height; break; case ParsedText::VALIGN_MIDDLE: m_voffset = (dest_rect.getHeight() - m_height) / 2; break; case ParsedText::VALIGN_TOP: default: m_voffset = 0; } else m_voffset = 0; } // Draw text in a rectangle with a given offset. Items are actually placed in // relative (to upper left corner) coordinates. void TextDrawer::draw(const core::rect<s32> &clip_rect, const core::position2d<s32> &dest_offset) { irr::video::IVideoDriver *driver = m_environment->getVideoDriver(); core::position2d<s32> offset = dest_offset; offset.Y += m_voffset; if (m_text.background_type == ParsedText::BACKGROUND_COLOR) driver->draw2DRectangle(m_text.background_color, clip_rect); for (auto &p : m_text.m_paragraphs) { for (auto &el : p.elements) { core::rect<s32> rect(el.pos + offset, el.dim); if (!rect.isRectCollided(clip_rect)) continue; switch (el.type) { case ParsedText::ELEMENT_SEPARATOR: case ParsedText::ELEMENT_TEXT: { irr::video::SColor color = el.color; for (auto tag : el.tags) if (&(*tag) == m_hovertag) color = el.hovercolor; if (!el.font) break; if (el.type == ParsedText::ELEMENT_TEXT) el.font->draw(el.text, rect, color, false, true, &clip_rect); if (el.underline && el.drawwidth) { s32 linepos = el.pos.Y + offset.Y + el.dim.Height - (el.baseline >> 1); core::rect<s32> linerect(el.pos.X + offset.X, linepos - (el.baseline >> 3) - 1, el.pos.X + offset.X + el.drawwidth, linepos + (el.baseline >> 3)); driver->draw2DRectangle(color, linerect, &clip_rect); } } break; case ParsedText::ELEMENT_IMAGE: { video::ITexture *texture = m_client->getTextureSource()->getTexture( stringw_to_utf8(el.text)); if (texture != 0) m_environment->getVideoDriver()->draw2DImage( texture, rect, irr::core::rect<s32>( core::position2d<s32>(0, 0), texture->getOriginalSize()), &clip_rect, 0, true); } break; case ParsedText::ELEMENT_ITEM: { IItemDefManager *idef = m_client->idef(); ItemStack item; item.deSerialize(stringw_to_utf8(el.text), idef); drawItemStack( m_environment->getVideoDriver(), g_fontengine->getFont(), item, rect, &clip_rect, m_client, IT_ROT_OTHER, el.angle, el.rotation ); } break; } } } } // ----------------------------------------------------------------------------- // GUIHyperText - The formated text area formspec item //! constructor GUIHyperText::GUIHyperText(const wchar_t *text, IGUIEnvironment *environment, IGUIElement *parent, s32 id, const core::rect<s32> &rectangle, Client *client, ISimpleTextureSource *tsrc) : IGUIElement(EGUIET_ELEMENT, environment, parent, id, rectangle), m_client(client), m_vscrollbar(nullptr), m_drawer(text, client, environment, tsrc), m_text_scrollpos(0, 0) { #ifdef _DEBUG setDebugName("GUIHyperText"); #endif IGUISkin *skin = 0; if (Environment) skin = Environment->getSkin(); m_scrollbar_width = skin ? skin->getSize(gui::EGDS_SCROLLBAR_SIZE) : 16; core::rect<s32> rect = irr::core::rect<s32>( RelativeRect.getWidth() - m_scrollbar_width, 0, RelativeRect.getWidth(), RelativeRect.getHeight()); m_vscrollbar = new GUIScrollBar(Environment, this, -1, rect, false, true); m_vscrollbar->setVisible(false); } //! destructor GUIHyperText::~GUIHyperText() { m_vscrollbar->remove(); m_vscrollbar->drop(); } ParsedText::Element *GUIHyperText::getElementAt(s32 X, s32 Y) { core::position2d<s32> pos{X, Y}; pos -= m_display_text_rect.UpperLeftCorner; pos -= m_text_scrollpos; return m_drawer.getElementAt(pos); } void GUIHyperText::checkHover(s32 X, s32 Y) { m_drawer.m_hovertag = nullptr; if (AbsoluteRect.isPointInside(core::position2d<s32>(X, Y))) { ParsedText::Element *element = getElementAt(X, Y); if (element) { for (auto &tag : element->tags) { if (tag->name == "action") { m_drawer.m_hovertag = tag; break; } } } } #ifndef HAVE_TOUCHSCREENGUI if (m_drawer.m_hovertag) RenderingEngine::get_raw_device()->getCursorControl()->setActiveIcon( gui::ECI_HAND); else RenderingEngine::get_raw_device()->getCursorControl()->setActiveIcon( gui::ECI_NORMAL); #endif } bool GUIHyperText::OnEvent(const SEvent &event) { // Scroll bar if (event.EventType == EET_GUI_EVENT && event.GUIEvent.EventType == EGET_SCROLL_BAR_CHANGED && event.GUIEvent.Caller == m_vscrollbar) { m_text_scrollpos.Y = -m_vscrollbar->getPos(); } // Reset hover if element left if (event.EventType == EET_GUI_EVENT && event.GUIEvent.EventType == EGET_ELEMENT_LEFT) { m_drawer.m_hovertag = nullptr; #ifndef HAVE_TOUCHSCREENGUI gui::ICursorControl *cursor_control = RenderingEngine::get_raw_device()->getCursorControl(); if (cursor_control->isVisible()) cursor_control->setActiveIcon(gui::ECI_NORMAL); #endif } if (event.EventType == EET_MOUSE_INPUT_EVENT) { if (event.MouseInput.Event == EMIE_MOUSE_MOVED) checkHover(event.MouseInput.X, event.MouseInput.Y); if (event.MouseInput.Event == EMIE_MOUSE_WHEEL) { m_vscrollbar->setPos(m_vscrollbar->getPos() - event.MouseInput.Wheel * m_vscrollbar->getSmallStep()); m_text_scrollpos.Y = -m_vscrollbar->getPos(); m_drawer.draw(m_display_text_rect, m_text_scrollpos); checkHover(event.MouseInput.X, event.MouseInput.Y); return true; } else if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) { ParsedText::Element *element = getElementAt( event.MouseInput.X, event.MouseInput.Y); if (element) { for (auto &tag : element->tags) { if (tag->name == "action") { Text = core::stringw(L"action:") + utf8_to_stringw(tag->attrs["name"]); if (Parent) { SEvent newEvent; newEvent.EventType = EET_GUI_EVENT; newEvent.GUIEvent.Caller = this; newEvent.GUIEvent.Element = 0; newEvent.GUIEvent.EventType = EGET_BUTTON_CLICKED; Parent->OnEvent(newEvent); } break; } } } } } return IGUIElement::OnEvent(event); } //! draws the element and its children void GUIHyperText::draw() { if (!IsVisible) return; // Text m_display_text_rect = AbsoluteRect; m_drawer.place(m_display_text_rect); // Show scrollbar if text overflow if (m_drawer.getHeight() > m_display_text_rect.getHeight()) { m_vscrollbar->setSmallStep(m_display_text_rect.getHeight() * 0.1f); m_vscrollbar->setLargeStep(m_display_text_rect.getHeight() * 0.5f); m_vscrollbar->setMax(m_drawer.getHeight() - m_display_text_rect.getHeight()); m_vscrollbar->setVisible(true); m_vscrollbar->setPageSize(s32(m_drawer.getHeight())); core::rect<s32> smaller_rect = m_display_text_rect; smaller_rect.LowerRightCorner.X -= m_scrollbar_width; m_drawer.place(smaller_rect); } else { m_vscrollbar->setMax(0); m_vscrollbar->setPos(0); m_vscrollbar->setVisible(false); } m_drawer.draw(AbsoluteClippingRect, m_display_text_rect.UpperLeftCorner + m_text_scrollpos); // draw children IGUIElement::draw(); }