From 1d40385d4aacf0cbea4b19ff06940e8c9bebaf47 Mon Sep 17 00:00:00 2001 From: TriBlade9 Date: Fri, 16 Jan 2015 14:54:26 +0800 Subject: Colored chat working as expected for both freetype and non-freetype builds. @nerzhul improvements * Add unit tests * Fix coding style * move guiChatConsole.hpp to client/ --- src/chat.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'src/chat.h') diff --git a/src/chat.h b/src/chat.h index db4146d35..661cafc82 100644 --- a/src/chat.h +++ b/src/chat.h @@ -20,11 +20,13 @@ with this program; if not, write to the Free Software Foundation, Inc., #ifndef CHAT_HEADER #define CHAT_HEADER -#include "irrlichttypes.h" #include #include #include +#include "irrlichttypes.h" +#include "util/coloredstring.h" + // Chat console related classes struct ChatLine @@ -34,7 +36,7 @@ struct ChatLine // name of sending player, or empty if sent by server std::wstring name; // message text - std::wstring text; + ColoredString text; ChatLine(std::wstring a_name, std::wstring a_text): age(0.0), -- cgit v1.2.3 From 14ef2b445adcec770defe1abf83af9d22ccf39d8 Mon Sep 17 00:00:00 2001 From: Ekdohibs Date: Tue, 31 May 2016 17:30:11 +0200 Subject: Add colored text (not only colored chat). Add documentation, move files to a proper place and avoid memory leaks. Make it work with most kind of texts, and allow backgrounds too. --- builtin/game/chatcommands.lua | 2 +- builtin/game/misc.lua | 43 ++- builtin/settingtypes.txt | 5 + doc/lua_api.txt | 18 + src/CMakeLists.txt | 2 + src/cguittfont/CGUITTFont.cpp | 30 +- src/cguittfont/CGUITTFont.h | 7 + src/chat.cpp | 37 +- src/chat.h | 17 +- src/client/CMakeLists.txt | 1 - src/client/guiChatConsole.cpp | 664 ---------------------------------- src/client/guiChatConsole.h | 138 ------- src/defaultsettings.cpp | 2 + src/game.cpp | 53 ++- src/guiChatConsole.cpp | 664 ++++++++++++++++++++++++++++++++++ src/guiChatConsole.h | 138 +++++++ src/guiEngine.cpp | 23 +- src/guiEngine.h | 3 + src/guiFormSpecMenu.cpp | 59 +-- src/guiFormSpecMenu.h | 13 +- src/irrlicht_changes/CMakeLists.txt | 7 + src/irrlicht_changes/static_text.cpp | 679 +++++++++++++++++++++++++++++++++++ src/irrlicht_changes/static_text.h | 268 ++++++++++++++ src/terminal_chat_console.cpp | 10 +- src/util/CMakeLists.txt | 11 +- src/util/coloredstring.cpp | 68 ---- src/util/coloredstring.h | 44 --- src/util/enriched_string.cpp | 166 +++++++++ src/util/enriched_string.h | 91 +++++ src/util/statictext.cpp | 654 --------------------------------- src/util/statictext.h | 150 -------- src/util/string.h | 32 ++ 32 files changed, 2235 insertions(+), 1864 deletions(-) delete mode 100644 src/client/guiChatConsole.cpp delete mode 100644 src/client/guiChatConsole.h create mode 100644 src/guiChatConsole.cpp create mode 100644 src/guiChatConsole.h create mode 100644 src/irrlicht_changes/CMakeLists.txt create mode 100644 src/irrlicht_changes/static_text.cpp create mode 100644 src/irrlicht_changes/static_text.h delete mode 100644 src/util/coloredstring.cpp delete mode 100644 src/util/coloredstring.h create mode 100644 src/util/enriched_string.cpp create mode 100644 src/util/enriched_string.h delete mode 100644 src/util/statictext.cpp delete mode 100644 src/util/statictext.h (limited to 'src/chat.h') diff --git a/builtin/game/chatcommands.lua b/builtin/game/chatcommands.lua index 2627559a5..22755386b 100644 --- a/builtin/game/chatcommands.lua +++ b/builtin/game/chatcommands.lua @@ -102,7 +102,7 @@ core.register_chatcommand("help", { description = "Get help for commands or list privileges", func = function(name, param) local function format_help_line(cmd, def) - local msg = core.colorize("00ffff", "/"..cmd) + local msg = core.colorize("#00ffff", "/"..cmd) if def.params and def.params ~= "" then msg = msg .. " " .. def.params end diff --git a/builtin/game/misc.lua b/builtin/game/misc.lua index 8d5c80216..918315656 100644 --- a/builtin/game/misc.lua +++ b/builtin/game/misc.lua @@ -198,19 +198,34 @@ function core.http_add_fetch(httpenv) return httpenv end -function core.get_color_escape_sequence(color) - --if string.len(color) == 3 then - -- local r = string.sub(color, 1, 1) - -- local g = string.sub(color, 2, 2) - -- local b = string.sub(color, 3, 3) - -- color = r .. r .. g .. g .. b .. b - --end - - --assert(#color == 6, "Color must be six characters in length.") - --return "\v" .. color - return "\v(color;" .. color .. ")" -end +if minetest.setting_getbool("disable_escape_sequences") then + + function core.get_color_escape_sequence(color) + return "" + end + + function core.get_background_escape_sequence(color) + return "" + end + + function core.colorize(color, message) + return message + end + +else + + local ESCAPE_CHAR = string.char(0x1b) + function core.get_color_escape_sequence(color) + return ESCAPE_CHAR .. "(c@" .. color .. ")" + end + + function core.get_background_escape_sequence(color) + return ESCAPE_CHAR .. "(b@" .. color .. ")" + end + + function core.colorize(color, message) + return core.get_color_escape_sequence(color) .. message .. core.get_color_escape_sequence("#ffffff") + end -function core.colorize(color, message) - return core.get_color_escape_sequence(color) .. message .. core.get_color_escape_sequence("ffffff") end + diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 93fb8e952..538a04f33 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -615,6 +615,11 @@ server_announce (Announce server) bool false # If you want to announce your ipv6 address, use serverlist_url = v6.servers.minetest.net. serverlist_url (Serverlist URL) string servers.minetest.net +# Disable escape sequences, e.g. chat coloring. +# Use this if you want to run a server with pre-0.4.14 clients and you want to disable +# the escape sequences generated by mods. +disable_escape_sequences (Disable escape sequences) bool false + [*Network] # Network port to listen (UDP). diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 82a0acbee..aa0d7e45d 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -1701,6 +1701,24 @@ numerical form, the raw integer value of an ARGB8 quad: or string form, a ColorString (defined above): `colorspec = "green"` +Escape sequences +---------------- +Most text can contain escape sequences, that can for example color the text. +There are a few exceptions: tab headers, dropdowns and vertical labels can't. +The following functions provide escape sequences: +* `core.get_color_escape_sequence(color)`: + * `color` is a ColorString + * The escape sequence sets the text color to `color` +* `core.colorize(color, message)`: + * Equivalent to: + `core.get_color_escape_sequence(color) .. + message .. + core.get_color_escape_sequence("#ffffff")` +* `color.get_background_escape_sequence(color)` + * `color` is a ColorString + * The escape sequence sets the background of the whole text element to + `color`. Only defined for item descriptions and tooltips. + Spatial Vectors --------------- * `vector.new(a[, b, c])`: returns a vector: diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ea1564eeb..f02812415 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -376,6 +376,7 @@ add_subdirectory(network) add_subdirectory(script) add_subdirectory(unittest) add_subdirectory(util) +add_subdirectory(irrlicht_changes) set(common_SRCS ban.cpp @@ -493,6 +494,7 @@ set(client_SRCS ${common_SRCS} ${sound_SRCS} ${client_network_SRCS} + ${client_irrlicht_changes_SRCS} camera.cpp client.cpp clientmap.cpp diff --git a/src/cguittfont/CGUITTFont.cpp b/src/cguittfont/CGUITTFont.cpp index 2342eb748..c2d37c6c0 100644 --- a/src/cguittfont/CGUITTFont.cpp +++ b/src/cguittfont/CGUITTFont.cpp @@ -1,6 +1,7 @@ /* CGUITTFont FreeType class for Irrlicht Copyright (c) 2009-2010 John Norman + Copyright (c) 2016 Nathanaël Courant This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any @@ -545,6 +546,13 @@ void CGUITTFont::setFontHinting(const bool enable, const bool enable_auto_hintin void CGUITTFont::draw(const core::stringw& text, const core::rect& position, video::SColor color, bool hcenter, bool vcenter, const core::rect* clip) { + draw(EnrichedString(std::wstring(text.c_str()), color), position, color, hcenter, vcenter, clip); +} + +void CGUITTFont::draw(const EnrichedString &text, const core::rect& position, video::SColor color, bool hcenter, bool vcenter, const core::rect* clip) +{ + std::vector colors = text.getColors(); + if (!Driver) return; @@ -572,7 +580,7 @@ void CGUITTFont::draw(const core::stringw& text, const core::rect& position } // Convert to a unicode string. - core::ustring utext(text); + core::ustring utext = text.getString(); // Set up our render map. core::map Render_Map; @@ -581,6 +589,7 @@ void CGUITTFont::draw(const core::stringw& text, const core::rect& position u32 n; uchar32_t previousChar = 0; core::ustring::const_iterator iter(utext); + std::vector applied_colors; while (!iter.atEnd()) { uchar32_t currentChar = *iter; @@ -590,7 +599,7 @@ void CGUITTFont::draw(const core::stringw& text, const core::rect& position if (currentChar == L'\r') // Mac or Windows breaks { lineBreak = true; - if (*(iter + 1) == (uchar32_t)'\n') // Windows line breaks. + if (*(iter + 1) == (uchar32_t)'\n') // Windows line breaks. currentChar = *(++iter); } else if (currentChar == (uchar32_t)'\n') // Unix breaks @@ -627,6 +636,9 @@ void CGUITTFont::draw(const core::stringw& text, const core::rect& position page->render_positions.push_back(core::position2di(offset.X + offx, offset.Y + offy)); page->render_source_rects.push_back(glyph.source_rect); Render_Map.set(glyph.glyph_page, page); + u32 current_color = iter.getPos(); + if (current_color < colors.size()) + applied_colors.push_back(colors[current_color]); } offset.X += getWidthFromCharacter(currentChar); @@ -645,8 +657,6 @@ void CGUITTFont::draw(const core::stringw& text, const core::rect& position CGUITTGlyphPage* page = n->getValue(); - if (!use_transparency) color.color |= 0xff000000; - if (shadow_offset) { for (size_t i = 0; i < page->render_positions.size(); ++i) page->render_positions[i] += core::vector2di(shadow_offset, shadow_offset); @@ -654,7 +664,17 @@ void CGUITTFont::draw(const core::stringw& text, const core::rect& position for (size_t i = 0; i < page->render_positions.size(); ++i) page->render_positions[i] -= core::vector2di(shadow_offset, shadow_offset); } - Driver->draw2DImageBatch(page->texture, page->render_positions, page->render_source_rects, clip, color, true); + for (size_t i = 0; i < page->render_positions.size(); ++i) { + irr::video::SColor col; + if (!applied_colors.empty()) { + col = applied_colors[i < applied_colors.size() ? i : 0]; + } else { + col = irr::video::SColor(255, 255, 255, 255); + } + if (!use_transparency) + col.color |= 0xff000000; + Driver->draw2DImage(page->texture, page->render_positions[i], page->render_source_rects[i], clip, col, true); + } } } diff --git a/src/cguittfont/CGUITTFont.h b/src/cguittfont/CGUITTFont.h index e24d8f18b..0aa540c5c 100644 --- a/src/cguittfont/CGUITTFont.h +++ b/src/cguittfont/CGUITTFont.h @@ -1,6 +1,7 @@ /* CGUITTFont FreeType class for Irrlicht Copyright (c) 2009-2010 John Norman + Copyright (c) 2016 Nathanaël Courant This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any @@ -33,6 +34,8 @@ #include #include +#include +#include "util/enriched_string.h" #include FT_FREETYPE_H namespace irr @@ -258,6 +261,10 @@ namespace gui virtual void draw(const core::stringw& text, const core::rect& position, video::SColor color, bool hcenter=false, bool vcenter=false, const core::rect* clip=0); + + virtual void draw(const EnrichedString& text, const core::rect& position, + video::SColor color, bool hcenter=false, bool vcenter=false, + const core::rect* clip=0); //! Returns the dimension of a character produced by this font. virtual core::dimension2d getCharDimension(const wchar_t ch) const; diff --git a/src/chat.cpp b/src/chat.cpp index 958389df5..46555b3dc 100644 --- a/src/chat.cpp +++ b/src/chat.cpp @@ -267,28 +267,26 @@ u32 ChatBuffer::formatChatLine(const ChatLine& line, u32 cols, next_frags.push_back(temp_frag); } - std::wstring name_sanitized = removeEscapes(line.name); + std::wstring name_sanitized = line.name.c_str(); // Choose an indentation level if (line.name.empty()) { // Server messages hanging_indentation = 0; - } - else if (name_sanitized.size() + 3 <= cols/2) { + } else if (name_sanitized.size() + 3 <= cols/2) { // Names shorter than about half the console width hanging_indentation = line.name.size() + 3; - } - else { + } else { // Very long names hanging_indentation = 2; } - ColoredString line_text(line.text); + //EnrichedString line_text(line.text); next_line.first = true; bool text_processing = false; // Produce fragments and layout them into lines - while (!next_frags.empty() || in_pos < line_text.size()) + while (!next_frags.empty() || in_pos < line.text.size()) { // Layout fragments into lines while (!next_frags.empty()) @@ -326,9 +324,9 @@ u32 ChatBuffer::formatChatLine(const ChatLine& line, u32 cols, } // Produce fragment - if (in_pos < line_text.size()) + if (in_pos < line.text.size()) { - u32 remaining_in_input = line_text.size() - in_pos; + u32 remaining_in_input = line.text.size() - in_pos; u32 remaining_in_output = cols - out_column; // Determine a fragment length <= the minimum of @@ -338,14 +336,14 @@ u32 ChatBuffer::formatChatLine(const ChatLine& line, u32 cols, while (frag_length < remaining_in_input && frag_length < remaining_in_output) { - if (isspace(line_text[in_pos + frag_length])) + if (isspace(line.text.getString()[in_pos + frag_length])) space_pos = frag_length; ++frag_length; } if (space_pos != 0 && frag_length < remaining_in_input) frag_length = space_pos + 1; - temp_frag.text = line_text.substr(in_pos, frag_length); + temp_frag.text = line.text.substr(in_pos, frag_length); temp_frag.column = 0; //temp_frag.bold = 0; next_frags.push_back(temp_frag); @@ -729,19 +727,22 @@ ChatBuffer& ChatBackend::getRecentBuffer() return m_recent_buffer; } -std::wstring ChatBackend::getRecentChat() +EnrichedString ChatBackend::getRecentChat() { - std::wostringstream stream; + EnrichedString result; for (u32 i = 0; i < m_recent_buffer.getLineCount(); ++i) { const ChatLine& line = m_recent_buffer.getLine(i); if (i != 0) - stream << L"\n"; - if (!line.name.empty()) - stream << L"<" << line.name << L"> "; - stream << line.text; + result += L"\n"; + if (!line.name.empty()) { + result += L"<"; + result += line.name; + result += L"> "; + } + result += line.text; } - return stream.str(); + return result; } ChatPrompt& ChatBackend::getPrompt() diff --git a/src/chat.h b/src/chat.h index 661cafc82..11061fd39 100644 --- a/src/chat.h +++ b/src/chat.h @@ -25,7 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include "irrlichttypes.h" -#include "util/coloredstring.h" +#include "util/enriched_string.h" // Chat console related classes @@ -34,9 +34,9 @@ struct ChatLine // age in seconds f32 age; // name of sending player, or empty if sent by server - std::wstring name; + EnrichedString name; // message text - ColoredString text; + EnrichedString text; ChatLine(std::wstring a_name, std::wstring a_text): age(0.0), @@ -44,12 +44,19 @@ struct ChatLine text(a_text) { } + + ChatLine(EnrichedString a_name, EnrichedString a_text): + age(0.0), + name(a_name), + text(a_text) + { + } }; struct ChatFormattedFragment { // text string - std::wstring text; + EnrichedString text; // starting column u32 column; // formatting @@ -262,7 +269,7 @@ public: // Get the recent messages buffer ChatBuffer& getRecentBuffer(); // Concatenate all recent messages - std::wstring getRecentChat(); + EnrichedString getRecentChat(); // Get the console prompt ChatPrompt& getPrompt(); diff --git a/src/client/CMakeLists.txt b/src/client/CMakeLists.txt index bcf114760..a1ec37fe3 100644 --- a/src/client/CMakeLists.txt +++ b/src/client/CMakeLists.txt @@ -1,6 +1,5 @@ set(client_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/clientlauncher.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/guiChatConsole.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tile.cpp PARENT_SCOPE ) diff --git a/src/client/guiChatConsole.cpp b/src/client/guiChatConsole.cpp deleted file mode 100644 index d8837556a..000000000 --- a/src/client/guiChatConsole.cpp +++ /dev/null @@ -1,664 +0,0 @@ -/* -Minetest -Copyright (C) 2013 celeron55, Perttu Ahola - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#include "guiChatConsole.h" -#include "chat.h" -#include "client.h" -#include "debug.h" -#include "gettime.h" -#include "keycode.h" -#include "settings.h" -#include "porting.h" -#include "client/tile.h" -#include "fontengine.h" -#include "log.h" -#include "gettext.h" -#include - -#if USE_FREETYPE - #include "xCGUITTFont.h" -#endif - -inline u32 clamp_u8(s32 value) -{ - return (u32) MYMIN(MYMAX(value, 0), 255); -} - - -GUIChatConsole::GUIChatConsole( - gui::IGUIEnvironment* env, - gui::IGUIElement* parent, - s32 id, - ChatBackend* backend, - Client* client, - IMenuManager* menumgr -): - IGUIElement(gui::EGUIET_ELEMENT, env, parent, id, - core::rect(0,0,100,100)), - m_chat_backend(backend), - m_client(client), - m_menumgr(menumgr), - m_screensize(v2u32(0,0)), - m_animate_time_old(0), - m_open(false), - m_close_on_enter(false), - m_height(0), - m_desired_height(0), - m_desired_height_fraction(0.0), - m_height_speed(5.0), - m_open_inhibited(0), - m_cursor_blink(0.0), - m_cursor_blink_speed(0.0), - m_cursor_height(0.0), - m_background(NULL), - m_background_color(255, 0, 0, 0), - m_font(NULL), - m_fontsize(0, 0) -{ - m_animate_time_old = getTimeMs(); - - // load background settings - s32 console_alpha = g_settings->getS32("console_alpha"); - m_background_color.setAlpha(clamp_u8(console_alpha)); - - // load the background texture depending on settings - ITextureSource *tsrc = client->getTextureSource(); - if (tsrc->isKnownSourceImage("background_chat.jpg")) { - m_background = tsrc->getTexture("background_chat.jpg"); - m_background_color.setRed(255); - m_background_color.setGreen(255); - m_background_color.setBlue(255); - } else { - v3f console_color = g_settings->getV3F("console_color"); - m_background_color.setRed(clamp_u8(myround(console_color.X))); - m_background_color.setGreen(clamp_u8(myround(console_color.Y))); - m_background_color.setBlue(clamp_u8(myround(console_color.Z))); - } - - m_font = g_fontengine->getFont(FONT_SIZE_UNSPECIFIED, FM_Mono); - - if (m_font == NULL) - { - errorstream << "GUIChatConsole: Unable to load mono font "; - } - else - { - core::dimension2d dim = m_font->getDimension(L"M"); - m_fontsize = v2u32(dim.Width, dim.Height); - m_font->grab(); - } - m_fontsize.X = MYMAX(m_fontsize.X, 1); - m_fontsize.Y = MYMAX(m_fontsize.Y, 1); - - // set default cursor options - setCursor(true, true, 2.0, 0.1); -} - -GUIChatConsole::~GUIChatConsole() -{ - if (m_font) - m_font->drop(); -} - -void GUIChatConsole::openConsole(f32 height) -{ - m_open = true; - m_desired_height_fraction = height; - m_desired_height = height * m_screensize.Y; - reformatConsole(); - m_animate_time_old = getTimeMs(); - IGUIElement::setVisible(true); - Environment->setFocus(this); - m_menumgr->createdMenu(this); -} - -bool GUIChatConsole::isOpen() const -{ - return m_open; -} - -bool GUIChatConsole::isOpenInhibited() const -{ - return m_open_inhibited > 0; -} - -void GUIChatConsole::closeConsole() -{ - m_open = false; - Environment->removeFocus(this); - m_menumgr->deletingMenu(this); -} - -void GUIChatConsole::closeConsoleAtOnce() -{ - closeConsole(); - m_height = 0; - recalculateConsolePosition(); -} - -f32 GUIChatConsole::getDesiredHeight() const -{ - return m_desired_height_fraction; -} - -void GUIChatConsole::replaceAndAddToHistory(std::wstring line) -{ - ChatPrompt& prompt = m_chat_backend->getPrompt(); - prompt.addToHistory(prompt.getLine()); - prompt.replace(line); -} - - -void GUIChatConsole::setCursor( - bool visible, bool blinking, f32 blink_speed, f32 relative_height) -{ - if (visible) - { - if (blinking) - { - // leave m_cursor_blink unchanged - m_cursor_blink_speed = blink_speed; - } - else - { - m_cursor_blink = 0x8000; // on - m_cursor_blink_speed = 0.0; - } - } - else - { - m_cursor_blink = 0; // off - m_cursor_blink_speed = 0.0; - } - m_cursor_height = relative_height; -} - -void GUIChatConsole::draw() -{ - if(!IsVisible) - return; - - video::IVideoDriver* driver = Environment->getVideoDriver(); - - // Check screen size - v2u32 screensize = driver->getScreenSize(); - if (screensize != m_screensize) - { - // screen size has changed - // scale current console height to new window size - if (m_screensize.Y != 0) - m_height = m_height * screensize.Y / m_screensize.Y; - m_desired_height = m_desired_height_fraction * m_screensize.Y; - m_screensize = screensize; - reformatConsole(); - } - - // Animation - u32 now = getTimeMs(); - animate(now - m_animate_time_old); - m_animate_time_old = now; - - // Draw console elements if visible - if (m_height > 0) - { - drawBackground(); - drawText(); - drawPrompt(); - } - - gui::IGUIElement::draw(); -} - -void GUIChatConsole::reformatConsole() -{ - s32 cols = m_screensize.X / m_fontsize.X - 2; // make room for a margin (looks better) - s32 rows = m_desired_height / m_fontsize.Y - 1; // make room for the input prompt - if (cols <= 0 || rows <= 0) - cols = rows = 0; - m_chat_backend->reformat(cols, rows); -} - -void GUIChatConsole::recalculateConsolePosition() -{ - core::rect rect(0, 0, m_screensize.X, m_height); - DesiredRect = rect; - recalculateAbsolutePosition(false); -} - -void GUIChatConsole::animate(u32 msec) -{ - // animate the console height - s32 goal = m_open ? m_desired_height : 0; - - // Set invisible if close animation finished (reset by openConsole) - // This function (animate()) is never called once its visibility becomes false so do not - // actually set visible to false before the inhibited period is over - if (!m_open && m_height == 0 && m_open_inhibited == 0) - IGUIElement::setVisible(false); - - if (m_height != goal) - { - s32 max_change = msec * m_screensize.Y * (m_height_speed / 1000.0); - if (max_change == 0) - max_change = 1; - - if (m_height < goal) - { - // increase height - if (m_height + max_change < goal) - m_height += max_change; - else - m_height = goal; - } - else - { - // decrease height - if (m_height > goal + max_change) - m_height -= max_change; - else - m_height = goal; - } - - recalculateConsolePosition(); - } - - // blink the cursor - if (m_cursor_blink_speed != 0.0) - { - u32 blink_increase = 0x10000 * msec * (m_cursor_blink_speed / 1000.0); - if (blink_increase == 0) - blink_increase = 1; - m_cursor_blink = ((m_cursor_blink + blink_increase) & 0xffff); - } - - // decrease open inhibit counter - if (m_open_inhibited > msec) - m_open_inhibited -= msec; - else - m_open_inhibited = 0; -} - -void GUIChatConsole::drawBackground() -{ - video::IVideoDriver* driver = Environment->getVideoDriver(); - if (m_background != NULL) - { - core::rect sourcerect(0, -m_height, m_screensize.X, 0); - driver->draw2DImage( - m_background, - v2s32(0, 0), - sourcerect, - &AbsoluteClippingRect, - m_background_color, - false); - } - else - { - driver->draw2DRectangle( - m_background_color, - core::rect(0, 0, m_screensize.X, m_height), - &AbsoluteClippingRect); - } -} - -void GUIChatConsole::drawText() -{ - if (m_font == NULL) - return; - - ChatBuffer& buf = m_chat_backend->getConsoleBuffer(); - for (u32 row = 0; row < buf.getRows(); ++row) - { - const ChatFormattedLine& line = buf.getFormattedLine(row); - if (line.fragments.empty()) - continue; - - s32 line_height = m_fontsize.Y; - s32 y = row * line_height + m_height - m_desired_height; - if (y + line_height < 0) - continue; - - for (u32 i = 0; i < line.fragments.size(); ++i) - { - const ChatFormattedFragment& fragment = line.fragments[i]; - s32 x = (fragment.column + 1) * m_fontsize.X; - core::rect destrect( - x, y, x + m_fontsize.X * fragment.text.size(), y + m_fontsize.Y); - - - #if USE_FREETYPE - // Draw colored text if FreeType is enabled - irr::gui::CGUITTFont *tmp = static_cast(m_font); - tmp->draw( - fragment.text.c_str(), - destrect, - fragment.text.getColors(), - false, - false, - &AbsoluteClippingRect); - #else - // Otherwise use standard text - m_font->draw( - fragment.text.c_str(), - destrect, - video::SColor(255, 255, 255, 255), - false, - false, - &AbsoluteClippingRect); - #endif - } - } -} - -void GUIChatConsole::drawPrompt() -{ - if (m_font == NULL) - return; - - u32 row = m_chat_backend->getConsoleBuffer().getRows(); - s32 line_height = m_fontsize.Y; - s32 y = row * line_height + m_height - m_desired_height; - - ChatPrompt& prompt = m_chat_backend->getPrompt(); - std::wstring prompt_text = prompt.getVisiblePortion(); - - // FIXME Draw string at once, not character by character - // That will only work with the cursor once we have a monospace font - for (u32 i = 0; i < prompt_text.size(); ++i) - { - wchar_t ws[2] = {prompt_text[i], 0}; - s32 x = (1 + i) * m_fontsize.X; - core::rect destrect( - x, y, x + m_fontsize.X, y + m_fontsize.Y); - m_font->draw( - ws, - destrect, - video::SColor(255, 255, 255, 255), - false, - false, - &AbsoluteClippingRect); - } - - // Draw the cursor during on periods - if ((m_cursor_blink & 0x8000) != 0) - { - s32 cursor_pos = prompt.getVisibleCursorPosition(); - if (cursor_pos >= 0) - { - s32 cursor_len = prompt.getCursorLength(); - video::IVideoDriver* driver = Environment->getVideoDriver(); - s32 x = (1 + cursor_pos) * m_fontsize.X; - core::rect destrect( - x, - y + m_fontsize.Y * (1.0 - m_cursor_height), - x + m_fontsize.X * MYMAX(cursor_len, 1), - y + m_fontsize.Y * (cursor_len ? m_cursor_height+1 : 1) - ); - video::SColor cursor_color(255,255,255,255); - driver->draw2DRectangle( - cursor_color, - destrect, - &AbsoluteClippingRect); - } - } - -} - -bool GUIChatConsole::OnEvent(const SEvent& event) -{ - - ChatPrompt &prompt = m_chat_backend->getPrompt(); - - if(event.EventType == EET_KEY_INPUT_EVENT && event.KeyInput.PressedDown) - { - // Key input - if(KeyPress(event.KeyInput) == getKeySetting("keymap_console")) - { - closeConsole(); - - // inhibit open so the_game doesn't reopen immediately - m_open_inhibited = 50; - m_close_on_enter = false; - return true; - } - else if(event.KeyInput.Key == KEY_ESCAPE) - { - closeConsoleAtOnce(); - m_close_on_enter = false; - // inhibit open so the_game doesn't reopen immediately - m_open_inhibited = 1; // so the ESCAPE button doesn't open the "pause menu" - return true; - } - else if(event.KeyInput.Key == KEY_PRIOR) - { - m_chat_backend->scrollPageUp(); - return true; - } - else if(event.KeyInput.Key == KEY_NEXT) - { - m_chat_backend->scrollPageDown(); - return true; - } - else if(event.KeyInput.Key == KEY_RETURN) - { - prompt.addToHistory(prompt.getLine()); - std::wstring text = prompt.replace(L""); - m_client->typeChatMessage(text); - if (m_close_on_enter) { - closeConsoleAtOnce(); - m_close_on_enter = false; - } - return true; - } - else if(event.KeyInput.Key == KEY_UP) - { - // Up pressed - // Move back in history - prompt.historyPrev(); - return true; - } - else if(event.KeyInput.Key == KEY_DOWN) - { - // Down pressed - // Move forward in history - prompt.historyNext(); - return true; - } - else if(event.KeyInput.Key == KEY_LEFT || event.KeyInput.Key == KEY_RIGHT) - { - // Left/right pressed - // Move/select character/word to the left depending on control and shift keys - ChatPrompt::CursorOp op = event.KeyInput.Shift ? - ChatPrompt::CURSOROP_SELECT : - ChatPrompt::CURSOROP_MOVE; - ChatPrompt::CursorOpDir dir = event.KeyInput.Key == KEY_LEFT ? - ChatPrompt::CURSOROP_DIR_LEFT : - ChatPrompt::CURSOROP_DIR_RIGHT; - ChatPrompt::CursorOpScope scope = event.KeyInput.Control ? - ChatPrompt::CURSOROP_SCOPE_WORD : - ChatPrompt::CURSOROP_SCOPE_CHARACTER; - prompt.cursorOperation(op, dir, scope); - return true; - } - else if(event.KeyInput.Key == KEY_HOME) - { - // Home pressed - // move to beginning of line - prompt.cursorOperation( - ChatPrompt::CURSOROP_MOVE, - ChatPrompt::CURSOROP_DIR_LEFT, - ChatPrompt::CURSOROP_SCOPE_LINE); - return true; - } - else if(event.KeyInput.Key == KEY_END) - { - // End pressed - // move to end of line - prompt.cursorOperation( - ChatPrompt::CURSOROP_MOVE, - ChatPrompt::CURSOROP_DIR_RIGHT, - ChatPrompt::CURSOROP_SCOPE_LINE); - return true; - } - else if(event.KeyInput.Key == KEY_BACK) - { - // Backspace or Ctrl-Backspace pressed - // delete character / word to the left - ChatPrompt::CursorOpScope scope = - event.KeyInput.Control ? - ChatPrompt::CURSOROP_SCOPE_WORD : - ChatPrompt::CURSOROP_SCOPE_CHARACTER; - prompt.cursorOperation( - ChatPrompt::CURSOROP_DELETE, - ChatPrompt::CURSOROP_DIR_LEFT, - scope); - return true; - } - else if(event.KeyInput.Key == KEY_DELETE) - { - // Delete or Ctrl-Delete pressed - // delete character / word to the right - ChatPrompt::CursorOpScope scope = - event.KeyInput.Control ? - ChatPrompt::CURSOROP_SCOPE_WORD : - ChatPrompt::CURSOROP_SCOPE_CHARACTER; - prompt.cursorOperation( - ChatPrompt::CURSOROP_DELETE, - ChatPrompt::CURSOROP_DIR_RIGHT, - scope); - return true; - } - else if(event.KeyInput.Key == KEY_KEY_A && event.KeyInput.Control) - { - // Ctrl-A pressed - // Select all text - prompt.cursorOperation( - ChatPrompt::CURSOROP_SELECT, - ChatPrompt::CURSOROP_DIR_LEFT, // Ignored - ChatPrompt::CURSOROP_SCOPE_LINE); - return true; - } - else if(event.KeyInput.Key == KEY_KEY_C && event.KeyInput.Control) - { - // Ctrl-C pressed - // Copy text to clipboard - if (prompt.getCursorLength() <= 0) - return true; - std::wstring wselected = prompt.getSelection(); - std::string selected(wselected.begin(), wselected.end()); - Environment->getOSOperator()->copyToClipboard(selected.c_str()); - return true; - } - else if(event.KeyInput.Key == KEY_KEY_V && event.KeyInput.Control) - { - // Ctrl-V pressed - // paste text from clipboard - if (prompt.getCursorLength() > 0) { - // Delete selected section of text - prompt.cursorOperation( - ChatPrompt::CURSOROP_DELETE, - ChatPrompt::CURSOROP_DIR_LEFT, // Ignored - ChatPrompt::CURSOROP_SCOPE_SELECTION); - } - IOSOperator *os_operator = Environment->getOSOperator(); - const c8 *text = os_operator->getTextFromClipboard(); - if (!text) - return true; - std::basic_string str((const unsigned char*)text); - prompt.input(std::wstring(str.begin(), str.end())); - return true; - } - else if(event.KeyInput.Key == KEY_KEY_X && event.KeyInput.Control) - { - // Ctrl-X pressed - // Cut text to clipboard - if (prompt.getCursorLength() <= 0) - return true; - std::wstring wselected = prompt.getSelection(); - std::string selected(wselected.begin(), wselected.end()); - Environment->getOSOperator()->copyToClipboard(selected.c_str()); - prompt.cursorOperation( - ChatPrompt::CURSOROP_DELETE, - ChatPrompt::CURSOROP_DIR_LEFT, // Ignored - ChatPrompt::CURSOROP_SCOPE_SELECTION); - return true; - } - else if(event.KeyInput.Key == KEY_KEY_U && event.KeyInput.Control) - { - // Ctrl-U pressed - // kill line to left end - prompt.cursorOperation( - ChatPrompt::CURSOROP_DELETE, - ChatPrompt::CURSOROP_DIR_LEFT, - ChatPrompt::CURSOROP_SCOPE_LINE); - return true; - } - else if(event.KeyInput.Key == KEY_KEY_K && event.KeyInput.Control) - { - // Ctrl-K pressed - // kill line to right end - prompt.cursorOperation( - ChatPrompt::CURSOROP_DELETE, - ChatPrompt::CURSOROP_DIR_RIGHT, - ChatPrompt::CURSOROP_SCOPE_LINE); - return true; - } - else if(event.KeyInput.Key == KEY_TAB) - { - // Tab or Shift-Tab pressed - // Nick completion - std::list names = m_client->getConnectedPlayerNames(); - bool backwards = event.KeyInput.Shift; - prompt.nickCompletion(names, backwards); - return true; - } - else if(event.KeyInput.Char != 0 && !event.KeyInput.Control) - { - #if (defined(linux) || defined(__linux)) - wchar_t wc = L'_'; - mbtowc( &wc, (char *) &event.KeyInput.Char, sizeof(event.KeyInput.Char) ); - prompt.input(wc); - #else - prompt.input(event.KeyInput.Char); - #endif - return true; - } - } - else if(event.EventType == EET_MOUSE_INPUT_EVENT) - { - if(event.MouseInput.Event == EMIE_MOUSE_WHEEL) - { - s32 rows = myround(-3.0 * event.MouseInput.Wheel); - m_chat_backend->scroll(rows); - } - } - - return Parent ? Parent->OnEvent(event) : false; -} - -void GUIChatConsole::setVisible(bool visible) -{ - m_open = visible; - IGUIElement::setVisible(visible); - if (!visible) { - m_height = 0; - recalculateConsolePosition(); - } -} - diff --git a/src/client/guiChatConsole.h b/src/client/guiChatConsole.h deleted file mode 100644 index 3013a1d31..000000000 --- a/src/client/guiChatConsole.h +++ /dev/null @@ -1,138 +0,0 @@ -/* -Minetest -Copyright (C) 2013 celeron55, Perttu Ahola - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#ifndef GUICHATCONSOLE_HEADER -#define GUICHATCONSOLE_HEADER - -#include "irrlichttypes_extrabloated.h" -#include "modalMenu.h" -#include "chat.h" -#include "config.h" - -class Client; - -class GUIChatConsole : public gui::IGUIElement -{ -public: - GUIChatConsole(gui::IGUIEnvironment* env, - gui::IGUIElement* parent, - s32 id, - ChatBackend* backend, - Client* client, - IMenuManager* menumgr); - virtual ~GUIChatConsole(); - - // Open the console (height = desired fraction of screen size) - // This doesn't open immediately but initiates an animation. - // You should call isOpenInhibited() before this. - void openConsole(f32 height); - - bool isOpen() const; - - // Check if the console should not be opened at the moment - // This is to avoid reopening the console immediately after closing - bool isOpenInhibited() const; - // Close the console, equivalent to openConsole(0). - // This doesn't close immediately but initiates an animation. - void closeConsole(); - // Close the console immediately, without animation. - void closeConsoleAtOnce(); - // Set whether to close the console after the user presses enter. - void setCloseOnEnter(bool close) { m_close_on_enter = close; } - - // Return the desired height (fraction of screen size) - // Zero if the console is closed or getting closed - f32 getDesiredHeight() const; - - // Replace actual line when adding the actual to the history (if there is any) - void replaceAndAddToHistory(std::wstring line); - - // Change how the cursor looks - void setCursor( - bool visible, - bool blinking = false, - f32 blink_speed = 1.0, - f32 relative_height = 1.0); - - // Irrlicht draw method - virtual void draw(); - - bool canTakeFocus(gui::IGUIElement* element) { return false; } - - virtual bool OnEvent(const SEvent& event); - - virtual void setVisible(bool visible); - -private: - void reformatConsole(); - void recalculateConsolePosition(); - - // These methods are called by draw - void animate(u32 msec); - void drawBackground(); - void drawText(); - void drawPrompt(); - -private: - ChatBackend* m_chat_backend; - Client* m_client; - IMenuManager* m_menumgr; - - // current screen size - v2u32 m_screensize; - - // used to compute how much time passed since last animate() - u32 m_animate_time_old; - - // should the console be opened or closed? - bool m_open; - // should it close after you press enter? - bool m_close_on_enter; - // current console height [pixels] - s32 m_height; - // desired height [pixels] - f32 m_desired_height; - // desired height [screen height fraction] - f32 m_desired_height_fraction; - // console open/close animation speed [screen height fraction / second] - f32 m_height_speed; - // if nonzero, opening the console is inhibited [milliseconds] - u32 m_open_inhibited; - - // cursor blink frame (16-bit value) - // cursor is off during [0,32767] and on during [32768,65535] - u32 m_cursor_blink; - // cursor blink speed [on/off toggles / second] - f32 m_cursor_blink_speed; - // cursor height [line height] - f32 m_cursor_height; - - // background texture - video::ITexture* m_background; - // background color (including alpha) - video::SColor m_background_color; - - // font - gui::IGUIFont* m_font; - v2u32 m_fontsize; -}; - - -#endif - diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index e8b652c17..632ec0df9 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -202,6 +202,8 @@ void set_default_settings(Settings *settings) settings->setDefault("server_name", ""); settings->setDefault("server_description", ""); + settings->setDefault("disable_escape_sequences", "false"); + #if USE_FREETYPE settings->setDefault("freetype", "true"); settings->setDefault("font_path", porting::getDataPath("fonts" DIR_DELIM "liberationsans.ttf")); diff --git a/src/game.cpp b/src/game.cpp index 71a04aef5..def202fe5 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -34,7 +34,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "log.h" #include "filesys.h" #include "gettext.h" -#include "client/guiChatConsole.h" +#include "guiChatConsole.h" #include "guiFormSpecMenu.h" #include "guiKeyChangeMenu.h" #include "guiPasswordChange.h" @@ -55,14 +55,11 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "tool.h" #include "util/directiontables.h" #include "util/pointedthing.h" +#include "irrlicht_changes/static_text.h" #include "version.h" #include "minimap.h" #include "mapblock_mesh.h" -#if USE_FREETYPE - #include "util/statictext.h" -#endif - #include "sound.h" #if USE_SOUND @@ -541,7 +538,7 @@ void update_profiler_gui(gui::IGUIStaticText *guitext_profiler, FontEngine *fe, std::ostringstream os(std::ios_base::binary); g_profiler->printPage(os, show_profiler, show_profiler_max); std::wstring text = utf8_to_wide(os.str()); - guitext_profiler->setText(text.c_str()); + setStaticText(guitext_profiler, text.c_str()); guitext_profiler->setVisible(true); s32 w = fe->getTextWidth(text.c_str()); @@ -1244,7 +1241,11 @@ static void updateChat(Client &client, f32 dtime, bool show_debug, // Get new messages from error log buffer while (!chat_log_error_buf.empty()) { - chat_backend.addMessage(L"", utf8_to_wide(chat_log_error_buf.get())); + std::wstring error_message = utf8_to_wide(chat_log_error_buf.get()); + if (!g_settings->getBool("disable_escape_sequences")) { + error_message = L"\x1b(c@red)" + error_message + L"\x1b(c@white)"; + } + chat_backend.addMessage(L"", error_message); } // Get new messages from client @@ -1259,10 +1260,10 @@ static void updateChat(Client &client, f32 dtime, bool show_debug, // Display all messages in a static text element unsigned int recent_chat_count = chat_backend.getRecentBuffer().getLineCount(); - std::wstring recent_chat = chat_backend.getRecentChat(); + EnrichedString recent_chat = chat_backend.getRecentChat(); unsigned int line_height = g_fontengine->getLineHeight(); - guitext_chat->setText(recent_chat.c_str()); + setStaticText(guitext_chat, recent_chat); // Update gui element size and position s32 chat_y = 5 + line_height; @@ -1271,7 +1272,7 @@ static void updateChat(Client &client, f32 dtime, bool show_debug, chat_y += line_height; // first pass to calculate height of text to be set - s32 width = std::min(g_fontengine->getTextWidth(recent_chat) + 10, + s32 width = std::min(g_fontengine->getTextWidth(recent_chat.c_str()) + 10, porting::getWindowSize().X - 20); core::rect rect(10, chat_y, width, chat_y + porting::getWindowSize().Y); guitext_chat->setRelativePosition(rect); @@ -2218,45 +2219,39 @@ bool Game::createClient(const std::string &playername, bool Game::initGui() { // First line of debug text - guitext = guienv->addStaticText( + guitext = addStaticText(guienv, utf8_to_wide(PROJECT_NAME_C).c_str(), core::rect(0, 0, 0, 0), false, false, guiroot); // Second line of debug text - guitext2 = guienv->addStaticText( + guitext2 = addStaticText(guienv, L"", core::rect(0, 0, 0, 0), false, false, guiroot); // At the middle of the screen // Object infos are shown in this - guitext_info = guienv->addStaticText( + guitext_info = addStaticText(guienv, L"", core::rect(0, 0, 400, g_fontengine->getTextHeight() * 5 + 5) + v2s32(100, 200), false, true, guiroot); // Status text (displays info when showing and hiding GUI stuff, etc.) - guitext_status = guienv->addStaticText( + guitext_status = addStaticText(guienv, L"", core::rect(0, 0, 0, 0), false, false, guiroot); guitext_status->setVisible(false); -#if USE_FREETYPE - // Colored chat support when using FreeType - guitext_chat = new gui::StaticText(L"", false, guienv, guiroot, -1, core::rect(0, 0, 0, 0), false); - guitext_chat->setWordWrap(true); - guitext_chat->drop(); -#else - // Standard chat when FreeType is disabled // Chat text - guitext_chat = guienv->addStaticText( + guitext_chat = addStaticText( + guienv, L"", core::rect(0, 0, 0, 0), //false, false); // Disable word wrap as of now false, true, guiroot); -#endif + // Remove stale "recent" chat messages from previous connections chat_backend->clearRecentChat(); @@ -2270,7 +2265,7 @@ bool Game::initGui() } // Profiler text (size is updated when text is updated) - guitext_profiler = guienv->addStaticText( + guitext_profiler = addStaticText(guienv, L"", core::rect(0, 0, 0, 0), false, false, guiroot); @@ -4308,12 +4303,12 @@ void Game::updateGui(float *statustext_time, const RunStats &stats, << ", v_range = " << draw_control->wanted_range << std::setprecision(3) << ", RTT = " << client->getRTT(); - guitext->setText(utf8_to_wide(os.str()).c_str()); + setStaticText(guitext, utf8_to_wide(os.str()).c_str()); guitext->setVisible(true); } else if (flags.show_hud || flags.show_chat) { std::ostringstream os(std::ios_base::binary); os << PROJECT_NAME_C " " << g_version_hash; - guitext->setText(utf8_to_wide(os.str()).c_str()); + setStaticText(guitext, utf8_to_wide(os.str()).c_str()); guitext->setVisible(true); } else { guitext->setVisible(false); @@ -4350,7 +4345,7 @@ void Game::updateGui(float *statustext_time, const RunStats &stats, } } - guitext2->setText(utf8_to_wide(os.str()).c_str()); + setStaticText(guitext2, utf8_to_wide(os.str()).c_str()); guitext2->setVisible(true); core::rect rect( @@ -4362,7 +4357,7 @@ void Game::updateGui(float *statustext_time, const RunStats &stats, guitext2->setVisible(false); } - guitext_info->setText(infotext.c_str()); + setStaticText(guitext_info, infotext.c_str()); guitext_info->setVisible(flags.show_hud && g_menumgr.menuCount() == 0); float statustext_time_max = 1.5; @@ -4376,7 +4371,7 @@ void Game::updateGui(float *statustext_time, const RunStats &stats, } } - guitext_status->setText(statustext.c_str()); + setStaticText(guitext_status, statustext.c_str()); guitext_status->setVisible(!statustext.empty()); if (!statustext.empty()) { diff --git a/src/guiChatConsole.cpp b/src/guiChatConsole.cpp new file mode 100644 index 000000000..bb58d1305 --- /dev/null +++ b/src/guiChatConsole.cpp @@ -0,0 +1,664 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "guiChatConsole.h" +#include "chat.h" +#include "client.h" +#include "debug.h" +#include "gettime.h" +#include "keycode.h" +#include "settings.h" +#include "porting.h" +#include "client/tile.h" +#include "fontengine.h" +#include "log.h" +#include "gettext.h" +#include + +#if USE_FREETYPE + #include "xCGUITTFont.h" +#endif + +inline u32 clamp_u8(s32 value) +{ + return (u32) MYMIN(MYMAX(value, 0), 255); +} + + +GUIChatConsole::GUIChatConsole( + gui::IGUIEnvironment* env, + gui::IGUIElement* parent, + s32 id, + ChatBackend* backend, + Client* client, + IMenuManager* menumgr +): + IGUIElement(gui::EGUIET_ELEMENT, env, parent, id, + core::rect(0,0,100,100)), + m_chat_backend(backend), + m_client(client), + m_menumgr(menumgr), + m_screensize(v2u32(0,0)), + m_animate_time_old(0), + m_open(false), + m_close_on_enter(false), + m_height(0), + m_desired_height(0), + m_desired_height_fraction(0.0), + m_height_speed(5.0), + m_open_inhibited(0), + m_cursor_blink(0.0), + m_cursor_blink_speed(0.0), + m_cursor_height(0.0), + m_background(NULL), + m_background_color(255, 0, 0, 0), + m_font(NULL), + m_fontsize(0, 0) +{ + m_animate_time_old = getTimeMs(); + + // load background settings + s32 console_alpha = g_settings->getS32("console_alpha"); + m_background_color.setAlpha(clamp_u8(console_alpha)); + + // load the background texture depending on settings + ITextureSource *tsrc = client->getTextureSource(); + if (tsrc->isKnownSourceImage("background_chat.jpg")) { + m_background = tsrc->getTexture("background_chat.jpg"); + m_background_color.setRed(255); + m_background_color.setGreen(255); + m_background_color.setBlue(255); + } else { + v3f console_color = g_settings->getV3F("console_color"); + m_background_color.setRed(clamp_u8(myround(console_color.X))); + m_background_color.setGreen(clamp_u8(myround(console_color.Y))); + m_background_color.setBlue(clamp_u8(myround(console_color.Z))); + } + + m_font = g_fontengine->getFont(FONT_SIZE_UNSPECIFIED, FM_Mono); + + if (m_font == NULL) + { + errorstream << "GUIChatConsole: Unable to load mono font "; + } + else + { + core::dimension2d dim = m_font->getDimension(L"M"); + m_fontsize = v2u32(dim.Width, dim.Height); + m_font->grab(); + } + m_fontsize.X = MYMAX(m_fontsize.X, 1); + m_fontsize.Y = MYMAX(m_fontsize.Y, 1); + + // set default cursor options + setCursor(true, true, 2.0, 0.1); +} + +GUIChatConsole::~GUIChatConsole() +{ + if (m_font) + m_font->drop(); +} + +void GUIChatConsole::openConsole(f32 height) +{ + m_open = true; + m_desired_height_fraction = height; + m_desired_height = height * m_screensize.Y; + reformatConsole(); + m_animate_time_old = getTimeMs(); + IGUIElement::setVisible(true); + Environment->setFocus(this); + m_menumgr->createdMenu(this); +} + +bool GUIChatConsole::isOpen() const +{ + return m_open; +} + +bool GUIChatConsole::isOpenInhibited() const +{ + return m_open_inhibited > 0; +} + +void GUIChatConsole::closeConsole() +{ + m_open = false; + Environment->removeFocus(this); + m_menumgr->deletingMenu(this); +} + +void GUIChatConsole::closeConsoleAtOnce() +{ + closeConsole(); + m_height = 0; + recalculateConsolePosition(); +} + +f32 GUIChatConsole::getDesiredHeight() const +{ + return m_desired_height_fraction; +} + +void GUIChatConsole::replaceAndAddToHistory(std::wstring line) +{ + ChatPrompt& prompt = m_chat_backend->getPrompt(); + prompt.addToHistory(prompt.getLine()); + prompt.replace(line); +} + + +void GUIChatConsole::setCursor( + bool visible, bool blinking, f32 blink_speed, f32 relative_height) +{ + if (visible) + { + if (blinking) + { + // leave m_cursor_blink unchanged + m_cursor_blink_speed = blink_speed; + } + else + { + m_cursor_blink = 0x8000; // on + m_cursor_blink_speed = 0.0; + } + } + else + { + m_cursor_blink = 0; // off + m_cursor_blink_speed = 0.0; + } + m_cursor_height = relative_height; +} + +void GUIChatConsole::draw() +{ + if(!IsVisible) + return; + + video::IVideoDriver* driver = Environment->getVideoDriver(); + + // Check screen size + v2u32 screensize = driver->getScreenSize(); + if (screensize != m_screensize) + { + // screen size has changed + // scale current console height to new window size + if (m_screensize.Y != 0) + m_height = m_height * screensize.Y / m_screensize.Y; + m_desired_height = m_desired_height_fraction * m_screensize.Y; + m_screensize = screensize; + reformatConsole(); + } + + // Animation + u32 now = getTimeMs(); + animate(now - m_animate_time_old); + m_animate_time_old = now; + + // Draw console elements if visible + if (m_height > 0) + { + drawBackground(); + drawText(); + drawPrompt(); + } + + gui::IGUIElement::draw(); +} + +void GUIChatConsole::reformatConsole() +{ + s32 cols = m_screensize.X / m_fontsize.X - 2; // make room for a margin (looks better) + s32 rows = m_desired_height / m_fontsize.Y - 1; // make room for the input prompt + if (cols <= 0 || rows <= 0) + cols = rows = 0; + m_chat_backend->reformat(cols, rows); +} + +void GUIChatConsole::recalculateConsolePosition() +{ + core::rect rect(0, 0, m_screensize.X, m_height); + DesiredRect = rect; + recalculateAbsolutePosition(false); +} + +void GUIChatConsole::animate(u32 msec) +{ + // animate the console height + s32 goal = m_open ? m_desired_height : 0; + + // Set invisible if close animation finished (reset by openConsole) + // This function (animate()) is never called once its visibility becomes false so do not + // actually set visible to false before the inhibited period is over + if (!m_open && m_height == 0 && m_open_inhibited == 0) + IGUIElement::setVisible(false); + + if (m_height != goal) + { + s32 max_change = msec * m_screensize.Y * (m_height_speed / 1000.0); + if (max_change == 0) + max_change = 1; + + if (m_height < goal) + { + // increase height + if (m_height + max_change < goal) + m_height += max_change; + else + m_height = goal; + } + else + { + // decrease height + if (m_height > goal + max_change) + m_height -= max_change; + else + m_height = goal; + } + + recalculateConsolePosition(); + } + + // blink the cursor + if (m_cursor_blink_speed != 0.0) + { + u32 blink_increase = 0x10000 * msec * (m_cursor_blink_speed / 1000.0); + if (blink_increase == 0) + blink_increase = 1; + m_cursor_blink = ((m_cursor_blink + blink_increase) & 0xffff); + } + + // decrease open inhibit counter + if (m_open_inhibited > msec) + m_open_inhibited -= msec; + else + m_open_inhibited = 0; +} + +void GUIChatConsole::drawBackground() +{ + video::IVideoDriver* driver = Environment->getVideoDriver(); + if (m_background != NULL) + { + core::rect sourcerect(0, -m_height, m_screensize.X, 0); + driver->draw2DImage( + m_background, + v2s32(0, 0), + sourcerect, + &AbsoluteClippingRect, + m_background_color, + false); + } + else + { + driver->draw2DRectangle( + m_background_color, + core::rect(0, 0, m_screensize.X, m_height), + &AbsoluteClippingRect); + } +} + +void GUIChatConsole::drawText() +{ + if (m_font == NULL) + return; + + ChatBuffer& buf = m_chat_backend->getConsoleBuffer(); + for (u32 row = 0; row < buf.getRows(); ++row) + { + const ChatFormattedLine& line = buf.getFormattedLine(row); + if (line.fragments.empty()) + continue; + + s32 line_height = m_fontsize.Y; + s32 y = row * line_height + m_height - m_desired_height; + if (y + line_height < 0) + continue; + + for (u32 i = 0; i < line.fragments.size(); ++i) + { + const ChatFormattedFragment& fragment = line.fragments[i]; + s32 x = (fragment.column + 1) * m_fontsize.X; + core::rect destrect( + x, y, x + m_fontsize.X * fragment.text.size(), y + m_fontsize.Y); + + + #if USE_FREETYPE + // Draw colored text if FreeType is enabled + irr::gui::CGUITTFont *tmp = static_cast(m_font); + tmp->draw( + fragment.text, + destrect, + video::SColor(255, 255, 255, 255), + false, + false, + &AbsoluteClippingRect); + #else + // Otherwise use standard text + m_font->draw( + fragment.text.c_str(), + destrect, + video::SColor(255, 255, 255, 255), + false, + false, + &AbsoluteClippingRect); + #endif + } + } +} + +void GUIChatConsole::drawPrompt() +{ + if (m_font == NULL) + return; + + u32 row = m_chat_backend->getConsoleBuffer().getRows(); + s32 line_height = m_fontsize.Y; + s32 y = row * line_height + m_height - m_desired_height; + + ChatPrompt& prompt = m_chat_backend->getPrompt(); + std::wstring prompt_text = prompt.getVisiblePortion(); + + // FIXME Draw string at once, not character by character + // That will only work with the cursor once we have a monospace font + for (u32 i = 0; i < prompt_text.size(); ++i) + { + wchar_t ws[2] = {prompt_text[i], 0}; + s32 x = (1 + i) * m_fontsize.X; + core::rect destrect( + x, y, x + m_fontsize.X, y + m_fontsize.Y); + m_font->draw( + ws, + destrect, + video::SColor(255, 255, 255, 255), + false, + false, + &AbsoluteClippingRect); + } + + // Draw the cursor during on periods + if ((m_cursor_blink & 0x8000) != 0) + { + s32 cursor_pos = prompt.getVisibleCursorPosition(); + if (cursor_pos >= 0) + { + s32 cursor_len = prompt.getCursorLength(); + video::IVideoDriver* driver = Environment->getVideoDriver(); + s32 x = (1 + cursor_pos) * m_fontsize.X; + core::rect destrect( + x, + y + m_fontsize.Y * (1.0 - m_cursor_height), + x + m_fontsize.X * MYMAX(cursor_len, 1), + y + m_fontsize.Y * (cursor_len ? m_cursor_height+1 : 1) + ); + video::SColor cursor_color(255,255,255,255); + driver->draw2DRectangle( + cursor_color, + destrect, + &AbsoluteClippingRect); + } + } + +} + +bool GUIChatConsole::OnEvent(const SEvent& event) +{ + + ChatPrompt &prompt = m_chat_backend->getPrompt(); + + if(event.EventType == EET_KEY_INPUT_EVENT && event.KeyInput.PressedDown) + { + // Key input + if(KeyPress(event.KeyInput) == getKeySetting("keymap_console")) + { + closeConsole(); + + // inhibit open so the_game doesn't reopen immediately + m_open_inhibited = 50; + m_close_on_enter = false; + return true; + } + else if(event.KeyInput.Key == KEY_ESCAPE) + { + closeConsoleAtOnce(); + m_close_on_enter = false; + // inhibit open so the_game doesn't reopen immediately + m_open_inhibited = 1; // so the ESCAPE button doesn't open the "pause menu" + return true; + } + else if(event.KeyInput.Key == KEY_PRIOR) + { + m_chat_backend->scrollPageUp(); + return true; + } + else if(event.KeyInput.Key == KEY_NEXT) + { + m_chat_backend->scrollPageDown(); + return true; + } + else if(event.KeyInput.Key == KEY_RETURN) + { + prompt.addToHistory(prompt.getLine()); + std::wstring text = prompt.replace(L""); + m_client->typeChatMessage(text); + if (m_close_on_enter) { + closeConsoleAtOnce(); + m_close_on_enter = false; + } + return true; + } + else if(event.KeyInput.Key == KEY_UP) + { + // Up pressed + // Move back in history + prompt.historyPrev(); + return true; + } + else if(event.KeyInput.Key == KEY_DOWN) + { + // Down pressed + // Move forward in history + prompt.historyNext(); + return true; + } + else if(event.KeyInput.Key == KEY_LEFT || event.KeyInput.Key == KEY_RIGHT) + { + // Left/right pressed + // Move/select character/word to the left depending on control and shift keys + ChatPrompt::CursorOp op = event.KeyInput.Shift ? + ChatPrompt::CURSOROP_SELECT : + ChatPrompt::CURSOROP_MOVE; + ChatPrompt::CursorOpDir dir = event.KeyInput.Key == KEY_LEFT ? + ChatPrompt::CURSOROP_DIR_LEFT : + ChatPrompt::CURSOROP_DIR_RIGHT; + ChatPrompt::CursorOpScope scope = event.KeyInput.Control ? + ChatPrompt::CURSOROP_SCOPE_WORD : + ChatPrompt::CURSOROP_SCOPE_CHARACTER; + prompt.cursorOperation(op, dir, scope); + return true; + } + else if(event.KeyInput.Key == KEY_HOME) + { + // Home pressed + // move to beginning of line + prompt.cursorOperation( + ChatPrompt::CURSOROP_MOVE, + ChatPrompt::CURSOROP_DIR_LEFT, + ChatPrompt::CURSOROP_SCOPE_LINE); + return true; + } + else if(event.KeyInput.Key == KEY_END) + { + // End pressed + // move to end of line + prompt.cursorOperation( + ChatPrompt::CURSOROP_MOVE, + ChatPrompt::CURSOROP_DIR_RIGHT, + ChatPrompt::CURSOROP_SCOPE_LINE); + return true; + } + else if(event.KeyInput.Key == KEY_BACK) + { + // Backspace or Ctrl-Backspace pressed + // delete character / word to the left + ChatPrompt::CursorOpScope scope = + event.KeyInput.Control ? + ChatPrompt::CURSOROP_SCOPE_WORD : + ChatPrompt::CURSOROP_SCOPE_CHARACTER; + prompt.cursorOperation( + ChatPrompt::CURSOROP_DELETE, + ChatPrompt::CURSOROP_DIR_LEFT, + scope); + return true; + } + else if(event.KeyInput.Key == KEY_DELETE) + { + // Delete or Ctrl-Delete pressed + // delete character / word to the right + ChatPrompt::CursorOpScope scope = + event.KeyInput.Control ? + ChatPrompt::CURSOROP_SCOPE_WORD : + ChatPrompt::CURSOROP_SCOPE_CHARACTER; + prompt.cursorOperation( + ChatPrompt::CURSOROP_DELETE, + ChatPrompt::CURSOROP_DIR_RIGHT, + scope); + return true; + } + else if(event.KeyInput.Key == KEY_KEY_A && event.KeyInput.Control) + { + // Ctrl-A pressed + // Select all text + prompt.cursorOperation( + ChatPrompt::CURSOROP_SELECT, + ChatPrompt::CURSOROP_DIR_LEFT, // Ignored + ChatPrompt::CURSOROP_SCOPE_LINE); + return true; + } + else if(event.KeyInput.Key == KEY_KEY_C && event.KeyInput.Control) + { + // Ctrl-C pressed + // Copy text to clipboard + if (prompt.getCursorLength() <= 0) + return true; + std::wstring wselected = prompt.getSelection(); + std::string selected(wselected.begin(), wselected.end()); + Environment->getOSOperator()->copyToClipboard(selected.c_str()); + return true; + } + else if(event.KeyInput.Key == KEY_KEY_V && event.KeyInput.Control) + { + // Ctrl-V pressed + // paste text from clipboard + if (prompt.getCursorLength() > 0) { + // Delete selected section of text + prompt.cursorOperation( + ChatPrompt::CURSOROP_DELETE, + ChatPrompt::CURSOROP_DIR_LEFT, // Ignored + ChatPrompt::CURSOROP_SCOPE_SELECTION); + } + IOSOperator *os_operator = Environment->getOSOperator(); + const c8 *text = os_operator->getTextFromClipboard(); + if (!text) + return true; + std::basic_string str((const unsigned char*)text); + prompt.input(std::wstring(str.begin(), str.end())); + return true; + } + else if(event.KeyInput.Key == KEY_KEY_X && event.KeyInput.Control) + { + // Ctrl-X pressed + // Cut text to clipboard + if (prompt.getCursorLength() <= 0) + return true; + std::wstring wselected = prompt.getSelection(); + std::string selected(wselected.begin(), wselected.end()); + Environment->getOSOperator()->copyToClipboard(selected.c_str()); + prompt.cursorOperation( + ChatPrompt::CURSOROP_DELETE, + ChatPrompt::CURSOROP_DIR_LEFT, // Ignored + ChatPrompt::CURSOROP_SCOPE_SELECTION); + return true; + } + else if(event.KeyInput.Key == KEY_KEY_U && event.KeyInput.Control) + { + // Ctrl-U pressed + // kill line to left end + prompt.cursorOperation( + ChatPrompt::CURSOROP_DELETE, + ChatPrompt::CURSOROP_DIR_LEFT, + ChatPrompt::CURSOROP_SCOPE_LINE); + return true; + } + else if(event.KeyInput.Key == KEY_KEY_K && event.KeyInput.Control) + { + // Ctrl-K pressed + // kill line to right end + prompt.cursorOperation( + ChatPrompt::CURSOROP_DELETE, + ChatPrompt::CURSOROP_DIR_RIGHT, + ChatPrompt::CURSOROP_SCOPE_LINE); + return true; + } + else if(event.KeyInput.Key == KEY_TAB) + { + // Tab or Shift-Tab pressed + // Nick completion + std::list names = m_client->getConnectedPlayerNames(); + bool backwards = event.KeyInput.Shift; + prompt.nickCompletion(names, backwards); + return true; + } + else if(event.KeyInput.Char != 0 && !event.KeyInput.Control) + { + #if (defined(linux) || defined(__linux)) + wchar_t wc = L'_'; + mbtowc( &wc, (char *) &event.KeyInput.Char, sizeof(event.KeyInput.Char) ); + prompt.input(wc); + #else + prompt.input(event.KeyInput.Char); + #endif + return true; + } + } + else if(event.EventType == EET_MOUSE_INPUT_EVENT) + { + if(event.MouseInput.Event == EMIE_MOUSE_WHEEL) + { + s32 rows = myround(-3.0 * event.MouseInput.Wheel); + m_chat_backend->scroll(rows); + } + } + + return Parent ? Parent->OnEvent(event) : false; +} + +void GUIChatConsole::setVisible(bool visible) +{ + m_open = visible; + IGUIElement::setVisible(visible); + if (!visible) { + m_height = 0; + recalculateConsolePosition(); + } +} + diff --git a/src/guiChatConsole.h b/src/guiChatConsole.h new file mode 100644 index 000000000..3013a1d31 --- /dev/null +++ b/src/guiChatConsole.h @@ -0,0 +1,138 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifndef GUICHATCONSOLE_HEADER +#define GUICHATCONSOLE_HEADER + +#include "irrlichttypes_extrabloated.h" +#include "modalMenu.h" +#include "chat.h" +#include "config.h" + +class Client; + +class GUIChatConsole : public gui::IGUIElement +{ +public: + GUIChatConsole(gui::IGUIEnvironment* env, + gui::IGUIElement* parent, + s32 id, + ChatBackend* backend, + Client* client, + IMenuManager* menumgr); + virtual ~GUIChatConsole(); + + // Open the console (height = desired fraction of screen size) + // This doesn't open immediately but initiates an animation. + // You should call isOpenInhibited() before this. + void openConsole(f32 height); + + bool isOpen() const; + + // Check if the console should not be opened at the moment + // This is to avoid reopening the console immediately after closing + bool isOpenInhibited() const; + // Close the console, equivalent to openConsole(0). + // This doesn't close immediately but initiates an animation. + void closeConsole(); + // Close the console immediately, without animation. + void closeConsoleAtOnce(); + // Set whether to close the console after the user presses enter. + void setCloseOnEnter(bool close) { m_close_on_enter = close; } + + // Return the desired height (fraction of screen size) + // Zero if the console is closed or getting closed + f32 getDesiredHeight() const; + + // Replace actual line when adding the actual to the history (if there is any) + void replaceAndAddToHistory(std::wstring line); + + // Change how the cursor looks + void setCursor( + bool visible, + bool blinking = false, + f32 blink_speed = 1.0, + f32 relative_height = 1.0); + + // Irrlicht draw method + virtual void draw(); + + bool canTakeFocus(gui::IGUIElement* element) { return false; } + + virtual bool OnEvent(const SEvent& event); + + virtual void setVisible(bool visible); + +private: + void reformatConsole(); + void recalculateConsolePosition(); + + // These methods are called by draw + void animate(u32 msec); + void drawBackground(); + void drawText(); + void drawPrompt(); + +private: + ChatBackend* m_chat_backend; + Client* m_client; + IMenuManager* m_menumgr; + + // current screen size + v2u32 m_screensize; + + // used to compute how much time passed since last animate() + u32 m_animate_time_old; + + // should the console be opened or closed? + bool m_open; + // should it close after you press enter? + bool m_close_on_enter; + // current console height [pixels] + s32 m_height; + // desired height [pixels] + f32 m_desired_height; + // desired height [screen height fraction] + f32 m_desired_height_fraction; + // console open/close animation speed [screen height fraction / second] + f32 m_height_speed; + // if nonzero, opening the console is inhibited [milliseconds] + u32 m_open_inhibited; + + // cursor blink frame (16-bit value) + // cursor is off during [0,32767] and on during [32768,65535] + u32 m_cursor_blink; + // cursor blink speed [on/off toggles / second] + f32 m_cursor_blink_speed; + // cursor height [line height] + f32 m_cursor_height; + + // background texture + video::ITexture* m_background; + // background color (including alpha) + video::SColor m_background_color; + + // font + gui::IGUIFont* m_font; + v2u32 m_fontsize; +}; + + +#endif + diff --git a/src/guiEngine.cpp b/src/guiEngine.cpp index ba286a91c..96de7a4f7 100644 --- a/src/guiEngine.cpp +++ b/src/guiEngine.cpp @@ -37,6 +37,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "log.h" #include "fontengine.h" #include "guiscalingfilter.h" +#include "irrlicht_changes/static_text.h" #ifdef __ANDROID__ #include "client/tile.h" @@ -172,15 +173,16 @@ GUIEngine::GUIEngine( irr::IrrlichtDevice* dev, m_sound_manager = &dummySoundManager; //create topleft header - std::wstring t = utf8_to_wide(std::string(PROJECT_NAME_C " ") + + m_toplefttext = utf8_to_wide(std::string(PROJECT_NAME_C " ") + g_version_hash); - core::rect rect(0, 0, g_fontengine->getTextWidth(t), g_fontengine->getTextHeight()); + core::rect rect(0, 0, g_fontengine->getTextWidth(m_toplefttext.c_str()), + g_fontengine->getTextHeight()); rect += v2s32(4, 0); m_irr_toplefttext = - m_device->getGUIEnvironment()->addStaticText(t.c_str(), - rect,false,true,0,-1); + addStaticText(m_device->getGUIEnvironment(), m_toplefttext, + rect, false, true, 0, -1); //create formspecsource m_formspecgui = new FormspecFormSource(""); @@ -578,7 +580,7 @@ void GUIEngine::setTopleftText(std::string append) toset += utf8_to_wide(append); } - m_irr_toplefttext->setText(toset.c_str()); + m_toplefttext = toset; updateTopLeftTextSize(); } @@ -586,15 +588,14 @@ void GUIEngine::setTopleftText(std::string append) /******************************************************************************/ void GUIEngine::updateTopLeftTextSize() { - std::wstring text = m_irr_toplefttext->getText(); - - core::rect rect(0, 0, g_fontengine->getTextWidth(text), g_fontengine->getTextHeight()); - rect += v2s32(4, 0); + core::rect rect(0, 0, g_fontengine->getTextWidth(m_toplefttext.c_str()), + g_fontengine->getTextHeight()); + rect += v2s32(4, 0); m_irr_toplefttext->remove(); m_irr_toplefttext = - m_device->getGUIEnvironment()->addStaticText(text.c_str(), - rect,false,true,0,-1); + addStaticText(m_device->getGUIEnvironment(), m_toplefttext, + rect, false, true, 0, -1); } /******************************************************************************/ diff --git a/src/guiEngine.h b/src/guiEngine.h index d527f7222..fa98a21e4 100644 --- a/src/guiEngine.h +++ b/src/guiEngine.h @@ -28,6 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "guiFormSpecMenu.h" #include "sound.h" #include "client/tile.h" +#include "util/enriched_string.h" /******************************************************************************/ /* Typedefs and macros */ @@ -275,6 +276,8 @@ private: /** pointer to gui element shown at topleft corner */ irr::gui::IGUIStaticText* m_irr_toplefttext; + /** and text that is in it */ + EnrichedString m_toplefttext; /** initialize cloud subsystem */ void cloudInit(); diff --git a/src/guiFormSpecMenu.cpp b/src/guiFormSpecMenu.cpp index 99b1153c1..cf01dc38c 100644 --- a/src/guiFormSpecMenu.cpp +++ b/src/guiFormSpecMenu.cpp @@ -50,6 +50,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/hex.h" #include "util/numeric.h" #include "util/string.h" // for parseColorString() +#include "irrlicht_changes/static_text.h" #include "guiscalingfilter.h" #if USE_FREETYPE && IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 9 @@ -249,37 +250,6 @@ std::vector* GUIFormSpecMenu::getDropDownValues(const std::string & return NULL; } -static std::vector split(const std::string &s, char delim) -{ - std::vector tokens; - - std::string current = ""; - bool last_was_escape = false; - for (unsigned int i = 0; i < s.size(); i++) { - char si = s.c_str()[i]; - if (last_was_escape) { - current += '\\'; - current += si; - last_was_escape = false; - } else { - if (si == delim) { - tokens.push_back(current); - current = ""; - last_was_escape = false; - } else if (si == '\\') { - last_was_escape = true; - } else { - current += si; - last_was_escape = false; - } - } - } - //push last element - tokens.push_back(current); - - return tokens; -} - void GUIFormSpecMenu::parseSize(parserData* data,std::string element) { std::vector parts = split(element,','); @@ -966,7 +936,7 @@ void GUIFormSpecMenu::parsePwdField(parserData* data,std::string element) int font_height = g_fontengine->getTextHeight(); rect.UpperLeftCorner.Y -= font_height; rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + font_height; - Environment->addStaticText(spec.flabel.c_str(), rect, false, true, this, 0); + addStaticText(Environment, spec.flabel.c_str(), rect, false, true, this, 0); } e->setPasswordBox(true,L'*'); @@ -1021,7 +991,7 @@ void GUIFormSpecMenu::parseSimpleField(parserData* data, if (name == "") { // spec field id to 0, this stops submit searching for a value that isn't there - Environment->addStaticText(spec.flabel.c_str(), rect, false, true, this, spec.fid); + addStaticText(Environment, spec.flabel.c_str(), rect, false, true, this, spec.fid); } else { @@ -1056,7 +1026,7 @@ void GUIFormSpecMenu::parseSimpleField(parserData* data, int font_height = g_fontengine->getTextHeight(); rect.UpperLeftCorner.Y -= font_height; rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + font_height; - Environment->addStaticText(spec.flabel.c_str(), rect, false, true, this, 0); + addStaticText(Environment, spec.flabel.c_str(), rect, false, true, this, 0); } } @@ -1117,7 +1087,7 @@ void GUIFormSpecMenu::parseTextArea(parserData* data, if (name == "") { // spec field id to 0, this stops submit searching for a value that isn't there - Environment->addStaticText(spec.flabel.c_str(), rect, false, true, this, spec.fid); + addStaticText(Environment, spec.flabel.c_str(), rect, false, true, this, spec.fid); } else { @@ -1161,7 +1131,7 @@ void GUIFormSpecMenu::parseTextArea(parserData* data, int font_height = g_fontengine->getTextHeight(); rect.UpperLeftCorner.Y -= font_height; rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + font_height; - Environment->addStaticText(spec.flabel.c_str(), rect, false, true, this, 0); + addStaticText(Environment, spec.flabel.c_str(), rect, false, true, this, 0); } } m_fields.push_back(spec); @@ -1230,7 +1200,7 @@ void GUIFormSpecMenu::parseLabel(parserData* data,std::string element) 258+m_fields.size() ); gui::IGUIStaticText *e = - Environment->addStaticText(spec.flabel.c_str(), + addStaticText(Environment, spec.flabel.c_str(), rect, false, false, this, spec.fid); e->setTextAlignment(gui::EGUIA_UPPERLEFT, gui::EGUIA_CENTER); @@ -1284,7 +1254,7 @@ void GUIFormSpecMenu::parseVertLabel(parserData* data,std::string element) 258+m_fields.size() ); gui::IGUIStaticText *t = - Environment->addStaticText(spec.flabel.c_str(), rect, false, false, this, spec.fid); + addStaticText(Environment, spec.flabel.c_str(), rect, false, false, this, spec.fid); t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_CENTER); m_fields.push_back(spec); return; @@ -1910,7 +1880,7 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) { assert(m_tooltip_element == NULL); // Note: parent != this so that the tooltip isn't clipped by the menu rectangle - m_tooltip_element = Environment->addStaticText(L"",core::rect(0,0,110,18)); + m_tooltip_element = addStaticText(Environment, L"",core::rect(0,0,110,18)); m_tooltip_element->enableOverrideColor(true); m_tooltip_element->setBackgroundColor(m_default_tooltip_bgcolor); m_tooltip_element->setDrawBackground(true); @@ -2255,7 +2225,6 @@ void GUIFormSpecMenu::drawList(const ListDrawSpec &s, int phase, std::wstring tooltip_text = L""; if (hovering && !m_selected_item) { tooltip_text = utf8_to_wide(item.getDefinition(m_gamedef->idef()).description); - tooltip_text = unescape_enriched(tooltip_text); } if (tooltip_text != L"") { std::vector tt_rows = str_split(tooltip_text, L'\n'); @@ -2263,7 +2232,7 @@ void GUIFormSpecMenu::drawList(const ListDrawSpec &s, int phase, m_tooltip_element->setOverrideColor(m_default_tooltip_color); m_tooltip_element->setVisible(true); this->bringToFront(m_tooltip_element); - m_tooltip_element->setText(tooltip_text.c_str()); + setStaticText(m_tooltip_element, tooltip_text.c_str()); s32 tooltip_width = m_tooltip_element->getTextWidth() + m_btn_height; #if IRRLICHT_VERSION_MAJOR <= 1 && IRRLICHT_VERSION_MINOR <= 8 && IRRLICHT_VERSION_REVISION < 2 s32 tooltip_height = m_tooltip_element->getTextHeight() * tt_rows.size() + 5; @@ -2535,8 +2504,10 @@ void GUIFormSpecMenu::drawMenu() iter != m_fields.end(); ++iter) { if (iter->fid == id && m_tooltips[iter->fname].tooltip != L"") { if (m_old_tooltip != m_tooltips[iter->fname].tooltip) { + m_tooltip_element->setBackgroundColor(m_tooltips[iter->fname].bgcolor); + m_tooltip_element->setOverrideColor(m_tooltips[iter->fname].color); m_old_tooltip = m_tooltips[iter->fname].tooltip; - m_tooltip_element->setText(m_tooltips[iter->fname].tooltip.c_str()); + setStaticText(m_tooltip_element, m_tooltips[iter->fname].tooltip.c_str()); std::vector tt_rows = str_split(m_tooltips[iter->fname].tooltip, L'\n'); s32 tooltip_width = m_tooltip_element->getTextWidth() + m_btn_height; s32 tooltip_height = m_tooltip_element->getTextHeight() * tt_rows.size() + 5; @@ -2558,8 +2529,6 @@ void GUIFormSpecMenu::drawMenu() core::position2d(tooltip_x, tooltip_y), core::dimension2d(tooltip_width, tooltip_height))); } - m_tooltip_element->setBackgroundColor(m_tooltips[iter->fname].bgcolor); - m_tooltip_element->setOverrideColor(m_tooltips[iter->fname].color); m_tooltip_element->setVisible(true); this->bringToFront(m_tooltip_element); break; @@ -2568,6 +2537,8 @@ void GUIFormSpecMenu::drawMenu() } } + m_tooltip_element->draw(); + /* Draw dragged item stack */ diff --git a/src/guiFormSpecMenu.h b/src/guiFormSpecMenu.h index ef230c81c..4122b1f56 100644 --- a/src/guiFormSpecMenu.h +++ b/src/guiFormSpecMenu.h @@ -30,6 +30,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "guiTable.h" #include "network/networkprotocol.h" #include "util/string.h" +#include "util/enriched_string.h" class IGameDef; class InventoryManager; @@ -202,7 +203,8 @@ class GUIFormSpecMenu : public GUIModalMenu fname(name), fid(id) { - flabel = unescape_enriched(label); + //flabel = unescape_enriched(label); + flabel = label; fdefault = unescape_enriched(default_text); send = false; ftype = f_Unknown; @@ -239,7 +241,8 @@ class GUIFormSpecMenu : public GUIModalMenu bgcolor(a_bgcolor), color(a_color) { - tooltip = unescape_enriched(utf8_to_wide(a_tooltip)); + //tooltip = unescape_enriched(utf8_to_wide(a_tooltip)); + tooltip = utf8_to_wide(a_tooltip); } std::wstring tooltip; irr::video::SColor bgcolor; @@ -256,7 +259,8 @@ class GUIFormSpecMenu : public GUIModalMenu rect(a_rect), parent_button(NULL) { - text = unescape_enriched(a_text); + //text = unescape_enriched(a_text); + text = a_text; } StaticTextSpec(const std::wstring &a_text, const core::rect &a_rect, @@ -264,7 +268,8 @@ class GUIFormSpecMenu : public GUIModalMenu rect(a_rect), parent_button(a_parent_button) { - text = unescape_enriched(a_text); + //text = unescape_enriched(a_text); + text = a_text; } std::wstring text; core::rect rect; diff --git a/src/irrlicht_changes/CMakeLists.txt b/src/irrlicht_changes/CMakeLists.txt new file mode 100644 index 000000000..3a265c99d --- /dev/null +++ b/src/irrlicht_changes/CMakeLists.txt @@ -0,0 +1,7 @@ +if (BUILD_CLIENT) + set(client_irrlicht_changes_SRCS + ${CMAKE_CURRENT_SOURCE_DIR}/static_text.cpp + PARENT_SCOPE + ) +endif() + diff --git a/src/irrlicht_changes/static_text.cpp b/src/irrlicht_changes/static_text.cpp new file mode 100644 index 000000000..703287eb3 --- /dev/null +++ b/src/irrlicht_changes/static_text.cpp @@ -0,0 +1,679 @@ +// Copyright (C) 2002-2012 Nikolaus Gebhardt +// Copyright (C) 2016 Nathanaël Courant: +// Modified the functions to use EnrichedText instead of string. +// This file is part of the "Irrlicht Engine". +// For conditions of distribution and use, see copyright notice in irrlicht.h + +#include "static_text.h" +#ifdef _IRR_COMPILE_WITH_GUI_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if USE_FREETYPE + #include "cguittfont/xCGUITTFont.h" +#endif + +#include "util/string.h" + +namespace irr +{ + +#if USE_FREETYPE + +namespace gui +{ +//! constructor +StaticText::StaticText(const EnrichedString &text, bool border, + IGUIEnvironment* environment, IGUIElement* parent, + s32 id, const core::rect& rectangle, + bool background) +: IGUIStaticText(environment, parent, id, rectangle), + HAlign(EGUIA_UPPERLEFT), VAlign(EGUIA_UPPERLEFT), + Border(border), OverrideColorEnabled(false), OverrideBGColorEnabled(false), WordWrap(false), Background(background), + RestrainTextInside(true), RightToLeft(false), + OverrideColor(video::SColor(101,255,255,255)), BGColor(video::SColor(101,210,210,210)), + OverrideFont(0), LastBreakFont(0) +{ + #ifdef _DEBUG + setDebugName("StaticText"); + #endif + + Text = text.c_str(); + cText = text; + if (environment && environment->getSkin()) + { + BGColor = environment->getSkin()->getColor(gui::EGDC_3D_FACE); + } +} + + +//! destructor +StaticText::~StaticText() +{ + if (OverrideFont) + OverrideFont->drop(); +} + +//! draws the element and its children +void StaticText::draw() +{ + if (!IsVisible) + return; + + IGUISkin* skin = Environment->getSkin(); + if (!skin) + return; + video::IVideoDriver* driver = Environment->getVideoDriver(); + + core::rect frameRect(AbsoluteRect); + + // draw background + + if (Background) + { + if ( !OverrideBGColorEnabled ) // skin-colors can change + BGColor = skin->getColor(gui::EGDC_3D_FACE); + + driver->draw2DRectangle(BGColor, frameRect, &AbsoluteClippingRect); + } + + // draw the border + + if (Border) + { + skin->draw3DSunkenPane(this, 0, true, false, frameRect, &AbsoluteClippingRect); + frameRect.UpperLeftCorner.X += skin->getSize(EGDS_TEXT_DISTANCE_X); + } + + // draw the text + if (cText.size()) + { + IGUIFont* font = getActiveFont(); + + if (font) + { + if (!WordWrap) + { + // TODO: add colors here + if (VAlign == EGUIA_LOWERRIGHT) + { + frameRect.UpperLeftCorner.Y = frameRect.LowerRightCorner.Y - + font->getDimension(L"A").Height - font->getKerningHeight(); + } + if (HAlign == EGUIA_LOWERRIGHT) + { + frameRect.UpperLeftCorner.X = frameRect.LowerRightCorner.X - + font->getDimension(cText.c_str()).Width; + } + + irr::gui::CGUITTFont *tmp = static_cast(font); + tmp->draw(cText, frameRect, + OverrideColorEnabled ? OverrideColor : skin->getColor(isEnabled() ? EGDC_BUTTON_TEXT : EGDC_GRAY_TEXT), + HAlign == EGUIA_CENTER, VAlign == EGUIA_CENTER, (RestrainTextInside ? &AbsoluteClippingRect : NULL)); + } + else + { + if (font != LastBreakFont) + breakText(); + + core::rect r = frameRect; + s32 height = font->getDimension(L"A").Height + font->getKerningHeight(); + s32 totalHeight = height * BrokenText.size(); + if (VAlign == EGUIA_CENTER) + { + r.UpperLeftCorner.Y = r.getCenter().Y - (totalHeight / 2); + } + else if (VAlign == EGUIA_LOWERRIGHT) + { + r.UpperLeftCorner.Y = r.LowerRightCorner.Y - totalHeight; + } + + irr::video::SColor previous_color(255, 255, 255, 255); + for (u32 i=0; igetDimension(BrokenText[i].c_str()).Width; + } + + //std::vector colors; + //std::wstring str; + EnrichedString str = BrokenText[i]; + + //str = colorizeText(BrokenText[i].c_str(), colors, previous_color); + //if (!colors.empty()) + // previous_color = colors[colors.size() - 1]; + + irr::gui::CGUITTFont *tmp = static_cast(font); + tmp->draw(str, r, + previous_color, // FIXME + HAlign == EGUIA_CENTER, false, (RestrainTextInside ? &AbsoluteClippingRect : NULL)); + + r.LowerRightCorner.Y += height; + r.UpperLeftCorner.Y += height; + } + } + } + } + + IGUIElement::draw(); +} + + +//! Sets another skin independent font. +void StaticText::setOverrideFont(IGUIFont* font) +{ + if (OverrideFont == font) + return; + + if (OverrideFont) + OverrideFont->drop(); + + OverrideFont = font; + + if (OverrideFont) + OverrideFont->grab(); + + breakText(); +} + +//! Gets the override font (if any) +IGUIFont * StaticText::getOverrideFont() const +{ + return OverrideFont; +} + +//! Get the font which is used right now for drawing +IGUIFont* StaticText::getActiveFont() const +{ + if ( OverrideFont ) + return OverrideFont; + IGUISkin* skin = Environment->getSkin(); + if (skin) + return skin->getFont(); + return 0; +} + +//! Sets another color for the text. +void StaticText::setOverrideColor(video::SColor color) +{ + OverrideColor = color; + OverrideColorEnabled = true; +} + + +//! Sets another color for the text. +void StaticText::setBackgroundColor(video::SColor color) +{ + BGColor = color; + OverrideBGColorEnabled = true; + Background = true; +} + + +//! Sets whether to draw the background +void StaticText::setDrawBackground(bool draw) +{ + Background = draw; +} + + +//! Gets the background color +video::SColor StaticText::getBackgroundColor() const +{ + return BGColor; +} + + +//! Checks if background drawing is enabled +bool StaticText::isDrawBackgroundEnabled() const +{ + _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX; + return Background; +} + + +//! Sets whether to draw the border +void StaticText::setDrawBorder(bool draw) +{ + Border = draw; +} + + +//! Checks if border drawing is enabled +bool StaticText::isDrawBorderEnabled() const +{ + _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX; + return Border; +} + + +void StaticText::setTextRestrainedInside(bool restrainTextInside) +{ + RestrainTextInside = restrainTextInside; +} + + +bool StaticText::isTextRestrainedInside() const +{ + return RestrainTextInside; +} + + +void StaticText::setTextAlignment(EGUI_ALIGNMENT horizontal, EGUI_ALIGNMENT vertical) +{ + HAlign = horizontal; + VAlign = vertical; +} + + +#if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 7 +const video::SColor& StaticText::getOverrideColor() const +#else +video::SColor StaticText::getOverrideColor() const +#endif +{ + return OverrideColor; +} + + +//! Sets if the static text should use the overide color or the +//! color in the gui skin. +void StaticText::enableOverrideColor(bool enable) +{ + OverrideColorEnabled = enable; +} + + +bool StaticText::isOverrideColorEnabled() const +{ + _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX; + return OverrideColorEnabled; +} + + +//! Enables or disables word wrap for using the static text as +//! multiline text control. +void StaticText::setWordWrap(bool enable) +{ + WordWrap = enable; + breakText(); +} + + +bool StaticText::isWordWrapEnabled() const +{ + _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX; + return WordWrap; +} + + +void StaticText::setRightToLeft(bool rtl) +{ + if (RightToLeft != rtl) + { + RightToLeft = rtl; + breakText(); + } +} + + +bool StaticText::isRightToLeft() const +{ + return RightToLeft; +} + + +//! Breaks the single text line. +void StaticText::breakText() +{ + if (!WordWrap) + return; + + BrokenText.clear(); + + IGUISkin* skin = Environment->getSkin(); + IGUIFont* font = getActiveFont(); + if (!font) + return; + + LastBreakFont = font; + + EnrichedString line; + EnrichedString word; + EnrichedString whitespace; + s32 size = cText.size(); + s32 length = 0; + s32 elWidth = RelativeRect.getWidth(); + if (Border) + elWidth -= 2*skin->getSize(EGDS_TEXT_DISTANCE_X); + wchar_t c; + + //std::vector colors; + + // We have to deal with right-to-left and left-to-right differently + // However, most parts of the following code is the same, it's just + // some order and boundaries which change. + if (!RightToLeft) + { + // regular (left-to-right) + for (s32 i=0; igetDimension(whitespace.c_str()).Width; + //const std::wstring sanitized = removeEscapes(word.c_str()); + const s32 wordlgth = font->getDimension(word.c_str()).Width; + + if (wordlgth > elWidth) + { + // This word is too long to fit in the available space, look for + // the Unicode Soft HYphen (SHY / 00AD) character for a place to + // break the word at + int where = core::stringw(word.c_str()).findFirst( wchar_t(0x00AD) ); + if (where != -1) + { + EnrichedString first = word.substr(0, where); + EnrichedString second = word.substr(where, word.size() - where); + first.addCharNoColor(L'-'); + BrokenText.push_back(line + first); + const s32 secondLength = font->getDimension(second.c_str()).Width; + + length = secondLength; + line = second; + } + else + { + // No soft hyphen found, so there's nothing more we can do + // break to next line + if (length) + BrokenText.push_back(line); + length = wordlgth; + line = word; + } + } + else if (length && (length + wordlgth + whitelgth > elWidth)) + { + // break to next line + BrokenText.push_back(line); + length = wordlgth; + line = word; + } + else + { + // add word to line + line += whitespace; + line += word; + length += whitelgth + wordlgth; + } + + word.clear(); + whitespace.clear(); + } + + if ( isWhitespace && c != 0) + { + whitespace.addChar(cText, i); + } + + // compute line break + if (lineBreak) + { + line += whitespace; + line += word; + BrokenText.push_back(line); + line.clear(); + word.clear(); + whitespace.clear(); + length = 0; + } + } + } + + line += whitespace; + line += word; + BrokenText.push_back(line); + } + else + { + // right-to-left + for (s32 i=size; i>=0; --i) + { + c = cText.getString()[i]; + bool lineBreak = false; + + if (c == L'\r') // Mac or Windows breaks + { + lineBreak = true; + //if ((i>0) && Text[i-1] == L'\n') // Windows breaks + //{ + // Text.erase(i-1); + // --size; + //} + c = '\0'; + } + else if (c == L'\n') // Unix breaks + { + lineBreak = true; + c = '\0'; + } + + if (c==L' ' || c==0 || i==0) + { + if (word.size()) + { + // here comes the next whitespace, look if + // we must break the last word to the next line. + const s32 whitelgth = font->getDimension(whitespace.c_str()).Width; + const s32 wordlgth = font->getDimension(word.c_str()).Width; + + if (length && (length + wordlgth + whitelgth > elWidth)) + { + // break to next line + BrokenText.push_back(line); + length = wordlgth; + line = word; + } + else + { + // add word to line + line = whitespace + line; + line = word + line; + length += whitelgth + wordlgth; + } + + word.clear(); + whitespace.clear(); + } + + if (c != 0) + // whitespace = core::stringw(&c, 1) + whitespace; + whitespace = cText.substr(i, 1) + whitespace; + + // compute line break + if (lineBreak) + { + line = whitespace + line; + line = word + line; + BrokenText.push_back(line); + line.clear(); + word.clear(); + whitespace.clear(); + length = 0; + } + } + else + { + // yippee this is a word.. + //word = core::stringw(&c, 1) + word; + word = cText.substr(i, 1) + word; + } + } + + line = whitespace + line; + line = word + line; + BrokenText.push_back(line); + } +} + + +//! Sets the new caption of this element. +void StaticText::setText(const wchar_t* text) +{ + setText(EnrichedString(text)); +} + +//! Sets the new caption of this element. +void StaticText::setText(const EnrichedString &text) +{ + IGUIElement::setText(text.c_str()); + cText = text; + if (text.hasBackground()) { + setBackgroundColor(text.getBackground()); + } + breakText(); +} + + +void StaticText::updateAbsolutePosition() +{ + IGUIElement::updateAbsolutePosition(); + breakText(); +} + + +//! Returns the height of the text in pixels when it is drawn. +s32 StaticText::getTextHeight() const +{ + IGUIFont* font = getActiveFont(); + if (!font) + return 0; + + s32 height = font->getDimension(L"A").Height + font->getKerningHeight(); + + if (WordWrap) + height *= BrokenText.size(); + + return height; +} + + +s32 StaticText::getTextWidth() const +{ + IGUIFont * font = getActiveFont(); + if(!font) + return 0; + + if(WordWrap) + { + s32 widest = 0; + + for(u32 line = 0; line < BrokenText.size(); ++line) + { + s32 width = font->getDimension(BrokenText[line].c_str()).Width; + + if(width > widest) + widest = width; + } + + return widest; + } + else + { + return font->getDimension(cText.c_str()).Width; + } +} + + +//! Writes attributes of the element. +//! Implement this to expose the attributes of your element for +//! scripting languages, editors, debuggers or xml serialization purposes. +void StaticText::serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options=0) const +{ + IGUIStaticText::serializeAttributes(out,options); + + out->addBool ("Border", Border); + out->addBool ("OverrideColorEnabled",OverrideColorEnabled); + out->addBool ("OverrideBGColorEnabled",OverrideBGColorEnabled); + out->addBool ("WordWrap", WordWrap); + out->addBool ("Background", Background); + out->addBool ("RightToLeft", RightToLeft); + out->addBool ("RestrainTextInside", RestrainTextInside); + out->addColor ("OverrideColor", OverrideColor); + out->addColor ("BGColor", BGColor); + out->addEnum ("HTextAlign", HAlign, GUIAlignmentNames); + out->addEnum ("VTextAlign", VAlign, GUIAlignmentNames); + + // out->addFont ("OverrideFont", OverrideFont); +} + + +//! Reads attributes of the element +void StaticText::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options=0) +{ + IGUIStaticText::deserializeAttributes(in,options); + + Border = in->getAttributeAsBool("Border"); + enableOverrideColor(in->getAttributeAsBool("OverrideColorEnabled")); + OverrideBGColorEnabled = in->getAttributeAsBool("OverrideBGColorEnabled"); + setWordWrap(in->getAttributeAsBool("WordWrap")); + Background = in->getAttributeAsBool("Background"); + RightToLeft = in->getAttributeAsBool("RightToLeft"); + RestrainTextInside = in->getAttributeAsBool("RestrainTextInside"); + OverrideColor = in->getAttributeAsColor("OverrideColor"); + BGColor = in->getAttributeAsColor("BGColor"); + + setTextAlignment( (EGUI_ALIGNMENT) in->getAttributeAsEnumeration("HTextAlign", GUIAlignmentNames), + (EGUI_ALIGNMENT) in->getAttributeAsEnumeration("VTextAlign", GUIAlignmentNames)); + + // OverrideFont = in->getAttributeAsFont("OverrideFont"); +} + +} // end namespace gui + +#endif // USE_FREETYPE + +} // end namespace irr + + +#endif // _IRR_COMPILE_WITH_GUI_ diff --git a/src/irrlicht_changes/static_text.h b/src/irrlicht_changes/static_text.h new file mode 100644 index 000000000..408a12784 --- /dev/null +++ b/src/irrlicht_changes/static_text.h @@ -0,0 +1,268 @@ +// Copyright (C) 2002-2012 Nikolaus Gebhardt +// Copyright (C) 2016 Nathanaël Courant +// Modified this class to work with EnrichedStrings too +// This file is part of the "Irrlicht Engine". +// For conditions of distribution and use, see copyright notice in irrlicht.h + +#ifndef __C_GUI_STATIC_TEXT_H_INCLUDED__ +#define __C_GUI_STATIC_TEXT_H_INCLUDED__ + +#include "IrrCompileConfig.h" +#ifdef _IRR_COMPILE_WITH_GUI_ + +#include "IGUIStaticText.h" +#include "irrArray.h" + +#include "log.h" + +#include + +#include "util/enriched_string.h" +#include "config.h" +#include + +#if USE_FREETYPE + +namespace irr +{ + +namespace gui +{ + + const EGUI_ELEMENT_TYPE EGUIET_ENRICHED_STATIC_TEXT = (EGUI_ELEMENT_TYPE)(0x1000); + + class StaticText : public IGUIStaticText + { + public: + + //! constructor + StaticText(const EnrichedString &text, bool border, IGUIEnvironment* environment, + IGUIElement* parent, s32 id, const core::rect& rectangle, + bool background = false); + + //! destructor + virtual ~StaticText(); + + //! draws the element and its children + virtual void draw(); + + //! Sets another skin independent font. + virtual void setOverrideFont(IGUIFont* font=0); + + //! Gets the override font (if any) + virtual IGUIFont* getOverrideFont() const; + + //! Get the font which is used right now for drawing + virtual IGUIFont* getActiveFont() const; + + //! Sets another color for the text. + virtual void setOverrideColor(video::SColor color); + + //! Sets another color for the background. + virtual void setBackgroundColor(video::SColor color); + + //! Sets whether to draw the background + virtual void setDrawBackground(bool draw); + + //! Gets the background color + virtual video::SColor getBackgroundColor() const; + + //! Checks if background drawing is enabled + virtual bool isDrawBackgroundEnabled() const; + + //! Sets whether to draw the border + virtual void setDrawBorder(bool draw); + + //! Checks if border drawing is enabled + virtual bool isDrawBorderEnabled() const; + + //! Sets alignment mode for text + virtual void setTextAlignment(EGUI_ALIGNMENT horizontal, EGUI_ALIGNMENT vertical); + + //! Gets the override color + #if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 7 + virtual const video::SColor& getOverrideColor() const; + #else + virtual video::SColor getOverrideColor() const; + #endif + + //! Sets if the static text should use the overide color or the + //! color in the gui skin. + virtual void enableOverrideColor(bool enable); + + //! Checks if an override color is enabled + virtual bool isOverrideColorEnabled() const; + + //! Set whether the text in this label should be clipped if it goes outside bounds + virtual void setTextRestrainedInside(bool restrainedInside); + + //! Checks if the text in this label should be clipped if it goes outside bounds + virtual bool isTextRestrainedInside() const; + + //! Enables or disables word wrap for using the static text as + //! multiline text control. + virtual void setWordWrap(bool enable); + + //! Checks if word wrap is enabled + virtual bool isWordWrapEnabled() const; + + //! Sets the new caption of this element. + virtual void setText(const wchar_t* text); + + //! Returns the height of the text in pixels when it is drawn. + virtual s32 getTextHeight() const; + + //! Returns the width of the current text, in the current font + virtual s32 getTextWidth() const; + + //! Updates the absolute position, splits text if word wrap is enabled + virtual void updateAbsolutePosition(); + + //! Set whether the string should be interpreted as right-to-left (RTL) text + /** \note This component does not implement the Unicode bidi standard, the + text of the component should be already RTL if you call this. The + main difference when RTL is enabled is that the linebreaks for multiline + elements are performed starting from the end. + */ + virtual void setRightToLeft(bool rtl); + + //! Checks if the text should be interpreted as right-to-left text + virtual bool isRightToLeft() const; + + //! Writes attributes of the element. + virtual void serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options) const; + + //! Reads attributes of the element + virtual void deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options); + + virtual bool hasType(EGUI_ELEMENT_TYPE t) const { + return (t == EGUIET_ENRICHED_STATIC_TEXT) || (t == EGUIET_STATIC_TEXT); + }; + + virtual bool hasType(EGUI_ELEMENT_TYPE t) { + return (t == EGUIET_ENRICHED_STATIC_TEXT) || (t == EGUIET_STATIC_TEXT); + }; + + void setText(const EnrichedString &text); + + private: + + //! Breaks the single text line. + void breakText(); + + EGUI_ALIGNMENT HAlign, VAlign; + bool Border; + bool OverrideColorEnabled; + bool OverrideBGColorEnabled; + bool WordWrap; + bool Background; + bool RestrainTextInside; + bool RightToLeft; + + video::SColor OverrideColor, BGColor; + gui::IGUIFont* OverrideFont; + gui::IGUIFont* LastBreakFont; // stored because: if skin changes, line break must be recalculated. + + EnrichedString cText; + core::array< EnrichedString > BrokenText; + }; + + +} // end namespace gui + +} // end namespace irr + +inline irr::gui::IGUIStaticText *addStaticText( + irr::gui::IGUIEnvironment *guienv, + const EnrichedString &text, + const core::rect< s32 > &rectangle, + bool border = false, + bool wordWrap = true, + irr::gui::IGUIElement *parent = NULL, + s32 id = -1, + bool fillBackground = false) +{ + if (parent == NULL) { + // parent is NULL, so we must find one, or we need not to drop + // result, but then there will be a memory leak. + // + // What Irrlicht does is to use guienv as a parent, but the problem + // is that guienv is here only an IGUIEnvironment, while it is a + // CGUIEnvironment in Irrlicht, which inherits from both IGUIElement + // and IGUIEnvironment. + // + // A solution would be to dynamic_cast guienv to a + // IGUIElement*, but Irrlicht is shipped without rtti support + // in some distributions, causing the dymanic_cast to segfault. + // + // Thus, to find the parent, we create a dummy StaticText and ask + // for its parent, and then remove it. + irr::gui::IGUIStaticText *dummy_text = + guienv->addStaticText(L"", rectangle, border, wordWrap, + parent, id, fillBackground); + parent = dummy_text->getParent(); + dummy_text->remove(); + } + irr::gui::IGUIStaticText *result = new irr::gui::StaticText( + text, border, guienv, parent, + id, rectangle, fillBackground); + + result->setWordWrap(wordWrap); + result->drop(); + return result; +} + +inline void setStaticText(irr::gui::IGUIStaticText *static_text, const EnrichedString &text) +{ + // dynamic_cast not possible due to some distributions shipped + // without rtti support in irrlicht + if (static_text->hasType(irr::gui::EGUIET_ENRICHED_STATIC_TEXT)) { + irr::gui::StaticText* stext = static_cast(static_text); + stext->setText(text); + } else { + static_text->setText(text.c_str()); + } +} + +#else // USE_FREETYPE + +inline irr::gui::IGUIStaticText *addStaticText( + irr::gui::IGUIEnvironment *guienv, + const EnrichedString &text, + const core::rect< s32 > &rectangle, + bool border = false, + bool wordWrap = true, + irr::gui::IGUIElement *parent = NULL, + s32 id = -1, + bool fillBackground = false) +{ + return guienv->addStaticText(text.c_str(), rectangle, border, wordWrap, parent, id, fillBackground); +} + +inline void setStaticText(irr::gui::IGUIStaticText *static_text, const EnrichedString &text) +{ + static_text->setText(text.c_str()); +} + +#endif + +inline irr::gui::IGUIStaticText *addStaticText( + irr::gui::IGUIEnvironment *guienv, + const wchar_t *text, + const core::rect< s32 > &rectangle, + bool border = false, + bool wordWrap = true, + irr::gui::IGUIElement *parent = NULL, + s32 id = -1, + bool fillBackground = false) { + return addStaticText(guienv, EnrichedString(text), rectangle, border, wordWrap, parent, id, fillBackground); +} + +inline void setStaticText(irr::gui::IGUIStaticText *static_text, const wchar_t *text) +{ + setStaticText(static_text, EnrichedString(text)); +} + +#endif // _IRR_COMPILE_WITH_GUI_ + +#endif // C_GUI_STATIC_TEXT_H_INCLUDED diff --git a/src/terminal_chat_console.cpp b/src/terminal_chat_console.cpp index c86a960fa..a8c4ebaef 100644 --- a/src/terminal_chat_console.cpp +++ b/src/terminal_chat_console.cpp @@ -345,9 +345,11 @@ void TerminalChatConsole::step(int ch) if (p.first > m_log_level) continue; - m_chat_backend.addMessage( - utf8_to_wide(Logger::getLevelLabel(p.first)), - utf8_to_wide(p.second)); + std::wstring error_message = utf8_to_wide(Logger::getLevelLabel(p.first)); + if (!g_settings->getBool("disable_escape_sequences")) { + error_message = L"\x1b(c@red)" + error_message + L"\x1b(c@white)"; + } + m_chat_backend.addMessage(error_message, utf8_to_wide(p.second)); } // handle input @@ -438,7 +440,7 @@ void TerminalChatConsole::draw_text() continue; for (u32 i = 0; i < line.fragments.size(); ++i) { const ChatFormattedFragment& fragment = line.fragments[i]; - addstr(wide_to_utf8(fragment.text).c_str()); + addstr(wide_to_utf8(fragment.text.getString()).c_str()); } } } diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index e028a0435..f571ab22c 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -1,17 +1,9 @@ -if(USE_FREETYPE) - set(UTIL_FREETYPEDEP_SRCS - ${CMAKE_CURRENT_SOURCE_DIR}/statictext.cpp - ) -else() - set(UTIL_FREETYPEDEP_SRCS ) -endif(USE_FREETYPE) - set(UTIL_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/areastore.cpp ${CMAKE_CURRENT_SOURCE_DIR}/auth.cpp ${CMAKE_CURRENT_SOURCE_DIR}/base64.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/coloredstring.cpp ${CMAKE_CURRENT_SOURCE_DIR}/directiontables.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/enriched_string.cpp ${CMAKE_CURRENT_SOURCE_DIR}/numeric.cpp ${CMAKE_CURRENT_SOURCE_DIR}/pointedthing.cpp ${CMAKE_CURRENT_SOURCE_DIR}/serialize.cpp @@ -20,6 +12,5 @@ set(UTIL_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/string.cpp ${CMAKE_CURRENT_SOURCE_DIR}/srp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/timetaker.cpp - ${UTIL_FREETYPEDEP_SRCS} PARENT_SCOPE) diff --git a/src/util/coloredstring.cpp b/src/util/coloredstring.cpp deleted file mode 100644 index 7db586550..000000000 --- a/src/util/coloredstring.cpp +++ /dev/null @@ -1,68 +0,0 @@ -/* -Copyright (C) 2013 xyz, Ilya Zhuravlev - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#include "coloredstring.h" -#include "util/string.h" - -ColoredString::ColoredString() -{} - -ColoredString::ColoredString(const std::wstring &string, const std::vector &colors): - m_string(string), - m_colors(colors) -{} - -ColoredString::ColoredString(const std::wstring &s) { - m_string = colorizeText(s, m_colors, SColor(255, 255, 255, 255)); -} - -void ColoredString::operator=(const wchar_t *str) { - m_string = colorizeText(str, m_colors, SColor(255, 255, 255, 255)); -} - -size_t ColoredString::size() const { - return m_string.size(); -} - -ColoredString ColoredString::substr(size_t pos, size_t len) const { - if (pos == m_string.length()) - return ColoredString(); - if (len == std::string::npos || pos + len > m_string.length()) { - return ColoredString( - m_string.substr(pos, std::string::npos), - std::vector(m_colors.begin() + pos, m_colors.end()) - ); - } else { - return ColoredString( - m_string.substr(pos, len), - std::vector(m_colors.begin() + pos, m_colors.begin() + pos + len) - ); - } -} - -const wchar_t *ColoredString::c_str() const { - return m_string.c_str(); -} - -const std::vector &ColoredString::getColors() const { - return m_colors; -} - -const std::wstring &ColoredString::getString() const { - return m_string; -} diff --git a/src/util/coloredstring.h b/src/util/coloredstring.h deleted file mode 100644 index a6d98db30..000000000 --- a/src/util/coloredstring.h +++ /dev/null @@ -1,44 +0,0 @@ -/* -Copyright (C) 2013 xyz, Ilya Zhuravlev - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#ifndef COLOREDSTRING_HEADER -#define COLOREDSTRING_HEADER - -#include -#include -#include - -using namespace irr::video; - -class ColoredString { -public: - ColoredString(); - ColoredString(const std::wstring &s); - ColoredString(const std::wstring &string, const std::vector &colors); - void operator=(const wchar_t *str); - size_t size() const; - ColoredString substr(size_t pos = 0, size_t len = std::string::npos) const; - const wchar_t *c_str() const; - const std::vector &getColors() const; - const std::wstring &getString() const; -private: - std::wstring m_string; - std::vector m_colors; -}; - -#endif diff --git a/src/util/enriched_string.cpp b/src/util/enriched_string.cpp new file mode 100644 index 000000000..a7fc3a828 --- /dev/null +++ b/src/util/enriched_string.cpp @@ -0,0 +1,166 @@ +/* +Copyright (C) 2013 xyz, Ilya Zhuravlev +Copyright (C) 2016 Nore, Nathanaël Courant + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "enriched_string.h" +#include "util/string.h" +#include "log.h" +using namespace irr::video; + +EnrichedString::EnrichedString() +{ + clear(); +} + +EnrichedString::EnrichedString(const std::wstring &string, + const std::vector &colors): + m_string(string), + m_colors(colors), + m_has_background(false) +{} + +EnrichedString::EnrichedString(const std::wstring &s, const SColor &color) +{ + clear(); + addAtEnd(s, color); +} + +EnrichedString::EnrichedString(const wchar_t *str, const SColor &color) +{ + clear(); + addAtEnd(std::wstring(str), color); +} + +void EnrichedString::operator=(const wchar_t *str) +{ + clear(); + addAtEnd(std::wstring(str), SColor(255, 255, 255, 255)); +} + +void EnrichedString::addAtEnd(const std::wstring &s, const SColor &initial_color) +{ + SColor color(initial_color); + size_t i = 0; + while (i < s.length()) { + if (s[i] != L'\x1b') { + m_string += s[i]; + m_colors.push_back(color); + ++i; + continue; + } + ++i; + size_t start_index = i; + size_t length; + if (i == s.length()) { + break; + } + if (s[i] == L'(') { + ++i; + ++start_index; + while (i < s.length() && s[i] != L')') { + if (s[i] == L'\\') { + ++i; + } + ++i; + } + length = i - start_index; + ++i; + } else { + ++i; + length = 1; + } + std::wstring escape_sequence(s, start_index, length); + std::vector parts = split(escape_sequence, L'@'); + if (parts[0] == L"c") { + if (parts.size() < 2) { + continue; + } + parseColorString(wide_to_utf8(parts[1]), color, true); + } else if (parts[0] == L"b") { + if (parts.size() < 2) { + continue; + } + parseColorString(wide_to_utf8(parts[1]), m_background, true); + m_has_background = true; + } + continue; + } +} + +void EnrichedString::addChar(const EnrichedString &source, size_t i) +{ + m_string += source.m_string[i]; + m_colors.push_back(source.m_colors[i]); +} + +void EnrichedString::addCharNoColor(wchar_t c) +{ + m_string += c; + if (m_colors.empty()) { + m_colors.push_back(SColor(255, 255, 255, 255)); + } else { + m_colors.push_back(m_colors[m_colors.size() - 1]); + } +} + +EnrichedString EnrichedString::operator+(const EnrichedString &other) const +{ + std::vector result; + result.insert(result.end(), m_colors.begin(), m_colors.end()); + result.insert(result.end(), other.m_colors.begin(), other.m_colors.end()); + return EnrichedString(m_string + other.m_string, result); +} + +void EnrichedString::operator+=(const EnrichedString &other) +{ + m_string += other.m_string; + m_colors.insert(m_colors.end(), other.m_colors.begin(), other.m_colors.end()); +} + +EnrichedString EnrichedString::substr(size_t pos, size_t len) const +{ + if (pos == m_string.length()) { + return EnrichedString(); + } + if (len == std::string::npos || pos + len > m_string.length()) { + return EnrichedString( + m_string.substr(pos, std::string::npos), + std::vector(m_colors.begin() + pos, m_colors.end()) + ); + } else { + return EnrichedString( + m_string.substr(pos, len), + std::vector(m_colors.begin() + pos, m_colors.begin() + pos + len) + ); + } +} + +const wchar_t *EnrichedString::c_str() const +{ + return m_string.c_str(); +} + +const std::vector &EnrichedString::getColors() const +{ + return m_colors; +} + +const std::wstring &EnrichedString::getString() const +{ + return m_string; +} diff --git a/src/util/enriched_string.h b/src/util/enriched_string.h new file mode 100644 index 000000000..1aca8948a --- /dev/null +++ b/src/util/enriched_string.h @@ -0,0 +1,91 @@ +/* +Copyright (C) 2013 xyz, Ilya Zhuravlev +Copyright (C) 2016 Nore, Nathanaël Courant + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifndef ENRICHEDSTRING_HEADER +#define ENRICHEDSTRING_HEADER + +#include +#include +#include + +class EnrichedString { +public: + EnrichedString(); + EnrichedString(const std::wstring &s, + const irr::video::SColor &color = irr::video::SColor(255, 255, 255, 255)); + EnrichedString(const wchar_t *str, + const irr::video::SColor &color = irr::video::SColor(255, 255, 255, 255)); + EnrichedString(const std::wstring &string, + const std::vector &colors); + void operator=(const wchar_t *str); + void addAtEnd(const std::wstring &s, const irr::video::SColor &color); + + // Adds the character source[i] at the end. + // An EnrichedString should always be able to be copied + // to the end of an existing EnrichedString that way. + void addChar(const EnrichedString &source, size_t i); + + // Adds a single character at the end, without specifying its + // color. The color used will be the one from the last character. + void addCharNoColor(wchar_t c); + + EnrichedString substr(size_t pos = 0, size_t len = std::string::npos) const; + EnrichedString operator+(const EnrichedString &other) const; + void operator+=(const EnrichedString &other); + const wchar_t *c_str() const; + const std::vector &getColors() const; + const std::wstring &getString() const; + inline bool operator==(const EnrichedString &other) const + { + return (m_string == other.m_string && m_colors == other.m_colors); + } + inline bool operator!=(const EnrichedString &other) const + { + return !(*this == other); + } + inline void clear() + { + m_string.clear(); + m_colors.clear(); + m_has_background = false; + } + inline bool empty() const + { + return m_string.empty(); + } + inline size_t size() const + { + return m_string.size(); + } + inline bool hasBackground() const + { + return m_has_background; + } + inline irr::video::SColor getBackground() const + { + return m_background; + } +private: + std::wstring m_string; + std::vector m_colors; + bool m_has_background; + irr::video::SColor m_background; +}; + +#endif diff --git a/src/util/statictext.cpp b/src/util/statictext.cpp deleted file mode 100644 index b534b560e..000000000 --- a/src/util/statictext.cpp +++ /dev/null @@ -1,654 +0,0 @@ -// Copyright (C) 2002-2012 Nikolaus Gebhardt -// This file is part of the "Irrlicht Engine". -// For conditions of distribution and use, see copyright notice in irrlicht.h - -#include "statictext.h" -#ifdef _IRR_COMPILE_WITH_GUI_ - -//Only compile this if freetype is enabled. - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "cguittfont/xCGUITTFont.h" -#include "util/string.h" - -namespace irr -{ -namespace gui -{ -//! constructor -StaticText::StaticText(const wchar_t* text, bool border, - IGUIEnvironment* environment, IGUIElement* parent, - s32 id, const core::rect& rectangle, - bool background) -: IGUIStaticText(environment, parent, id, rectangle), - HAlign(EGUIA_UPPERLEFT), VAlign(EGUIA_UPPERLEFT), - Border(border), OverrideColorEnabled(false), OverrideBGColorEnabled(false), WordWrap(false), Background(background), - RestrainTextInside(true), RightToLeft(false), - OverrideColor(video::SColor(101,255,255,255)), BGColor(video::SColor(101,210,210,210)), - OverrideFont(0), LastBreakFont(0) -{ - #ifdef _DEBUG - setDebugName("StaticText"); - #endif - - Text = text; - if (environment && environment->getSkin()) - { - BGColor = environment->getSkin()->getColor(gui::EGDC_3D_FACE); - } -} - - -//! destructor -StaticText::~StaticText() -{ - if (OverrideFont) - OverrideFont->drop(); -} - - -//! draws the element and its children -void StaticText::draw() -{ - if (!IsVisible) - return; - - IGUISkin* skin = Environment->getSkin(); - if (!skin) - return; - video::IVideoDriver* driver = Environment->getVideoDriver(); - - core::rect frameRect(AbsoluteRect); - - // draw background - - if (Background) - { - if ( !OverrideBGColorEnabled ) // skin-colors can change - BGColor = skin->getColor(gui::EGDC_3D_FACE); - - driver->draw2DRectangle(BGColor, frameRect, &AbsoluteClippingRect); - } - - // draw the border - - if (Border) - { - skin->draw3DSunkenPane(this, 0, true, false, frameRect, &AbsoluteClippingRect); - frameRect.UpperLeftCorner.X += skin->getSize(EGDS_TEXT_DISTANCE_X); - } - - // draw the text - if (Text.size()) - { - IGUIFont* font = getActiveFont(); - - if (font) - { - if (!WordWrap) - { - // TODO: add colors here - if (VAlign == EGUIA_LOWERRIGHT) - { - frameRect.UpperLeftCorner.Y = frameRect.LowerRightCorner.Y - - font->getDimension(L"A").Height - font->getKerningHeight(); - } - if (HAlign == EGUIA_LOWERRIGHT) - { - frameRect.UpperLeftCorner.X = frameRect.LowerRightCorner.X - - font->getDimension(Text.c_str()).Width; - } - - font->draw(Text.c_str(), frameRect, - OverrideColorEnabled ? OverrideColor : skin->getColor(isEnabled() ? EGDC_BUTTON_TEXT : EGDC_GRAY_TEXT), - HAlign == EGUIA_CENTER, VAlign == EGUIA_CENTER, (RestrainTextInside ? &AbsoluteClippingRect : NULL)); - } - else - { - if (font != LastBreakFont) - breakText(); - - core::rect r = frameRect; - s32 height = font->getDimension(L"A").Height + font->getKerningHeight(); - s32 totalHeight = height * BrokenText.size(); - if (VAlign == EGUIA_CENTER) - { - r.UpperLeftCorner.Y = r.getCenter().Y - (totalHeight / 2); - } - else if (VAlign == EGUIA_LOWERRIGHT) - { - r.UpperLeftCorner.Y = r.LowerRightCorner.Y - totalHeight; - } - - irr::video::SColor previous_color(255, 255, 255, 255); - for (u32 i=0; igetDimension(BrokenText[i].c_str()).Width; - } - - std::vector colors; - std::wstring str; - - str = colorizeText(BrokenText[i].c_str(), colors, previous_color); - if (!colors.empty()) - previous_color = colors[colors.size() - 1]; - - irr::gui::CGUITTFont *tmp = static_cast(font); - tmp->draw(str.c_str(), r, - colors, - HAlign == EGUIA_CENTER, false, (RestrainTextInside ? &AbsoluteClippingRect : NULL)); - - r.LowerRightCorner.Y += height; - r.UpperLeftCorner.Y += height; - } - } - } - } - - IGUIElement::draw(); -} - - -//! Sets another skin independent font. -void StaticText::setOverrideFont(IGUIFont* font) -{ - if (OverrideFont == font) - return; - - if (OverrideFont) - OverrideFont->drop(); - - OverrideFont = font; - - if (OverrideFont) - OverrideFont->grab(); - - breakText(); -} - -//! Gets the override font (if any) -IGUIFont * StaticText::getOverrideFont() const -{ - return OverrideFont; -} - -//! Get the font which is used right now for drawing -IGUIFont* StaticText::getActiveFont() const -{ - if ( OverrideFont ) - return OverrideFont; - IGUISkin* skin = Environment->getSkin(); - if (skin) - return skin->getFont(); - return 0; -} - -//! Sets another color for the text. -void StaticText::setOverrideColor(video::SColor color) -{ - OverrideColor = color; - OverrideColorEnabled = true; -} - - -//! Sets another color for the text. -void StaticText::setBackgroundColor(video::SColor color) -{ - BGColor = color; - OverrideBGColorEnabled = true; - Background = true; -} - - -//! Sets whether to draw the background -void StaticText::setDrawBackground(bool draw) -{ - Background = draw; -} - - -//! Gets the background color -video::SColor StaticText::getBackgroundColor() const -{ - return BGColor; -} - - -//! Checks if background drawing is enabled -bool StaticText::isDrawBackgroundEnabled() const -{ - _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX; - return Background; -} - - -//! Sets whether to draw the border -void StaticText::setDrawBorder(bool draw) -{ - Border = draw; -} - - -//! Checks if border drawing is enabled -bool StaticText::isDrawBorderEnabled() const -{ - _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX; - return Border; -} - - -void StaticText::setTextRestrainedInside(bool restrainTextInside) -{ - RestrainTextInside = restrainTextInside; -} - - -bool StaticText::isTextRestrainedInside() const -{ - return RestrainTextInside; -} - - -void StaticText::setTextAlignment(EGUI_ALIGNMENT horizontal, EGUI_ALIGNMENT vertical) -{ - HAlign = horizontal; - VAlign = vertical; -} - - -#if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 7 -const video::SColor& StaticText::getOverrideColor() const -#else -video::SColor StaticText::getOverrideColor() const -#endif -{ - return OverrideColor; -} - - -//! Sets if the static text should use the overide color or the -//! color in the gui skin. -void StaticText::enableOverrideColor(bool enable) -{ - OverrideColorEnabled = enable; -} - - -bool StaticText::isOverrideColorEnabled() const -{ - _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX; - return OverrideColorEnabled; -} - - -//! Enables or disables word wrap for using the static text as -//! multiline text control. -void StaticText::setWordWrap(bool enable) -{ - WordWrap = enable; - breakText(); -} - - -bool StaticText::isWordWrapEnabled() const -{ - _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX; - return WordWrap; -} - - -void StaticText::setRightToLeft(bool rtl) -{ - if (RightToLeft != rtl) - { - RightToLeft = rtl; - breakText(); - } -} - - -bool StaticText::isRightToLeft() const -{ - return RightToLeft; -} - - -//! Breaks the single text line. -void StaticText::breakText() -{ - if (!WordWrap) - return; - - BrokenText.clear(); - - IGUISkin* skin = Environment->getSkin(); - IGUIFont* font = getActiveFont(); - if (!font) - return; - - LastBreakFont = font; - - core::stringw line; - core::stringw word; - core::stringw whitespace; - s32 size = Text.size(); - s32 length = 0; - s32 elWidth = RelativeRect.getWidth(); - if (Border) - elWidth -= 2*skin->getSize(EGDS_TEXT_DISTANCE_X); - wchar_t c; - - std::vector colors; - - // We have to deal with right-to-left and left-to-right differently - // However, most parts of the following code is the same, it's just - // some order and boundaries which change. - if (!RightToLeft) - { - // regular (left-to-right) - for (s32 i=0; igetDimension(whitespace.c_str()).Width; - const std::wstring sanitized = removeEscapes(word.c_str()); - const s32 wordlgth = font->getDimension(sanitized.c_str()).Width; - - if (wordlgth > elWidth) - { - // This word is too long to fit in the available space, look for - // the Unicode Soft HYphen (SHY / 00AD) character for a place to - // break the word at - int where = word.findFirst( wchar_t(0x00AD) ); - if (where != -1) - { - core::stringw first = word.subString(0, where); - core::stringw second = word.subString(where, word.size() - where); - BrokenText.push_back(line + first + L"-"); - const s32 secondLength = font->getDimension(second.c_str()).Width; - - length = secondLength; - line = second; - } - else - { - // No soft hyphen found, so there's nothing more we can do - // break to next line - if (length) - BrokenText.push_back(line); - length = wordlgth; - line = word; - } - } - else if (length && (length + wordlgth + whitelgth > elWidth)) - { - // break to next line - BrokenText.push_back(line); - length = wordlgth; - line = word; - } - else - { - // add word to line - line += whitespace; - line += word; - length += whitelgth + wordlgth; - } - - word = L""; - whitespace = L""; - } - - if ( isWhitespace ) - { - whitespace += c; - } - - // compute line break - if (lineBreak) - { - line += whitespace; - line += word; - BrokenText.push_back(line); - line = L""; - word = L""; - whitespace = L""; - length = 0; - } - } - } - - line += whitespace; - line += word; - BrokenText.push_back(line); - } - else - { - // right-to-left - for (s32 i=size; i>=0; --i) - { - c = Text[i]; - bool lineBreak = false; - - if (c == L'\r') // Mac or Windows breaks - { - lineBreak = true; - if ((i>0) && Text[i-1] == L'\n') // Windows breaks - { - Text.erase(i-1); - --size; - } - c = '\0'; - } - else if (c == L'\n') // Unix breaks - { - lineBreak = true; - c = '\0'; - } - - if (c==L' ' || c==0 || i==0) - { - if (word.size()) - { - // here comes the next whitespace, look if - // we must break the last word to the next line. - const s32 whitelgth = font->getDimension(whitespace.c_str()).Width; - const s32 wordlgth = font->getDimension(word.c_str()).Width; - - if (length && (length + wordlgth + whitelgth > elWidth)) - { - // break to next line - BrokenText.push_back(line); - length = wordlgth; - line = word; - } - else - { - // add word to line - line = whitespace + line; - line = word + line; - length += whitelgth + wordlgth; - } - - word = L""; - whitespace = L""; - } - - if (c != 0) - whitespace = core::stringw(&c, 1) + whitespace; - - // compute line break - if (lineBreak) - { - line = whitespace + line; - line = word + line; - BrokenText.push_back(line); - line = L""; - word = L""; - whitespace = L""; - length = 0; - } - } - else - { - // yippee this is a word.. - word = core::stringw(&c, 1) + word; - } - } - - line = whitespace + line; - line = word + line; - BrokenText.push_back(line); - } -} - - -//! Sets the new caption of this element. -void StaticText::setText(const wchar_t* text) -{ - IGUIElement::setText(text); - breakText(); -} - - -void StaticText::updateAbsolutePosition() -{ - IGUIElement::updateAbsolutePosition(); - breakText(); -} - - -//! Returns the height of the text in pixels when it is drawn. -s32 StaticText::getTextHeight() const -{ - IGUIFont* font = getActiveFont(); - if (!font) - return 0; - - s32 height = font->getDimension(L"A").Height + font->getKerningHeight(); - - if (WordWrap) - height *= BrokenText.size(); - - return height; -} - - -s32 StaticText::getTextWidth() const -{ - IGUIFont * font = getActiveFont(); - if(!font) - return 0; - - if(WordWrap) - { - s32 widest = 0; - - for(u32 line = 0; line < BrokenText.size(); ++line) - { - s32 width = font->getDimension(BrokenText[line].c_str()).Width; - - if(width > widest) - widest = width; - } - - return widest; - } - else - { - return font->getDimension(Text.c_str()).Width; - } -} - - -//! Writes attributes of the element. -//! Implement this to expose the attributes of your element for -//! scripting languages, editors, debuggers or xml serialization purposes. -void StaticText::serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options=0) const -{ - IGUIStaticText::serializeAttributes(out,options); - - out->addBool ("Border", Border); - out->addBool ("OverrideColorEnabled",OverrideColorEnabled); - out->addBool ("OverrideBGColorEnabled",OverrideBGColorEnabled); - out->addBool ("WordWrap", WordWrap); - out->addBool ("Background", Background); - out->addBool ("RightToLeft", RightToLeft); - out->addBool ("RestrainTextInside", RestrainTextInside); - out->addColor ("OverrideColor", OverrideColor); - out->addColor ("BGColor", BGColor); - out->addEnum ("HTextAlign", HAlign, GUIAlignmentNames); - out->addEnum ("VTextAlign", VAlign, GUIAlignmentNames); - - // out->addFont ("OverrideFont", OverrideFont); -} - - -//! Reads attributes of the element -void StaticText::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options=0) -{ - IGUIStaticText::deserializeAttributes(in,options); - - Border = in->getAttributeAsBool("Border"); - enableOverrideColor(in->getAttributeAsBool("OverrideColorEnabled")); - OverrideBGColorEnabled = in->getAttributeAsBool("OverrideBGColorEnabled"); - setWordWrap(in->getAttributeAsBool("WordWrap")); - Background = in->getAttributeAsBool("Background"); - RightToLeft = in->getAttributeAsBool("RightToLeft"); - RestrainTextInside = in->getAttributeAsBool("RestrainTextInside"); - OverrideColor = in->getAttributeAsColor("OverrideColor"); - BGColor = in->getAttributeAsColor("BGColor"); - - setTextAlignment( (EGUI_ALIGNMENT) in->getAttributeAsEnumeration("HTextAlign", GUIAlignmentNames), - (EGUI_ALIGNMENT) in->getAttributeAsEnumeration("VTextAlign", GUIAlignmentNames)); - - // OverrideFont = in->getAttributeAsFont("OverrideFont"); -} - -} // end namespace gui -} // end namespace irr - - -#endif // _IRR_COMPILE_WITH_GUI_ diff --git a/src/util/statictext.h b/src/util/statictext.h deleted file mode 100644 index 8d2f879e7..000000000 --- a/src/util/statictext.h +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright (C) 2002-2012 Nikolaus Gebhardt -// This file is part of the "Irrlicht Engine". -// For conditions of distribution and use, see copyright notice in irrlicht.h - -#ifndef __C_GUI_STATIC_TEXT_H_INCLUDED__ -#define __C_GUI_STATIC_TEXT_H_INCLUDED__ - -#include "IrrCompileConfig.h" -#ifdef _IRR_COMPILE_WITH_GUI_ - -#include "IGUIStaticText.h" -#include "irrArray.h" - -#include - -namespace irr -{ -namespace gui -{ - class StaticText : public IGUIStaticText - { - public: - - //! constructor - StaticText(const wchar_t* text, bool border, IGUIEnvironment* environment, - IGUIElement* parent, s32 id, const core::rect& rectangle, - bool background = false); - - //! destructor - virtual ~StaticText(); - - //! draws the element and its children - virtual void draw(); - - //! Sets another skin independent font. - virtual void setOverrideFont(IGUIFont* font=0); - - //! Gets the override font (if any) - virtual IGUIFont* getOverrideFont() const; - - //! Get the font which is used right now for drawing - virtual IGUIFont* getActiveFont() const; - - //! Sets another color for the text. - virtual void setOverrideColor(video::SColor color); - - //! Sets another color for the background. - virtual void setBackgroundColor(video::SColor color); - - //! Sets whether to draw the background - virtual void setDrawBackground(bool draw); - - //! Gets the background color - virtual video::SColor getBackgroundColor() const; - - //! Checks if background drawing is enabled - virtual bool isDrawBackgroundEnabled() const; - - //! Sets whether to draw the border - virtual void setDrawBorder(bool draw); - - //! Checks if border drawing is enabled - virtual bool isDrawBorderEnabled() const; - - //! Sets alignment mode for text - virtual void setTextAlignment(EGUI_ALIGNMENT horizontal, EGUI_ALIGNMENT vertical); - - //! Gets the override color - #if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 7 - virtual const video::SColor& getOverrideColor() const; - #else - virtual video::SColor getOverrideColor() const; - #endif - - //! Sets if the static text should use the overide color or the - //! color in the gui skin. - virtual void enableOverrideColor(bool enable); - - //! Checks if an override color is enabled - virtual bool isOverrideColorEnabled() const; - - //! Set whether the text in this label should be clipped if it goes outside bounds - virtual void setTextRestrainedInside(bool restrainedInside); - - //! Checks if the text in this label should be clipped if it goes outside bounds - virtual bool isTextRestrainedInside() const; - - //! Enables or disables word wrap for using the static text as - //! multiline text control. - virtual void setWordWrap(bool enable); - - //! Checks if word wrap is enabled - virtual bool isWordWrapEnabled() const; - - //! Sets the new caption of this element. - virtual void setText(const wchar_t* text); - - //! Returns the height of the text in pixels when it is drawn. - virtual s32 getTextHeight() const; - - //! Returns the width of the current text, in the current font - virtual s32 getTextWidth() const; - - //! Updates the absolute position, splits text if word wrap is enabled - virtual void updateAbsolutePosition(); - - //! Set whether the string should be interpreted as right-to-left (RTL) text - /** \note This component does not implement the Unicode bidi standard, the - text of the component should be already RTL if you call this. The - main difference when RTL is enabled is that the linebreaks for multiline - elements are performed starting from the end. - */ - virtual void setRightToLeft(bool rtl); - - //! Checks if the text should be interpreted as right-to-left text - virtual bool isRightToLeft() const; - - //! Writes attributes of the element. - virtual void serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options) const; - - //! Reads attributes of the element - virtual void deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options); - - private: - - //! Breaks the single text line. - void breakText(); - - EGUI_ALIGNMENT HAlign, VAlign; - bool Border; - bool OverrideColorEnabled; - bool OverrideBGColorEnabled; - bool WordWrap; - bool Background; - bool RestrainTextInside; - bool RightToLeft; - - video::SColor OverrideColor, BGColor; - gui::IGUIFont* OverrideFont; - gui::IGUIFont* LastBreakFont; // stored because: if skin changes, line break must be recalculated. - - core::array< core::stringw > BrokenText; - }; - -} // end namespace gui -} // end namespace irr - -#endif // _IRR_COMPILE_WITH_GUI_ - -#endif // C_GUI_STATIC_TEXT_H_INCLUDED diff --git a/src/util/string.h b/src/util/string.h index 40ef3e4d3..c77c5a6f9 100644 --- a/src/util/string.h +++ b/src/util/string.h @@ -519,6 +519,38 @@ std::basic_string unescape_enriched(const std::basic_string &s) return output; } +template +std::vector > split(const std::basic_string &s, T delim) +{ + std::vector > tokens; + + std::basic_string current; + bool last_was_escape = false; + for (size_t i = 0; i < s.length(); i++) { + T si = s[i]; + if (last_was_escape) { + current += '\\'; + current += si; + last_was_escape = false; + } else { + if (si == delim) { + tokens.push_back(current); + current = std::basic_string(); + last_was_escape = false; + } else if (si == '\\') { + last_was_escape = true; + } else { + current += si; + last_was_escape = false; + } + } + } + //push last element + tokens.push_back(current); + + return tokens; +} + /** * Checks that all characters in \p to_check are a decimal digits. * -- cgit v1.2.3