From fa6b21a15b415cd82dce6896b94a5341b7dd76f0 Mon Sep 17 00:00:00 2001 From: est31 Date: Sat, 14 May 2016 16:25:57 +0200 Subject: Tell irrlicht if we handle a key or not. We can remove the function in MtNativeActivity now as it serves precisely that purpose: to tell irrlicht that we handled the esc key. TODO for later: * Perhaps try to find a more performant container than KeyList --- src/client/inputhandler.h | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) (limited to 'src/client') diff --git a/src/client/inputhandler.h b/src/client/inputhandler.h index 69e4b25fa..73e507fdc 100644 --- a/src/client/inputhandler.h +++ b/src/client/inputhandler.h @@ -42,11 +42,15 @@ public: // Remember whether each key is down or up if (event.EventType == irr::EET_KEY_INPUT_EVENT) { - if (event.KeyInput.PressedDown) { - keyIsDown.set(event.KeyInput); - keyWasDown.set(event.KeyInput); - } else { - keyIsDown.unset(event.KeyInput); + const KeyPress &keyCode = event.KeyInput; + if (keysListenedFor[keyCode]) { + if (event.KeyInput.PressedDown) { + keyIsDown.set(keyCode); + keyWasDown.set(keyCode); + } else { + keyIsDown.unset(keyCode); + } + return true; } } @@ -116,6 +120,15 @@ public: return b; } + void listenForKey(const KeyPress &keyCode) + { + keysListenedFor.set(keyCode); + } + void dontListenForKeys() + { + keysListenedFor.clear(); + } + s32 getMouseWheel() { s32 a = mouse_wheel; @@ -168,6 +181,12 @@ private: KeyList keyIsDown; // Whether a key has been pressed or not KeyList keyWasDown; + // List of keys we listen for + // TODO perhaps the type of this is not really + // performant as KeyList is designed for few but + // often changing keys, and keysListenedFor is expected + // to change seldomly but contain lots of keys. + KeyList keysListenedFor; }; @@ -192,6 +211,14 @@ public: { return m_receiver->WasKeyDown(keyCode); } + virtual void listenForKey(const KeyPress &keyCode) + { + m_receiver->listenForKey(keyCode); + } + virtual void dontListenForKeys() + { + m_receiver->dontListenForKeys(); + } virtual v2s32 getMousePos() { if (m_device->getCursorControl()) { -- cgit v1.2.3 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/ --- builtin/game/chatcommands.lua | 2 +- builtin/game/misc.lua | 17 ++ src/chat.cpp | 29 +- src/chat.h | 6 +- src/client/CMakeLists.txt | 1 + src/client/guiChatConsole.cpp | 664 ++++++++++++++++++++++++++++++++++++++++++ src/client/guiChatConsole.h | 138 +++++++++ src/game.cpp | 14 +- src/guiChatConsole.cpp | 649 ----------------------------------------- src/guiChatConsole.h | 138 --------- src/util/CMakeLists.txt | 10 + src/util/coloredstring.cpp | 68 +++++ src/util/coloredstring.h | 44 +++ src/util/statictext.cpp | 654 +++++++++++++++++++++++++++++++++++++++++ src/util/statictext.h | 150 ++++++++++ 15 files changed, 1777 insertions(+), 807 deletions(-) create mode 100644 src/client/guiChatConsole.cpp create mode 100644 src/client/guiChatConsole.h delete mode 100644 src/guiChatConsole.cpp delete mode 100644 src/guiChatConsole.h create mode 100644 src/util/coloredstring.cpp create mode 100644 src/util/coloredstring.h create mode 100644 src/util/statictext.cpp create mode 100644 src/util/statictext.h (limited to 'src/client') diff --git a/builtin/game/chatcommands.lua b/builtin/game/chatcommands.lua index 3350140ee..2627559a5 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 = "/"..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 de41cfc91..8d5c80216 100644 --- a/builtin/game/misc.lua +++ b/builtin/game/misc.lua @@ -197,3 +197,20 @@ 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 + +function core.colorize(color, message) + return core.get_color_escape_sequence(color) .. message .. core.get_color_escape_sequence("ffffff") +end diff --git a/src/chat.cpp b/src/chat.cpp index cebe31225..958389df5 100644 --- a/src/chat.cpp +++ b/src/chat.cpp @@ -19,6 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "chat.h" #include "debug.h" +#include "config.h" #include "util/strfnd.h" #include #include @@ -251,8 +252,7 @@ u32 ChatBuffer::formatChatLine(const ChatLine& line, u32 cols, u32 hanging_indentation = 0; // Format the sender name and produce fragments - if (!line.name.empty()) - { + if (!line.name.empty()) { temp_frag.text = L"<"; temp_frag.column = 0; //temp_frag.bold = 0; @@ -267,28 +267,28 @@ u32 ChatBuffer::formatChatLine(const ChatLine& line, u32 cols, next_frags.push_back(temp_frag); } + std::wstring name_sanitized = removeEscapes(line.name); + // Choose an indentation level - if (line.name.empty()) - { + if (line.name.empty()) { // Server messages hanging_indentation = 0; } - else if (line.name.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); 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 +326,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 +338,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[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); @@ -686,9 +686,6 @@ ChatBackend::~ChatBackend() void ChatBackend::addMessage(std::wstring name, std::wstring text) { - name = unescape_enriched(name); - text = unescape_enriched(text); - // Note: A message may consist of multiple lines, for example the MOTD. WStrfnd fnd(text); while (!fnd.at_end()) 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), diff --git a/src/client/CMakeLists.txt b/src/client/CMakeLists.txt index a1ec37fe3..bcf114760 100644 --- a/src/client/CMakeLists.txt +++ b/src/client/CMakeLists.txt @@ -1,5 +1,6 @@ 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 new file mode 100644 index 000000000..d8837556a --- /dev/null +++ b/src/client/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.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 new file mode 100644 index 000000000..3013a1d31 --- /dev/null +++ b/src/client/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/game.cpp b/src/game.cpp index c5211a042..71a04aef5 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 "guiChatConsole.h" +#include "client/guiChatConsole.h" #include "guiFormSpecMenu.h" #include "guiKeyChangeMenu.h" #include "guiPasswordChange.h" @@ -59,6 +59,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "minimap.h" #include "mapblock_mesh.h" +#if USE_FREETYPE + #include "util/statictext.h" +#endif + #include "sound.h" #if USE_SOUND @@ -2239,12 +2243,20 @@ bool Game::initGui() 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( 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(); diff --git a/src/guiChatConsole.cpp b/src/guiChatConsole.cpp deleted file mode 100644 index 17a1689c7..000000000 --- a/src/guiChatConsole.cpp +++ /dev/null @@ -1,649 +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); - m_font->draw( - fragment.text.c_str(), - destrect, - video::SColor(255, 255, 255, 255), - false, - false, - &AbsoluteClippingRect); - } - } -} - -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 deleted file mode 100644 index 3013a1d31..000000000 --- a/src/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/util/CMakeLists.txt b/src/util/CMakeLists.txt index 0e7cbad07..e028a0435 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -1,7 +1,16 @@ +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}/numeric.cpp ${CMAKE_CURRENT_SOURCE_DIR}/pointedthing.cpp @@ -11,5 +20,6 @@ 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 new file mode 100644 index 000000000..7db586550 --- /dev/null +++ b/src/util/coloredstring.cpp @@ -0,0 +1,68 @@ +/* +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 new file mode 100644 index 000000000..a6d98db30 --- /dev/null +++ b/src/util/coloredstring.h @@ -0,0 +1,44 @@ +/* +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/statictext.cpp b/src/util/statictext.cpp new file mode 100644 index 000000000..b534b560e --- /dev/null +++ b/src/util/statictext.cpp @@ -0,0 +1,654 @@ +// 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 new file mode 100644 index 000000000..8d2f879e7 --- /dev/null +++ b/src/util/statictext.h @@ -0,0 +1,150 @@ +// 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 -- 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/client') 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 From 1e86c89f3614cf298916149a8f13d44ea671da64 Mon Sep 17 00:00:00 2001 From: est31 Date: Wed, 25 May 2016 09:22:20 +0200 Subject: Input related generalisations * Move key types into own file * Use Generalized input methods in game.cpp --- src/client/keys.h | 79 +++++++++++++++ src/game.cpp | 293 ++++++++++++++++++++++++++---------------------------- src/game.h | 1 + 3 files changed, 223 insertions(+), 150 deletions(-) create mode 100644 src/client/keys.h (limited to 'src/client') diff --git a/src/client/keys.h b/src/client/keys.h new file mode 100644 index 000000000..08b1c4123 --- /dev/null +++ b/src/client/keys.h @@ -0,0 +1,79 @@ +/* +Minetest +Copyright (C) 2016 est31, + +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 KEYS_HEADER +#define KEYS_HEADER + +#include + +class KeyType { +public: + enum T { + // Player movement + FORWARD, + BACKWARD, + LEFT, + RIGHT, + JUMP, + SPECIAL1, + SNEAK, + AUTORUN, + + ESC, + + // Other + DROP, + INVENTORY, + CHAT, + CMD, + CONSOLE, + MINIMAP, + FREEMOVE, + FASTMOVE, + NOCLIP, + CINEMATIC, + SCREENSHOT, + TOGGLE_HUD, + TOGGLE_CHAT, + TOGGLE_FORCE_FOG_OFF, + TOGGLE_UPDATE_CAMERA, + TOGGLE_DEBUG, + TOGGLE_PROFILER, + CAMERA_MODE, + INCREASE_VIEWING_RANGE, + DECREASE_VIEWING_RANGE, + RANGESELECT, + + QUICKTUNE_NEXT, + QUICKTUNE_PREV, + QUICKTUNE_INC, + QUICKTUNE_DEC, + + DEBUG_STACKS, + + // Fake keycode for array size and internal checks + INTERNAL_ENUM_COUNT + + }; +}; + +typedef KeyType::T GameKeyType; + + +#endif diff --git a/src/game.cpp b/src/game.cpp index def202fe5..ac1c0fe6b 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "camera.h" #include "client.h" #include "client/tile.h" // For TextureSource +#include "client/keys.h" #include "clientmap.h" #include "clouds.h" #include "config.h" @@ -1306,114 +1307,76 @@ struct KeyCache { { handler = NULL; populate(); + populate_nonchanging(); } - enum { - // Player movement - KEYMAP_ID_FORWARD, - KEYMAP_ID_BACKWARD, - KEYMAP_ID_LEFT, - KEYMAP_ID_RIGHT, - KEYMAP_ID_JUMP, - KEYMAP_ID_SPECIAL1, - KEYMAP_ID_SNEAK, - KEYMAP_ID_AUTORUN, - - // Other - KEYMAP_ID_DROP, - KEYMAP_ID_INVENTORY, - KEYMAP_ID_CHAT, - KEYMAP_ID_CMD, - KEYMAP_ID_CONSOLE, - KEYMAP_ID_MINIMAP, - KEYMAP_ID_FREEMOVE, - KEYMAP_ID_FASTMOVE, - KEYMAP_ID_NOCLIP, - KEYMAP_ID_CINEMATIC, - KEYMAP_ID_SCREENSHOT, - KEYMAP_ID_TOGGLE_HUD, - KEYMAP_ID_TOGGLE_CHAT, - KEYMAP_ID_TOGGLE_FORCE_FOG_OFF, - KEYMAP_ID_TOGGLE_UPDATE_CAMERA, - KEYMAP_ID_TOGGLE_DEBUG, - KEYMAP_ID_TOGGLE_PROFILER, - KEYMAP_ID_CAMERA_MODE, - KEYMAP_ID_INCREASE_VIEWING_RANGE, - KEYMAP_ID_DECREASE_VIEWING_RANGE, - KEYMAP_ID_RANGESELECT, - - KEYMAP_ID_QUICKTUNE_NEXT, - KEYMAP_ID_QUICKTUNE_PREV, - KEYMAP_ID_QUICKTUNE_INC, - KEYMAP_ID_QUICKTUNE_DEC, - - KEYMAP_ID_DEBUG_STACKS, - - // Fake keycode for array size and internal checks - KEYMAP_INTERNAL_ENUM_COUNT - - - }; - void populate(); - KeyPress key[KEYMAP_INTERNAL_ENUM_COUNT]; + // Keys that are not settings dependent + void populate_nonchanging(); + + KeyPress key[KeyType::INTERNAL_ENUM_COUNT]; InputHandler *handler; }; +void KeyCache::populate_nonchanging() +{ + key[KeyType::ESC] = EscapeKey; +} + void KeyCache::populate() { - key[KEYMAP_ID_FORWARD] = getKeySetting("keymap_forward"); - key[KEYMAP_ID_BACKWARD] = getKeySetting("keymap_backward"); - key[KEYMAP_ID_LEFT] = getKeySetting("keymap_left"); - key[KEYMAP_ID_RIGHT] = getKeySetting("keymap_right"); - key[KEYMAP_ID_JUMP] = getKeySetting("keymap_jump"); - key[KEYMAP_ID_SPECIAL1] = getKeySetting("keymap_special1"); - key[KEYMAP_ID_SNEAK] = getKeySetting("keymap_sneak"); - - key[KEYMAP_ID_AUTORUN] = getKeySetting("keymap_autorun"); - - key[KEYMAP_ID_DROP] = getKeySetting("keymap_drop"); - key[KEYMAP_ID_INVENTORY] = getKeySetting("keymap_inventory"); - key[KEYMAP_ID_CHAT] = getKeySetting("keymap_chat"); - key[KEYMAP_ID_CMD] = getKeySetting("keymap_cmd"); - key[KEYMAP_ID_CONSOLE] = getKeySetting("keymap_console"); - key[KEYMAP_ID_MINIMAP] = getKeySetting("keymap_minimap"); - key[KEYMAP_ID_FREEMOVE] = getKeySetting("keymap_freemove"); - key[KEYMAP_ID_FASTMOVE] = getKeySetting("keymap_fastmove"); - key[KEYMAP_ID_NOCLIP] = getKeySetting("keymap_noclip"); - key[KEYMAP_ID_CINEMATIC] = getKeySetting("keymap_cinematic"); - key[KEYMAP_ID_SCREENSHOT] = getKeySetting("keymap_screenshot"); - key[KEYMAP_ID_TOGGLE_HUD] = getKeySetting("keymap_toggle_hud"); - key[KEYMAP_ID_TOGGLE_CHAT] = getKeySetting("keymap_toggle_chat"); - key[KEYMAP_ID_TOGGLE_FORCE_FOG_OFF] + key[KeyType::FORWARD] = getKeySetting("keymap_forward"); + key[KeyType::BACKWARD] = getKeySetting("keymap_backward"); + key[KeyType::LEFT] = getKeySetting("keymap_left"); + key[KeyType::RIGHT] = getKeySetting("keymap_right"); + key[KeyType::JUMP] = getKeySetting("keymap_jump"); + key[KeyType::SPECIAL1] = getKeySetting("keymap_special1"); + key[KeyType::SNEAK] = getKeySetting("keymap_sneak"); + + key[KeyType::AUTORUN] = getKeySetting("keymap_autorun"); + + key[KeyType::DROP] = getKeySetting("keymap_drop"); + key[KeyType::INVENTORY] = getKeySetting("keymap_inventory"); + key[KeyType::CHAT] = getKeySetting("keymap_chat"); + key[KeyType::CMD] = getKeySetting("keymap_cmd"); + key[KeyType::CONSOLE] = getKeySetting("keymap_console"); + key[KeyType::MINIMAP] = getKeySetting("keymap_minimap"); + key[KeyType::FREEMOVE] = getKeySetting("keymap_freemove"); + key[KeyType::FASTMOVE] = getKeySetting("keymap_fastmove"); + key[KeyType::NOCLIP] = getKeySetting("keymap_noclip"); + key[KeyType::CINEMATIC] = getKeySetting("keymap_cinematic"); + key[KeyType::SCREENSHOT] = getKeySetting("keymap_screenshot"); + key[KeyType::TOGGLE_HUD] = getKeySetting("keymap_toggle_hud"); + key[KeyType::TOGGLE_CHAT] = getKeySetting("keymap_toggle_chat"); + key[KeyType::TOGGLE_FORCE_FOG_OFF] = getKeySetting("keymap_toggle_force_fog_off"); - key[KEYMAP_ID_TOGGLE_UPDATE_CAMERA] + key[KeyType::TOGGLE_UPDATE_CAMERA] = getKeySetting("keymap_toggle_update_camera"); - key[KEYMAP_ID_TOGGLE_DEBUG] + key[KeyType::TOGGLE_DEBUG] = getKeySetting("keymap_toggle_debug"); - key[KEYMAP_ID_TOGGLE_PROFILER] + key[KeyType::TOGGLE_PROFILER] = getKeySetting("keymap_toggle_profiler"); - key[KEYMAP_ID_CAMERA_MODE] + key[KeyType::CAMERA_MODE] = getKeySetting("keymap_camera_mode"); - key[KEYMAP_ID_INCREASE_VIEWING_RANGE] + key[KeyType::INCREASE_VIEWING_RANGE] = getKeySetting("keymap_increase_viewing_range_min"); - key[KEYMAP_ID_DECREASE_VIEWING_RANGE] + key[KeyType::DECREASE_VIEWING_RANGE] = getKeySetting("keymap_decrease_viewing_range_min"); - key[KEYMAP_ID_RANGESELECT] + key[KeyType::RANGESELECT] = getKeySetting("keymap_rangeselect"); - key[KEYMAP_ID_QUICKTUNE_NEXT] = getKeySetting("keymap_quicktune_next"); - key[KEYMAP_ID_QUICKTUNE_PREV] = getKeySetting("keymap_quicktune_prev"); - key[KEYMAP_ID_QUICKTUNE_INC] = getKeySetting("keymap_quicktune_inc"); - key[KEYMAP_ID_QUICKTUNE_DEC] = getKeySetting("keymap_quicktune_dec"); + key[KeyType::QUICKTUNE_NEXT] = getKeySetting("keymap_quicktune_next"); + key[KeyType::QUICKTUNE_PREV] = getKeySetting("keymap_quicktune_prev"); + key[KeyType::QUICKTUNE_INC] = getKeySetting("keymap_quicktune_inc"); + key[KeyType::QUICKTUNE_DEC] = getKeySetting("keymap_quicktune_dec"); - key[KEYMAP_ID_DEBUG_STACKS] = getKeySetting("keymap_print_debug_stacks"); + key[KeyType::DEBUG_STACKS] = getKeySetting("keymap_print_debug_stacks"); if (handler) { // First clear all keys, then re-add the ones we listen for handler->dontListenForKeys(); - for (size_t i = 0; i < KEYMAP_INTERNAL_ENUM_COUNT; i++) { + for (size_t i = 0; i < KeyType::INTERNAL_ENUM_COUNT; i++) { handler->listenForKey(key[i]); } handler->listenForKey(EscapeKey); @@ -1575,9 +1538,10 @@ protected: f32 dtime); void updateStats(RunStats *stats, const FpsControl &draw_times, f32 dtime); + // Input related void processUserInput(VolatileRunFlags *flags, GameRunData *runData, f32 dtime); - void processKeyboardInput(VolatileRunFlags *flags, + void processKeyInput(VolatileRunFlags *flags, float *statustext_time, float *jump_timer, bool *reset_jump_timer, @@ -1647,6 +1611,36 @@ protected: static void settingChangedCallback(const std::string &setting_name, void *data); void readSettings(); + bool getLeftClicked() + { + return input->getLeftClicked(); + } + bool getRightClicked() + { + return input->getRightClicked(); + } + bool isLeftPressed() + { + return input->getLeftState(); + } + bool isRightPressed() + { + return input->getRightState(); + } + bool getLeftReleased() + { + return input->getLeftReleased(); + } + + bool isKeyDown(GameKeyType k) + { + return input->isKeyDown(keycache.key[k]); + } + bool wasKeyDown(GameKeyType k) + { + return input->wasKeyDown(keycache.key[k]); + } + #ifdef __ANDROID__ void handleAndroidChatInput(); #endif @@ -2379,7 +2373,7 @@ bool Game::connectToServer(const std::string &playername, break; } - if (input->wasKeyDown(EscapeKey) || input->wasKeyDown(CancelKey)) { + if (wasKeyDown(KeyType::ESC) || input->wasKeyDown(CancelKey)) { *aborted = true; infostream << "Connect aborted [Escape]" << std::endl; break; @@ -2440,7 +2434,7 @@ bool Game::getServerContent(bool *aborted) return false; } - if (input->wasKeyDown(EscapeKey) || input->wasKeyDown(CancelKey)) { + if (wasKeyDown(KeyType::ESC) || input->wasKeyDown(CancelKey)) { *aborted = true; infostream << "Connect aborted [Escape]" << std::endl; return false; @@ -2702,7 +2696,7 @@ void Game::processUserInput(VolatileRunFlags *flags, if (m_cache_doubletap_jump && runData->jump_timer <= 0.2) runData->jump_timer += dtime; - processKeyboardInput( + processKeyInput( flags, &runData->statustext_time, &runData->jump_timer, @@ -2714,7 +2708,7 @@ void Game::processUserInput(VolatileRunFlags *flags, } -void Game::processKeyboardInput(VolatileRunFlags *flags, +void Game::processKeyInput(VolatileRunFlags *flags, float *statustext_time, float *jump_timer, bool *reset_jump_timer, @@ -2724,66 +2718,66 @@ void Game::processKeyboardInput(VolatileRunFlags *flags, //TimeTaker tt("process kybd input", NULL, PRECISION_NANO); - if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_DROP])) { + if (wasKeyDown(KeyType::DROP)) { dropSelectedItem(); - } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_AUTORUN])) { + } else if (wasKeyDown(KeyType::AUTORUN)) { toggleAutorun(statustext_time); - } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_INVENTORY])) { + } else if (wasKeyDown(KeyType::INVENTORY)) { openInventory(); - } else if (input->wasKeyDown(EscapeKey) || input->wasKeyDown(CancelKey)) { + } else if (wasKeyDown(KeyType::ESC) || input->wasKeyDown(CancelKey)) { if (!gui_chat_console->isOpenInhibited()) { show_pause_menu(¤t_formspec, client, gamedef, texture_src, device, simple_singleplayer_mode); } - } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_CHAT])) { + } else if (wasKeyDown(KeyType::CHAT)) { openConsole(0.2, L""); - } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_CMD])) { + } else if (wasKeyDown(KeyType::CMD)) { openConsole(0.2, L"/"); - } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_CONSOLE])) { + } else if (wasKeyDown(KeyType::CONSOLE)) { openConsole(1); - } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_FREEMOVE])) { + } else if (wasKeyDown(KeyType::FREEMOVE)) { toggleFreeMove(statustext_time); - } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_JUMP])) { + } else if (wasKeyDown(KeyType::JUMP)) { toggleFreeMoveAlt(statustext_time, jump_timer); *reset_jump_timer = true; - } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_FASTMOVE])) { + } else if (wasKeyDown(KeyType::FASTMOVE)) { toggleFast(statustext_time); - } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_NOCLIP])) { + } else if (wasKeyDown(KeyType::NOCLIP)) { toggleNoClip(statustext_time); - } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_CINEMATIC])) { + } else if (wasKeyDown(KeyType::CINEMATIC)) { toggleCinematic(statustext_time); - } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_SCREENSHOT])) { + } else if (wasKeyDown(KeyType::SCREENSHOT)) { client->makeScreenshot(device); - } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_TOGGLE_HUD])) { + } else if (wasKeyDown(KeyType::TOGGLE_HUD)) { toggleHud(statustext_time, &flags->show_hud); - } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_MINIMAP])) { + } else if (wasKeyDown(KeyType::MINIMAP)) { toggleMinimap(statustext_time, &flags->show_minimap, flags->show_hud, - input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_SNEAK])); - } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_TOGGLE_CHAT])) { + isKeyDown(KeyType::SNEAK)); + } else if (wasKeyDown(KeyType::TOGGLE_CHAT)) { toggleChat(statustext_time, &flags->show_chat); - } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_TOGGLE_FORCE_FOG_OFF])) { + } else if (wasKeyDown(KeyType::TOGGLE_FORCE_FOG_OFF)) { toggleFog(statustext_time, &flags->force_fog_off); - } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_TOGGLE_UPDATE_CAMERA])) { + } else if (wasKeyDown(KeyType::TOGGLE_UPDATE_CAMERA)) { toggleUpdateCamera(statustext_time, &flags->disable_camera_update); - } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_TOGGLE_DEBUG])) { + } else if (wasKeyDown(KeyType::TOGGLE_DEBUG)) { toggleDebug(statustext_time, &flags->show_debug, &flags->show_profiler_graph); - } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_TOGGLE_PROFILER])) { + } else if (wasKeyDown(KeyType::TOGGLE_PROFILER)) { toggleProfiler(statustext_time, profiler_current_page, profiler_max_page); - } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_INCREASE_VIEWING_RANGE])) { + } else if (wasKeyDown(KeyType::INCREASE_VIEWING_RANGE)) { increaseViewRange(statustext_time); - } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_DECREASE_VIEWING_RANGE])) { + } else if (wasKeyDown(KeyType::DECREASE_VIEWING_RANGE)) { decreaseViewRange(statustext_time); - } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_RANGESELECT])) { + } else if (wasKeyDown(KeyType::RANGESELECT)) { toggleFullViewRange(statustext_time); - } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_QUICKTUNE_NEXT])) { + } else if (wasKeyDown(KeyType::QUICKTUNE_NEXT)) { quicktune->next(); - } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_QUICKTUNE_PREV])) { + } else if (wasKeyDown(KeyType::QUICKTUNE_PREV)) { quicktune->prev(); - } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_QUICKTUNE_INC])) { + } else if (wasKeyDown(KeyType::QUICKTUNE_INC)) { quicktune->inc(); - } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_QUICKTUNE_DEC])) { + } else if (wasKeyDown(KeyType::QUICKTUNE_DEC)) { quicktune->dec(); - } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_DEBUG_STACKS])) { + } else if (wasKeyDown(KeyType::DEBUG_STACKS)) { // Print debug stacks dstream << "-----------------------------------------" << std::endl; @@ -2793,7 +2787,7 @@ void Game::processKeyboardInput(VolatileRunFlags *flags, debug_stacks_print(); } - if (!input->isKeyDown(getKeySetting("keymap_jump")) && *reset_jump_timer) { + if (!isKeyDown(KeyType::JUMP) && *reset_jump_timer) { *reset_jump_timer = false; *jump_timer = 0.0; } @@ -2807,7 +2801,6 @@ void Game::processKeyboardInput(VolatileRunFlags *flags, } } - void Game::processItemSelection(u16 *new_playeritem) { LocalPlayer *player = client->getEnv().getLocalPlayer(); @@ -3228,13 +3221,13 @@ void Game::updatePlayerControl(const CameraOrientation &cam) //TimeTaker tt("update player control", NULL, PRECISION_NANO); PlayerControl control( - input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_FORWARD]), - input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_BACKWARD]), - input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_LEFT]), - input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_RIGHT]), - input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_JUMP]), - input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_SPECIAL1]), - input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_SNEAK]), + input->isKeyDown(keycache.key[KeyType::FORWARD]), + input->isKeyDown(keycache.key[KeyType::BACKWARD]), + input->isKeyDown(keycache.key[KeyType::LEFT]), + input->isKeyDown(keycache.key[KeyType::RIGHT]), + input->isKeyDown(keycache.key[KeyType::JUMP]), + input->isKeyDown(keycache.key[KeyType::SPECIAL1]), + input->isKeyDown(keycache.key[KeyType::SNEAK]), input->getLeftState(), input->getRightState(), cam.camera_pitch, @@ -3242,13 +3235,13 @@ void Game::updatePlayerControl(const CameraOrientation &cam) ); u32 keypress_bits = - ( (u32)(input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_FORWARD]) & 0x1) << 0) | - ( (u32)(input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_BACKWARD]) & 0x1) << 1) | - ( (u32)(input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_LEFT]) & 0x1) << 2) | - ( (u32)(input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_RIGHT]) & 0x1) << 3) | - ( (u32)(input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_JUMP]) & 0x1) << 4) | - ( (u32)(input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_SPECIAL1]) & 0x1) << 5) | - ( (u32)(input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_SNEAK]) & 0x1) << 6) | + ( (u32)(input->isKeyDown(keycache.key[KeyType::FORWARD]) & 0x1) << 0) | + ( (u32)(input->isKeyDown(keycache.key[KeyType::BACKWARD]) & 0x1) << 1) | + ( (u32)(input->isKeyDown(keycache.key[KeyType::LEFT]) & 0x1) << 2) | + ( (u32)(input->isKeyDown(keycache.key[KeyType::RIGHT]) & 0x1) << 3) | + ( (u32)(input->isKeyDown(keycache.key[KeyType::JUMP]) & 0x1) << 4) | + ( (u32)(input->isKeyDown(keycache.key[KeyType::SPECIAL1]) & 0x1) << 5) | + ( (u32)(input->isKeyDown(keycache.key[KeyType::SNEAK]) & 0x1) << 6) | ( (u32)(input->getLeftState() & 0x1) << 7) | ( (u32)(input->getRightState() & 0x1) << 8 ); @@ -3522,7 +3515,7 @@ void Game::updateCamera(VolatileRunFlags *flags, u32 busy_time, v3s16 old_camera_offset = camera->getOffset(); - if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_CAMERA_MODE])) { + if (wasKeyDown(KeyType::CAMERA_MODE)) { GenericCAO *playercao = player->getCAO(); // If playercao not loaded, don't change camera @@ -3665,7 +3658,7 @@ void Game::processPlayerInteraction(GameRunData *runData, - pointing away from node */ if (runData->digging) { - if (input->getLeftReleased()) { + if (getLeftReleased()) { infostream << "Left button released" << " (stopped digging)" << std::endl; runData->digging = false; @@ -3691,7 +3684,7 @@ void Game::processPlayerInteraction(GameRunData *runData, } } - if (!runData->digging && runData->ldown_for_dig && !input->getLeftState()) { + if (!runData->digging && runData->ldown_for_dig && !isLeftPressed()) { runData->ldown_for_dig = false; } @@ -3699,13 +3692,13 @@ void Game::processPlayerInteraction(GameRunData *runData, soundmaker->m_player_leftpunch_sound.name = ""; - if (input->getRightState()) + if (isRightPressed()) runData->repeat_rightclick_timer += dtime; else runData->repeat_rightclick_timer = 0; - if (playeritem_def.usable && input->getLeftState()) { - if (input->getLeftClicked()) + if (playeritem_def.usable && isLeftPressed()) { + if (getLeftClicked()) client->interact(4, pointed); } else if (pointed.type == POINTEDTHING_NODE) { ToolCapabilities playeritem_toolcap = @@ -3715,16 +3708,16 @@ void Game::processPlayerInteraction(GameRunData *runData, } else if (pointed.type == POINTEDTHING_OBJECT) { handlePointingAtObject(runData, pointed, playeritem, player_position, show_debug); - } else if (input->getLeftState()) { + } else if (isLeftPressed()) { // When button is held down in air, show continuous animation runData->left_punch = true; - } else if (input->getRightClicked()) { + } else if (getRightClicked()) { handlePointingAtNothing(runData, playeritem); } runData->pointed_old = pointed; - if (runData->left_punch || input->getLeftClicked()) + if (runData->left_punch || getLeftClicked()) camera->setDigging(0); // left click animation input->resetLeftClicked(); @@ -3769,19 +3762,19 @@ void Game::handlePointingAtNode(GameRunData *runData, } } - if (runData->nodig_delay_timer <= 0.0 && input->getLeftState() + if (runData->nodig_delay_timer <= 0.0 && isLeftPressed() && client->checkPrivilege("interact")) { handleDigging(runData, pointed, nodepos, playeritem_toolcap, dtime); } - if ((input->getRightClicked() || + if ((getRightClicked() || runData->repeat_rightclick_timer >= m_repeat_right_click_time) && client->checkPrivilege("interact")) { runData->repeat_rightclick_timer = 0; infostream << "Ground right-clicked" << std::endl; if (meta && meta->getString("formspec") != "" && !random_input - && !input->isKeyDown(getKeySetting("keymap_sneak"))) { + && !isKeyDown(KeyType::SNEAK)) { infostream << "Launching custom inventory view" << std::endl; InventoryLocation inventoryloc; @@ -3846,7 +3839,7 @@ void Game::handlePointingAtObject(GameRunData *runData, runData->selected_object->debugInfoText())); } - if (input->getLeftState()) { + if (isLeftPressed()) { bool do_punch = false; bool do_punch_damage = false; @@ -3856,7 +3849,7 @@ void Game::handlePointingAtObject(GameRunData *runData, runData->object_hit_delay_timer = object_hit_delay; } - if (input->getLeftClicked()) + if (getLeftClicked()) do_punch = true; if (do_punch) { @@ -3876,7 +3869,7 @@ void Game::handlePointingAtObject(GameRunData *runData, if (!disable_send) client->interact(0, pointed); } - } else if (input->getRightClicked()) { + } else if (getRightClicked()) { infostream << "Right-clicked object" << std::endl; client->interact(3, pointed); // place } diff --git a/src/game.h b/src/game.h index 5465ecdc6..d8c2f78fc 100644 --- a/src/game.h +++ b/src/game.h @@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrlichttypes_extrabloated.h" #include +#include "client/keys.h" #include "keycode.h" #include -- cgit v1.2.3 From 2060fd9cbe587d7e8ffe0cecdd67925f13a56c05 Mon Sep 17 00:00:00 2001 From: est31 Date: Fri, 27 May 2016 08:35:07 +0200 Subject: Initial Gamepad support Adds initial ingame gamepad support to minetest. Full Formspec support is not implemented yet and can be added by a later change. --- builtin/settingtypes.txt | 11 +++ src/client/CMakeLists.txt | 1 + src/client/clientlauncher.cpp | 20 ++++- src/client/inputhandler.h | 13 +++ src/client/joystick_controller.cpp | 179 +++++++++++++++++++++++++++++++++++++ src/client/joystick_controller.h | 163 +++++++++++++++++++++++++++++++++ src/client/keys.h | 6 ++ src/content_cao.cpp | 4 +- src/defaultsettings.cpp | 3 + src/game.cpp | 165 ++++++++++++++++++++++------------ src/game.h | 3 + src/guiEngine.cpp | 2 + src/guiEngine.h | 3 +- src/guiFormSpecMenu.cpp | 42 +++++++-- src/guiFormSpecMenu.h | 13 ++- src/localplayer.cpp | 17 ++-- src/player.h | 10 ++- 17 files changed, 576 insertions(+), 79 deletions(-) create mode 100644 src/client/joystick_controller.cpp create mode 100644 src/client/joystick_controller.h (limited to 'src/client') diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 538a04f33..b53e35a85 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -104,6 +104,17 @@ random_input (Random input) bool false # Continuous forward movement (only used for testing). continuous_forward (Continuous forward) bool false +# Enable Joysticks +enable_joysticks (Enable Joysticks) bool true + +# The time in seconds it takes between repeated events +# when holding down a joystick button combination. +repeat_joystick_button_time (Joystick button repetition invterval) float 0.17 + +# The sensitivity of the joystick axes for moving the +# ingame view frustum around. +joystick_frustum_sensitivity (Joystick frustum sensitivity) float 170 + # Key for moving the player forward. # See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3 keymap_forward (Forward key) key KEY_KEY_W diff --git a/src/client/CMakeLists.txt b/src/client/CMakeLists.txt index a1ec37fe3..5faa186a7 100644 --- a/src/client/CMakeLists.txt +++ b/src/client/CMakeLists.txt @@ -1,6 +1,7 @@ set(client_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/clientlauncher.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tile.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/joystick_controller.cpp PARENT_SCOPE ) diff --git a/src/client/clientlauncher.cpp b/src/client/clientlauncher.cpp index 404a16310..ee8662ed6 100644 --- a/src/client/clientlauncher.cpp +++ b/src/client/clientlauncher.cpp @@ -32,6 +32,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "guiEngine.h" #include "player.h" #include "fontengine.h" +#include "joystick_controller.h" #include "clientlauncher.h" /* mainmenumanager.h @@ -499,7 +500,8 @@ void ClientLauncher::main_menu(MainMenuData *menudata) #endif /* show main menu */ - GUIEngine mymenu(device, guiroot, &g_menumgr, smgr, menudata, *kill); + GUIEngine mymenu(device, &input->joystick, guiroot, + &g_menumgr, smgr, menudata, *kill); smgr->clear(); /* leave scene manager in a clean state */ } @@ -558,6 +560,22 @@ bool ClientLauncher::create_engine_device() device = createDeviceEx(params); if (device) { + if (g_settings->getBool("enable_joysticks")) { + irr::core::array infos; + std::vector joystick_infos; + // Make sure this is called maximum once per + // irrlicht device, otherwise it will give you + // multiple events for the same joystick. + if (device->activateJoysticks(infos)) { + infostream << "Joystick support enabled" << std::endl; + joystick_infos.reserve(infos.size()); + for (u32 i = 0; i < infos.size(); i++) { + joystick_infos.push_back(infos[i]); + } + } else { + errorstream << "Could not activate joystick support." << std::endl; + } + } porting::initIrrlicht(device); } diff --git a/src/client/inputhandler.h b/src/client/inputhandler.h index 73e507fdc..824b0da2e 100644 --- a/src/client/inputhandler.h +++ b/src/client/inputhandler.h @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #define INPUT_HANDLER_H #include "irrlichttypes_extrabloated.h" +#include "joystick_controller.h" class MyEventReceiver : public IEventReceiver { @@ -62,6 +63,14 @@ public: return true; } #endif + + if (event.EventType == irr::EET_JOYSTICK_INPUT_EVENT) { + /* TODO add a check like: + if (event.JoystickEvent != joystick_we_listen_for) + return false; + */ + return joystick->handleEvent(event.JoystickEvent); + } // handle mouse events if (event.EventType == irr::EET_MOUSE_INPUT_EVENT) { if (noMenuActive() == false) { @@ -172,6 +181,8 @@ public: s32 mouse_wheel; + JoystickController *joystick; + #ifdef HAVE_TOUCHSCREENGUI TouchScreenGUI* m_touchscreengui; #endif @@ -202,6 +213,7 @@ public: m_receiver(receiver), m_mousepos(0,0) { + m_receiver->joystick = &joystick; } virtual bool isKeyDown(const KeyPress &keyCode) { @@ -288,6 +300,7 @@ public: void clear() { + joystick.clear(); m_receiver->clearInput(); } private: diff --git a/src/client/joystick_controller.cpp b/src/client/joystick_controller.cpp new file mode 100644 index 000000000..ef8d18ab0 --- /dev/null +++ b/src/client/joystick_controller.cpp @@ -0,0 +1,179 @@ +/* +Minetest +Copyright (C) 2016 est31, + +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 "joystick_controller.h" +#include "irrlichttypes_extrabloated.h" +#include "keys.h" +#include "settings.h" +#include "gettime.h" + +bool JoystickButtonCmb::isTriggered(const irr::SEvent::SJoystickEvent &ev) const +{ + u32 buttons = ev.ButtonStates; + + buttons &= filter_mask; + return buttons == compare_mask; +} + +bool JoystickAxisCmb::isTriggered(const irr::SEvent::SJoystickEvent &ev) const +{ + s16 ax_val = ev.Axis[axis_to_compare]; + + return (ax_val * direction < 0) && (thresh * direction > ax_val * direction); +} + +// spares many characters +#define JLO_B_PB(A, B, C) jlo.button_keys.push_back(JoystickButtonCmb(A, B, C)) +#define JLO_A_PB(A, B, C, D) jlo.axis_keys.push_back(JoystickAxisCmb(A, B, C, D)) + +static JoystickLayout create_default_layout() +{ + JoystickLayout jlo; + + jlo.axes_dead_border = 1024; + + const JoystickAxisLayout axes[JA_COUNT] = { + {0, 1}, // JA_SIDEWARD_MOVE + {1, 1}, // JA_FORWARD_MOVE + {3, 1}, // JA_FRUSTUM_HORIZONTAL + {4, 1}, // JA_FRUSTUM_VERTICAL + }; + memcpy(jlo.axes, axes, sizeof(jlo.axes)); + + u32 sb = 1 << 7; // START button mask + u32 fb = 1 << 3; // FOUR button mask + u32 bm = sb | fb; // Mask for Both Modifiers + + // The back button means "ESC". + JLO_B_PB(KeyType::ESC, 1 << 6, 1 << 6); + + // The start button counts as modifier as well as use key. + // JLO_B_PB(KeyType::USE, sb, sb)); + + // Accessible without start modifier button pressed + // regardless whether four is pressed or not + JLO_B_PB(KeyType::SNEAK, sb | 1 << 2, 1 << 2); + + // Accessible without four modifier button pressed + // regardless whether start is pressed or not + JLO_B_PB(KeyType::MOUSE_L, fb | 1 << 4, 1 << 4); + JLO_B_PB(KeyType::MOUSE_R, fb | 1 << 5, 1 << 5); + + // Accessible without any modifier pressed + JLO_B_PB(KeyType::JUMP, bm | 1 << 0, 1 << 0); + JLO_B_PB(KeyType::SPECIAL1, bm | 1 << 1, 1 << 1); + + // Accessible with start button not pressed, but four pressed + // TODO find usage for button 0 + JLO_B_PB(KeyType::DROP, bm | 1 << 1, fb | 1 << 1); + JLO_B_PB(KeyType::SCROLL_UP, bm | 1 << 4, fb | 1 << 4); + JLO_B_PB(KeyType::SCROLL_DOWN,bm | 1 << 5, fb | 1 << 5); + + // Accessible with start button and four pressed + // TODO find usage for buttons 0, 1 and 4, 5 + + // Now about the buttons simulated by the axes + + // Movement buttons, important for vessels + JLO_A_PB(KeyType::FORWARD, 1, 1, 1024); + JLO_A_PB(KeyType::BACKWARD, 1, -1, 1024); + JLO_A_PB(KeyType::LEFT, 0, 1, 1024); + JLO_A_PB(KeyType::RIGHT, 0, -1, 1024); + + // Scroll buttons + JLO_A_PB(KeyType::SCROLL_UP, 2, -1, 1024); + JLO_A_PB(KeyType::SCROLL_DOWN, 5, -1, 1024); + + return jlo; +} + +static const JoystickLayout default_layout = create_default_layout(); + +JoystickController::JoystickController() +{ + m_layout = &default_layout; + doubling_dtime = g_settings->getFloat("repeat_joystick_button_time"); + + for (size_t i = 0; i < KeyType::INTERNAL_ENUM_COUNT; i++) { + m_past_pressed_time[i] = 0; + } + clear(); +} + +bool JoystickController::handleEvent(const irr::SEvent::SJoystickEvent &ev) +{ + m_internal_time = getTimeMs() / 1000.f; + + std::bitset keys_pressed; + + // First generate a list of keys pressed + + for (size_t i = 0; i < m_layout->button_keys.size(); i++) { + if (m_layout->button_keys[i].isTriggered(ev)) { + keys_pressed.set(m_layout->button_keys[i].key); + } + } + + for (size_t i = 0; i < m_layout->axis_keys.size(); i++) { + if (m_layout->axis_keys[i].isTriggered(ev)) { + keys_pressed.set(m_layout->axis_keys[i].key); + } + } + + // Then update the values + + for (size_t i = 0; i < KeyType::INTERNAL_ENUM_COUNT; i++) { + if (keys_pressed[i]) { + if (!m_past_pressed_keys[i] && + m_past_pressed_time[i] < m_internal_time - doubling_dtime) { + m_past_pressed_keys[i] = true; + m_past_pressed_time[i] = m_internal_time; + } + } else if (m_pressed_keys[i]) { + m_past_released_keys[i] = true; + } + + m_pressed_keys[i] = keys_pressed[i]; + } + + for (size_t i = 0; i < JA_COUNT; i++) { + const JoystickAxisLayout &ax_la = m_layout->axes[i]; + m_axes_vals[i] = ax_la.invert * ev.Axis[ax_la.axis_id]; + } + + + return true; +} + +void JoystickController::clear() +{ + m_pressed_keys.reset(); + m_past_pressed_keys.reset(); + m_past_released_keys.reset(); + memset(m_axes_vals, 0, sizeof(m_axes_vals)); +} + +s16 JoystickController::getAxisWithoutDead(JoystickAxis axis) +{ + s16 v = m_axes_vals[axis]; + if (((v > 0) && (v < m_layout->axes_dead_border)) || + ((v < 0) && (v > -m_layout->axes_dead_border))) + return 0; + return v; +} diff --git a/src/client/joystick_controller.h b/src/client/joystick_controller.h new file mode 100644 index 000000000..ed0ee4068 --- /dev/null +++ b/src/client/joystick_controller.h @@ -0,0 +1,163 @@ +/* +Minetest +Copyright (C) 2016 est31, + +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 JOYSTICK_HEADER +#define JOYSTICK_HEADER + +#include "irrlichttypes_extrabloated.h" +#include "keys.h" +#include +#include + +enum JoystickAxis { + JA_SIDEWARD_MOVE, + JA_FORWARD_MOVE, + + JA_FRUSTUM_HORIZONTAL, + JA_FRUSTUM_VERTICAL, + + // To know the count of enum values + JA_COUNT, +}; + +struct JoystickAxisLayout { + u16 axis_id; + // -1 if to invert, 1 if to keep it. + int invert; +}; + + +struct JoystickCombination { + + virtual bool isTriggered(const irr::SEvent::SJoystickEvent &ev) const=0; + + GameKeyType key; +}; + +struct JoystickButtonCmb : public JoystickCombination { + + JoystickButtonCmb() {} + JoystickButtonCmb(GameKeyType key, u32 filter_mask, u32 compare_mask) : + filter_mask(filter_mask), + compare_mask(compare_mask) + { + this->key = key; + } + + virtual bool isTriggered(const irr::SEvent::SJoystickEvent &ev) const; + + u32 filter_mask; + u32 compare_mask; +}; + +struct JoystickAxisCmb : public JoystickCombination { + + JoystickAxisCmb() {} + JoystickAxisCmb(GameKeyType key, u16 axis_to_compare, int direction, s16 thresh) : + axis_to_compare(axis_to_compare), + direction(direction), + thresh(thresh) + { + this->key = key; + } + + virtual bool isTriggered(const irr::SEvent::SJoystickEvent &ev) const; + + u16 axis_to_compare; + + // if -1, thresh must be smaller than the axis value in order to trigger + // if 1, thresh must be bigger than the axis value in order to trigger + int direction; + s16 thresh; +}; + +struct JoystickLayout { + std::vector button_keys; + std::vector axis_keys; + JoystickAxisLayout axes[JA_COUNT]; + s16 axes_dead_border; +}; + +class JoystickController { + +public: + JoystickController(); + bool handleEvent(const irr::SEvent::SJoystickEvent &ev); + void clear(); + + bool wasKeyDown(GameKeyType b) + { + bool r = m_past_pressed_keys[b]; + m_past_pressed_keys[b] = false; + return r; + } + bool getWasKeyDown(GameKeyType b) + { + return m_past_pressed_keys[b]; + } + void clearWasKeyDown(GameKeyType b) + { + m_past_pressed_keys[b] = false; + } + + bool wasKeyReleased(GameKeyType b) + { + bool r = m_past_released_keys[b]; + m_past_released_keys[b] = false; + return r; + } + bool getWasKeyReleased(GameKeyType b) + { + return m_past_pressed_keys[b]; + } + void clearWasKeyReleased(GameKeyType b) + { + m_past_pressed_keys[b] = false; + } + + bool isKeyDown(GameKeyType b) + { + return m_pressed_keys[b]; + } + + s16 getAxis(JoystickAxis axis) + { + return m_axes_vals[axis]; + } + + s16 getAxisWithoutDead(JoystickAxis axis); + + f32 doubling_dtime; + +private: + const JoystickLayout *m_layout; + + s16 m_axes_vals[JA_COUNT]; + + std::bitset m_pressed_keys; + + f32 m_internal_time; + + f32 m_past_pressed_time[KeyType::INTERNAL_ENUM_COUNT]; + + std::bitset m_past_pressed_keys; + std::bitset m_past_released_keys; +}; + +#endif diff --git a/src/client/keys.h b/src/client/keys.h index 08b1c4123..0921bc166 100644 --- a/src/client/keys.h +++ b/src/client/keys.h @@ -67,6 +67,12 @@ public: DEBUG_STACKS, + // joystick specific keys + MOUSE_L, + MOUSE_R, + SCROLL_UP, + SCROLL_DOWN, + // Fake keycode for array size and internal checks INTERNAL_ENUM_COUNT diff --git a/src/content_cao.cpp b/src/content_cao.cpp index 35ab1c508..eeb85c8a6 100644 --- a/src/content_cao.cpp +++ b/src/content_cao.cpp @@ -1056,7 +1056,9 @@ void GenericCAO::step(float dtime, ClientEnvironment *env) PlayerControl controls = player->getPlayerControl(); bool walking = false; - if(controls.up || controls.down || controls.left || controls.right) + if (controls.up || controls.down || controls.left || controls.right || + controls.forw_move_joystick_axis != 0.f || + controls.sidew_move_joystick_axis != 0.f) walking = true; f32 new_speed = player->local_animation_speed; diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 632ec0df9..43a7a7c00 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -116,6 +116,9 @@ void set_default_settings(Settings *settings) settings->setDefault("free_move", "false"); settings->setDefault("noclip", "false"); settings->setDefault("continuous_forward", "false"); + settings->setDefault("enable_joysticks", "true"); + settings->setDefault("repeat_joystick_button_time", "0.17"); + settings->setDefault("joystick_frustum_sensitivity", "170"); settings->setDefault("cinematic", "false"); settings->setDefault("camera_smoothing", "0"); settings->setDefault("cinematic_camera_smoothing", "0.7"); diff --git a/src/game.cpp b/src/game.cpp index ac1c0fe6b..ba77d299a 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "client.h" #include "client/tile.h" // For TextureSource #include "client/keys.h" +#include "client/joystick_controller.h" #include "clientmap.h" #include "clouds.h" #include "config.h" @@ -1108,12 +1109,14 @@ bool nodePlacementPrediction(Client &client, static inline void create_formspec_menu(GUIFormSpecMenu **cur_formspec, InventoryManager *invmgr, IGameDef *gamedef, IWritableTextureSource *tsrc, IrrlichtDevice *device, + JoystickController *joystick, IFormSource *fs_src, TextDest *txt_dest, Client *client) { if (*cur_formspec == 0) { - *cur_formspec = new GUIFormSpecMenu(device, guiroot, -1, &g_menumgr, - invmgr, gamedef, tsrc, fs_src, txt_dest, client); + *cur_formspec = new GUIFormSpecMenu(device, joystick, + guiroot, -1, &g_menumgr, invmgr, gamedef, tsrc, + fs_src, txt_dest, client); (*cur_formspec)->doPause = false; /* @@ -1138,7 +1141,8 @@ static inline void create_formspec_menu(GUIFormSpecMenu **cur_formspec, static void show_deathscreen(GUIFormSpecMenu **cur_formspec, InventoryManager *invmgr, IGameDef *gamedef, - IWritableTextureSource *tsrc, IrrlichtDevice *device, Client *client) + IWritableTextureSource *tsrc, IrrlichtDevice *device, + JoystickController *joystick, Client *client) { std::string formspec = std::string(FORMSPEC_VERSION_STRING) + @@ -1154,14 +1158,15 @@ static void show_deathscreen(GUIFormSpecMenu **cur_formspec, FormspecFormSource *fs_src = new FormspecFormSource(formspec); LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", client); - create_formspec_menu(cur_formspec, invmgr, gamedef, tsrc, device, fs_src, txt_dst, NULL); + create_formspec_menu(cur_formspec, invmgr, gamedef, tsrc, device, + joystick, fs_src, txt_dst, NULL); } /******************************************************************************/ static void show_pause_menu(GUIFormSpecMenu **cur_formspec, InventoryManager *invmgr, IGameDef *gamedef, IWritableTextureSource *tsrc, IrrlichtDevice *device, - bool singleplayermode) + JoystickController *joystick, bool singleplayermode) { #ifdef __ANDROID__ std::string control_text = strgettext("Default Controls:\n" @@ -1226,7 +1231,8 @@ static void show_pause_menu(GUIFormSpecMenu **cur_formspec, FormspecFormSource *fs_src = new FormspecFormSource(os.str()); LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU"); - create_formspec_menu(cur_formspec, invmgr, gamedef, tsrc, device, fs_src, txt_dst, NULL); + create_formspec_menu(cur_formspec, invmgr, gamedef, tsrc, device, + joystick, fs_src, txt_dst, NULL); std::string con("btn_continue"); (*cur_formspec)->setFocus(con); (*cur_formspec)->doPause = true; @@ -1574,9 +1580,10 @@ protected: void decreaseViewRange(float *statustext_time); void toggleFullViewRange(float *statustext_time); - void updateCameraDirection(CameraOrientation *cam, VolatileRunFlags *flags); + void updateCameraDirection(CameraOrientation *cam, VolatileRunFlags *flags, + float dtime); void updateCameraOrientation(CameraOrientation *cam, - const VolatileRunFlags &flags); + const VolatileRunFlags &flags, float dtime); void updatePlayerControl(const CameraOrientation &cam); void step(f32 *dtime); void processClientEvents(CameraOrientation *cam, float *damage_flash); @@ -1611,34 +1618,39 @@ protected: static void settingChangedCallback(const std::string &setting_name, void *data); void readSettings(); - bool getLeftClicked() + inline bool getLeftClicked() { - return input->getLeftClicked(); + return input->getLeftClicked() || + input->joystick.getWasKeyDown(KeyType::MOUSE_L); } - bool getRightClicked() + inline bool getRightClicked() { - return input->getRightClicked(); + return input->getRightClicked() || + input->joystick.getWasKeyDown(KeyType::MOUSE_R); } - bool isLeftPressed() + inline bool isLeftPressed() { - return input->getLeftState(); + return input->getLeftState() || + input->joystick.isKeyDown(KeyType::MOUSE_L); } - bool isRightPressed() + inline bool isRightPressed() { - return input->getRightState(); + return input->getRightState() || + input->joystick.isKeyDown(KeyType::MOUSE_R); } - bool getLeftReleased() + inline bool getLeftReleased() { - return input->getLeftReleased(); + return input->getLeftReleased() || + input->joystick.wasKeyReleased(KeyType::MOUSE_L); } - bool isKeyDown(GameKeyType k) + inline bool isKeyDown(GameKeyType k) { - return input->isKeyDown(keycache.key[k]); + return input->isKeyDown(keycache.key[k]) || input->joystick.isKeyDown(k); } - bool wasKeyDown(GameKeyType k) + inline bool wasKeyDown(GameKeyType k) { - return input->wasKeyDown(keycache.key[k]); + return input->wasKeyDown(keycache.key[k]) || input->joystick.wasKeyDown(k); } #ifdef __ANDROID__ @@ -1724,9 +1736,11 @@ private: */ bool m_cache_doubletap_jump; bool m_cache_enable_clouds; + bool m_cache_enable_joysticks; bool m_cache_enable_particles; bool m_cache_enable_fog; f32 m_cache_mouse_sensitivity; + f32 m_cache_joystick_frustum_sensitivity; f32 m_repeat_right_click_time; #ifdef __ANDROID__ @@ -1762,12 +1776,16 @@ Game::Game() : &settingChangedCallback, this); g_settings->registerChangedCallback("enable_clouds", &settingChangedCallback, this); + g_settings->registerChangedCallback("doubletap_joysticks", + &settingChangedCallback, this); g_settings->registerChangedCallback("enable_particles", &settingChangedCallback, this); g_settings->registerChangedCallback("enable_fog", &settingChangedCallback, this); g_settings->registerChangedCallback("mouse_sensitivity", &settingChangedCallback, this); + g_settings->registerChangedCallback("joystick_frustum_sensitivity", + &settingChangedCallback, this); g_settings->registerChangedCallback("repeat_rightclick_time", &settingChangedCallback, this); @@ -1930,7 +1948,7 @@ void Game::run() updateProfilers(runData, stats, draw_times, dtime); processUserInput(&flags, &runData, dtime); // Update camera before player movement to avoid camera lag of one frame - updateCameraDirection(&cam_view_target, &flags); + updateCameraDirection(&cam_view_target, &flags, dtime); float cam_smoothing = 0; if (g_settings->getBool("cinematic")) cam_smoothing = 1 - g_settings->getFloat("cinematic_camera_smoothing"); @@ -2727,7 +2745,8 @@ void Game::processKeyInput(VolatileRunFlags *flags, } else if (wasKeyDown(KeyType::ESC) || input->wasKeyDown(CancelKey)) { if (!gui_chat_console->isOpenInhibited()) { show_pause_menu(¤t_formspec, client, gamedef, - texture_src, device, simple_singleplayer_mode); + texture_src, device, &input->joystick, + simple_singleplayer_mode); } } else if (wasKeyDown(KeyType::CHAT)) { openConsole(0.2, L""); @@ -2813,12 +2832,21 @@ void Game::processItemSelection(u16 *new_playeritem) u16 max_item = MYMIN(PLAYER_INVENTORY_SIZE - 1, player->hud_hotbar_itemcount - 1); - if (wheel < 0) + s32 dir = wheel; + + if (input->joystick.wasKeyDown(KeyType::SCROLL_DOWN)) { + dir = -1; + } + + if (input->joystick.wasKeyDown(KeyType::SCROLL_UP)) { + dir = 1; + } + + if (dir < 0) *new_playeritem = *new_playeritem < max_item ? *new_playeritem + 1 : 0; - else if (wheel > 0) + else if (dir > 0) *new_playeritem = *new_playeritem > 0 ? *new_playeritem - 1 : max_item; - // else wheel == 0 - + // else dir == 0 /* Item selection using keyboard */ @@ -2868,7 +2896,7 @@ void Game::openInventory() TextDest *txt_dst = new TextDestPlayerInventory(client); create_formspec_menu(¤t_formspec, client, gamedef, texture_src, - device, fs_src, txt_dst, client); + device, &input->joystick, fs_src, txt_dst, client); InventoryLocation inventoryloc; inventoryloc.setCurrentPlayer(); @@ -3154,7 +3182,7 @@ void Game::toggleFullViewRange(float *statustext_time) void Game::updateCameraDirection(CameraOrientation *cam, - VolatileRunFlags *flags) + VolatileRunFlags *flags, float dtime) { if ((device->isWindowActive() && noMenuActive()) || random_input) { @@ -3169,7 +3197,7 @@ void Game::updateCameraDirection(CameraOrientation *cam, if (flags->first_loop_after_window_activation) flags->first_loop_after_window_activation = false; else - updateCameraOrientation(cam, *flags); + updateCameraOrientation(cam, *flags, dtime); input->setMousePos((driver->getScreenSize().Width / 2), (driver->getScreenSize().Height / 2)); @@ -3187,9 +3215,8 @@ void Game::updateCameraDirection(CameraOrientation *cam, } } - void Game::updateCameraOrientation(CameraOrientation *cam, - const VolatileRunFlags &flags) + const VolatileRunFlags &flags, float dtime) { #ifdef HAVE_TOUCHSCREENGUI if (g_touchscreengui) { @@ -3197,6 +3224,7 @@ void Game::updateCameraOrientation(CameraOrientation *cam, cam->camera_pitch = g_touchscreengui->getPitch(); } else { #endif + s32 dx = input->getMousePos().X - (driver->getScreenSize().Width / 2); s32 dy = input->getMousePos().Y - (driver->getScreenSize().Height / 2); @@ -3212,6 +3240,14 @@ void Game::updateCameraOrientation(CameraOrientation *cam, } #endif + if (m_cache_enable_joysticks) { + f32 c = m_cache_joystick_frustum_sensitivity * (1.f / 32767.f) * dtime; + cam->camera_yaw -= input->joystick.getAxisWithoutDead(JA_FRUSTUM_HORIZONTAL) * + c; + cam->camera_pitch += input->joystick.getAxisWithoutDead(JA_FRUSTUM_VERTICAL) * + c; + } + cam->camera_pitch = rangelim(cam->camera_pitch, -89.5, 89.5); } @@ -3220,30 +3256,36 @@ void Game::updatePlayerControl(const CameraOrientation &cam) { //TimeTaker tt("update player control", NULL, PRECISION_NANO); + // DO NOT use the isKeyDown method for the forward, backward, left, right + // buttons, as the code that uses the controls needs to be able to + // distinguish between the two in order to know when to use joysticks. + PlayerControl control( input->isKeyDown(keycache.key[KeyType::FORWARD]), input->isKeyDown(keycache.key[KeyType::BACKWARD]), input->isKeyDown(keycache.key[KeyType::LEFT]), input->isKeyDown(keycache.key[KeyType::RIGHT]), - input->isKeyDown(keycache.key[KeyType::JUMP]), - input->isKeyDown(keycache.key[KeyType::SPECIAL1]), - input->isKeyDown(keycache.key[KeyType::SNEAK]), - input->getLeftState(), - input->getRightState(), + isKeyDown(KeyType::JUMP), + isKeyDown(KeyType::SPECIAL1), + isKeyDown(KeyType::SNEAK), + isLeftPressed(), + isRightPressed(), cam.camera_pitch, - cam.camera_yaw + cam.camera_yaw, + input->joystick.getAxisWithoutDead(JA_SIDEWARD_MOVE), + input->joystick.getAxisWithoutDead(JA_FORWARD_MOVE) ); u32 keypress_bits = - ( (u32)(input->isKeyDown(keycache.key[KeyType::FORWARD]) & 0x1) << 0) | - ( (u32)(input->isKeyDown(keycache.key[KeyType::BACKWARD]) & 0x1) << 1) | - ( (u32)(input->isKeyDown(keycache.key[KeyType::LEFT]) & 0x1) << 2) | - ( (u32)(input->isKeyDown(keycache.key[KeyType::RIGHT]) & 0x1) << 3) | - ( (u32)(input->isKeyDown(keycache.key[KeyType::JUMP]) & 0x1) << 4) | - ( (u32)(input->isKeyDown(keycache.key[KeyType::SPECIAL1]) & 0x1) << 5) | - ( (u32)(input->isKeyDown(keycache.key[KeyType::SNEAK]) & 0x1) << 6) | - ( (u32)(input->getLeftState() & 0x1) << 7) | - ( (u32)(input->getRightState() & 0x1) << 8 + ( (u32)(isKeyDown(KeyType::FORWARD) & 0x1) << 0) | + ( (u32)(isKeyDown(KeyType::BACKWARD) & 0x1) << 1) | + ( (u32)(isKeyDown(KeyType::LEFT) & 0x1) << 2) | + ( (u32)(isKeyDown(KeyType::RIGHT) & 0x1) << 3) | + ( (u32)(isKeyDown(KeyType::JUMP) & 0x1) << 4) | + ( (u32)(isKeyDown(KeyType::SPECIAL1) & 0x1) << 5) | + ( (u32)(isKeyDown(KeyType::SNEAK) & 0x1) << 6) | + ( (u32)(isLeftPressed() & 0x1) << 7) | + ( (u32)(isRightPressed() & 0x1) << 8 ); #ifdef ANDROID @@ -3312,7 +3354,7 @@ void Game::processClientEvents(CameraOrientation *cam, float *damage_flash) cam->camera_pitch = event.player_force_move.pitch; } else if (event.type == CE_DEATHSCREEN) { show_deathscreen(¤t_formspec, client, gamedef, texture_src, - device, client); + device, &input->joystick, client); chat_backend->addMessage(L"", L"You died."); @@ -3328,7 +3370,8 @@ void Game::processClientEvents(CameraOrientation *cam, float *damage_flash) new TextDestPlayerInventory(client, *(event.show_formspec.formname)); create_formspec_menu(¤t_formspec, client, gamedef, - texture_src, device, fs_src, txt_dst, client); + texture_src, device, &input->joystick, + fs_src, txt_dst, client); delete(event.show_formspec.formspec); delete(event.show_formspec.formname); @@ -3723,8 +3766,14 @@ void Game::processPlayerInteraction(GameRunData *runData, input->resetLeftClicked(); input->resetRightClicked(); + input->joystick.clearWasKeyDown(KeyType::MOUSE_L); + input->joystick.clearWasKeyDown(KeyType::MOUSE_R); + input->resetLeftReleased(); input->resetRightReleased(); + + input->joystick.clearWasKeyReleased(KeyType::MOUSE_L); + input->joystick.clearWasKeyReleased(KeyType::MOUSE_R); } @@ -3785,7 +3834,7 @@ void Game::handlePointingAtNode(GameRunData *runData, TextDest *txt_dst = new TextDestNodeMetadata(nodepos, client); create_formspec_menu(¤t_formspec, client, gamedef, - texture_src, device, fs_src, txt_dst, client); + texture_src, device, &input->joystick, fs_src, txt_dst, client); current_formspec->setFormSpec(meta->getString("formspec"), inventoryloc); } else { @@ -4468,12 +4517,14 @@ void Game::settingChangedCallback(const std::string &setting_name, void *data) void Game::readSettings() { - m_cache_doubletap_jump = g_settings->getBool("doubletap_jump"); - m_cache_enable_clouds = g_settings->getBool("enable_clouds"); - m_cache_enable_particles = g_settings->getBool("enable_particles"); - m_cache_enable_fog = g_settings->getBool("enable_fog"); - m_cache_mouse_sensitivity = g_settings->getFloat("mouse_sensitivity"); - m_repeat_right_click_time = g_settings->getFloat("repeat_rightclick_time"); + m_cache_doubletap_jump = g_settings->getBool("doubletap_jump"); + m_cache_enable_clouds = g_settings->getBool("enable_clouds"); + m_cache_enable_joysticks = g_settings->getBool("enable_joysticks"); + m_cache_enable_particles = g_settings->getBool("enable_particles"); + m_cache_enable_fog = g_settings->getBool("enable_fog"); + m_cache_mouse_sensitivity = g_settings->getFloat("mouse_sensitivity"); + m_cache_joystick_frustum_sensitivity = g_settings->getFloat("joystick_frustum_sensitivity"); + m_repeat_right_click_time = g_settings->getFloat("repeat_rightclick_time"); m_cache_mouse_sensitivity = rangelim(m_cache_mouse_sensitivity, 0.001, 100.0); } diff --git a/src/game.h b/src/game.h index d8c2f78fc..df32e3397 100644 --- a/src/game.h +++ b/src/game.h @@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrlichttypes_extrabloated.h" #include #include "client/keys.h" +#include "client/joystick_controller.h" #include "keycode.h" #include @@ -135,6 +136,8 @@ public: virtual void step(float dtime) {} virtual void clear() {} + + JoystickController joystick; }; class ChatBackend; /* to avoid having to include chat.h */ diff --git a/src/guiEngine.cpp b/src/guiEngine.cpp index 96de7a4f7..b9d796ccb 100644 --- a/src/guiEngine.cpp +++ b/src/guiEngine.cpp @@ -131,6 +131,7 @@ void MenuMusicFetcher::fetchSounds(const std::string &name, /** GUIEngine */ /******************************************************************************/ GUIEngine::GUIEngine( irr::IrrlichtDevice* dev, + JoystickController *joystick, gui::IGUIElement* parent, IMenuManager *menumgr, scene::ISceneManager* smgr, @@ -189,6 +190,7 @@ GUIEngine::GUIEngine( irr::IrrlichtDevice* dev, /* Create menu */ m_menu = new GUIFormSpecMenu(m_device, + joystick, m_parent, -1, m_menumanager, diff --git a/src/guiEngine.h b/src/guiEngine.h index fa98a21e4..a59436953 100644 --- a/src/guiEngine.h +++ b/src/guiEngine.h @@ -149,7 +149,8 @@ public: * @param smgr scene manager to add scene elements to * @param data struct to transfer data to main game handling */ - GUIEngine( irr::IrrlichtDevice* dev, + GUIEngine(irr::IrrlichtDevice* dev, + JoystickController *joystick, gui::IGUIElement* parent, IMenuManager *menumgr, scene::ISceneManager* smgr, diff --git a/src/guiFormSpecMenu.cpp b/src/guiFormSpecMenu.cpp index cf01dc38c..a9cbb6254 100644 --- a/src/guiFormSpecMenu.cpp +++ b/src/guiFormSpecMenu.cpp @@ -79,6 +79,7 @@ static unsigned int font_line_height(gui::IGUIFont *font) } GUIFormSpecMenu::GUIFormSpecMenu(irr::IrrlichtDevice* dev, + JoystickController *joystick, gui::IGUIElement* parent, s32 id, IMenuManager *menumgr, InventoryManager *invmgr, IGameDef *gamedef, ISimpleTextureSource *tsrc, IFormSource* fsrc, TextDest* tdst, @@ -102,6 +103,7 @@ GUIFormSpecMenu::GUIFormSpecMenu(irr::IrrlichtDevice* dev, m_text_dst(tdst), m_formspec_version(0), m_focused_element(""), + m_joystick(joystick), m_font(NULL), m_remap_dbl_click(remap_dbl_click) #ifdef __ANDROID__ @@ -2459,7 +2461,7 @@ void GUIFormSpecMenu::drawMenu() Draw static text elements */ for (u32 i = 0; i < m_static_texts.size(); i++) { - const StaticTextSpec &spec = m_static_texts[i]; + const StaticTextSpec &spec = m_static_texts[i]; core::rect rect = spec.rect; if (spec.parent_button && spec.parent_button->isPressed()) { #if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8) @@ -3024,6 +3026,25 @@ bool GUIFormSpecMenu::preprocessEvent(const SEvent& event) } #endif + if (event.EventType == irr::EET_JOYSTICK_INPUT_EVENT) { + /* TODO add a check like: + if (event.JoystickEvent != joystick_we_listen_for) + return false; + */ + bool handled = m_joystick->handleEvent(event.JoystickEvent); + if (handled) { + if (m_joystick->wasKeyDown(KeyType::ESC)) { + tryClose(); + } else if (m_joystick->wasKeyDown(KeyType::JUMP)) { + if (m_allowclose) { + acceptInput(quit_mode_accept); + quitMenu(); + } + } + } + return handled; + } + return false; } @@ -3085,19 +3106,24 @@ bool GUIFormSpecMenu::DoubleClickDetection(const SEvent event) return false; } +void GUIFormSpecMenu::tryClose() +{ + if (m_allowclose) { + doPause = false; + acceptInput(quit_mode_cancel); + quitMenu(); + } else { + m_text_dst->gotText(L"MenuQuit"); + } +} + bool GUIFormSpecMenu::OnEvent(const SEvent& event) { if (event.EventType==EET_KEY_INPUT_EVENT) { KeyPress kp(event.KeyInput); if (event.KeyInput.PressedDown && ( (kp == EscapeKey) || (kp == getKeySetting("keymap_inventory")) || (kp == CancelKey))) { - if (m_allowclose) { - doPause = false; - acceptInput(quit_mode_cancel); - quitMenu(); - } else { - m_text_dst->gotText(L"MenuQuit"); - } + tryClose(); return true; } else if (m_client != NULL && event.KeyInput.PressedDown && (kp == getKeySetting("keymap_screenshot"))) { diff --git a/src/guiFormSpecMenu.h b/src/guiFormSpecMenu.h index 4122b1f56..2fb55070d 100644 --- a/src/guiFormSpecMenu.h +++ b/src/guiFormSpecMenu.h @@ -29,6 +29,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "modalMenu.h" #include "guiTable.h" #include "network/networkprotocol.h" +#include "client/joystick_controller.h" #include "util/string.h" #include "util/enriched_string.h" @@ -278,6 +279,7 @@ class GUIFormSpecMenu : public GUIModalMenu public: GUIFormSpecMenu(irr::IrrlichtDevice* dev, + JoystickController *joystick, gui::IGUIElement* parent, s32 id, IMenuManager *menumgr, InventoryManager *invmgr, @@ -433,10 +435,11 @@ protected: video::SColor m_default_tooltip_color; private: - IFormSource *m_form_src; - TextDest *m_text_dst; - unsigned int m_formspec_version; - std::string m_focused_element; + IFormSource *m_form_src; + TextDest *m_text_dst; + unsigned int m_formspec_version; + std::string m_focused_element; + JoystickController *m_joystick; typedef struct { bool explicit_size; @@ -494,6 +497,8 @@ private: bool parseSizeDirect(parserData* data, std::string element); void parseScrollBar(parserData* data, std::string element); + void tryClose(); + /** * check if event is part of a double click * @param event event to evaluate diff --git a/src/localplayer.cpp b/src/localplayer.cpp index 507f31980..732ca8acf 100644 --- a/src/localplayer.cpp +++ b/src/localplayer.cpp @@ -528,18 +528,23 @@ void LocalPlayer::applyControl(float dtime) speedH += move_direction; } } - if(control.down) - { + if (control.down) { speedH -= move_direction; } - if(control.left) - { + if (!control.up && !control.down) { + speedH -= move_direction * + (control.forw_move_joystick_axis / 32767.f); + } + if (control.left) { speedH += move_direction.crossProduct(v3f(0,1,0)); } - if(control.right) - { + if (control.right) { speedH += move_direction.crossProduct(v3f(0,-1,0)); } + if (!control.left && !control.right) { + speedH -= move_direction.crossProduct(v3f(0,1,0)) * + (control.sidew_move_joystick_axis / 32767.f); + } if(control.jump) { if (free_move) { diff --git a/src/player.h b/src/player.h index b317cda4f..6687ca86e 100644 --- a/src/player.h +++ b/src/player.h @@ -46,6 +46,8 @@ struct PlayerControl RMB = false; pitch = 0; yaw = 0; + sidew_move_joystick_axis = .0f; + forw_move_joystick_axis = .0f; } PlayerControl( bool a_up, @@ -58,7 +60,9 @@ struct PlayerControl bool a_LMB, bool a_RMB, float a_pitch, - float a_yaw + float a_yaw, + float a_sidew_move_joystick_axis, + float a_forw_move_joystick_axis ) { up = a_up; @@ -72,6 +76,8 @@ struct PlayerControl RMB = a_RMB; pitch = a_pitch; yaw = a_yaw; + sidew_move_joystick_axis = a_sidew_move_joystick_axis; + forw_move_joystick_axis = a_forw_move_joystick_axis; } bool up; bool down; @@ -84,6 +90,8 @@ struct PlayerControl bool RMB; float pitch; float yaw; + float sidew_move_joystick_axis; + float forw_move_joystick_axis; }; class Map; -- cgit v1.2.3 From e1aa98fe077faecb14a9820f3861bb1c82b5db6e Mon Sep 17 00:00:00 2001 From: est31 Date: Sun, 3 Jul 2016 19:36:51 +0200 Subject: Remove top left minetest watermark Move version information into the window caption. On popular player request. Fixes #4209. --- src/client/clientlauncher.cpp | 5 ++++- src/game.cpp | 11 ++++------- src/guiEngine.cpp | 16 +++------------- src/guiEngine.h | 4 ++-- 4 files changed, 13 insertions(+), 23 deletions(-) (limited to 'src/client') diff --git a/src/client/clientlauncher.cpp b/src/client/clientlauncher.cpp index ee8662ed6..a0781ef37 100644 --- a/src/client/clientlauncher.cpp +++ b/src/client/clientlauncher.cpp @@ -34,6 +34,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "fontengine.h" #include "joystick_controller.h" #include "clientlauncher.h" +#include "version.h" /* mainmenumanager.h */ @@ -185,7 +186,9 @@ bool ClientLauncher::run(GameParams &game_params, const Settings &cmd_args) { // Set the window caption const wchar_t *text = wgettext("Main Menu"); - device->setWindowCaption((utf8_to_wide(PROJECT_NAME_C) + L" [" + text + L"]").c_str()); + device->setWindowCaption((utf8_to_wide(PROJECT_NAME_C) + + L" " + utf8_to_wide(g_version_hash) + + L" [" + text + L"]").c_str()); delete[] text; try { // This is used for catching disconnects diff --git a/src/game.cpp b/src/game.cpp index ba77d299a..93d9e6d2c 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -1273,10 +1273,10 @@ static void updateChat(Client &client, f32 dtime, bool show_debug, setStaticText(guitext_chat, recent_chat); // Update gui element size and position - s32 chat_y = 5 + line_height; + s32 chat_y = 5; if (show_debug) - chat_y += line_height; + chat_y += 2 * line_height; // first pass to calculate height of text to be set s32 width = std::min(g_fontengine->getTextWidth(recent_chat.c_str()) + 10, @@ -2205,6 +2205,8 @@ bool Game::createClient(const std::string &playername, /* Set window caption */ std::wstring str = utf8_to_wide(PROJECT_NAME_C); + str += L" "; + str += utf8_to_wide(g_version_hash); str += L" ["; str += driver->getName(); str += L"]"; @@ -4347,11 +4349,6 @@ void Game::updateGui(float *statustext_time, const RunStats &stats, << ", RTT = " << client->getRTT(); 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; - setStaticText(guitext, utf8_to_wide(os.str()).c_str()); - guitext->setVisible(true); } else { guitext->setVisible(false); } diff --git a/src/guiEngine.cpp b/src/guiEngine.cpp index b9d796ccb..e15533dcd 100644 --- a/src/guiEngine.cpp +++ b/src/guiEngine.cpp @@ -174,8 +174,7 @@ GUIEngine::GUIEngine( irr::IrrlichtDevice* dev, m_sound_manager = &dummySoundManager; //create topleft header - m_toplefttext = utf8_to_wide(std::string(PROJECT_NAME_C " ") + - g_version_hash); + m_toplefttext = L""; core::rect rect(0, 0, g_fontengine->getTextWidth(m_toplefttext.c_str()), g_fontengine->getTextHeight()); @@ -571,18 +570,9 @@ bool GUIEngine::downloadFile(std::string url, std::string target) } /******************************************************************************/ -void GUIEngine::setTopleftText(std::string append) +void GUIEngine::setTopleftText(const std::string &text) { - std::wstring toset = utf8_to_wide(std::string(PROJECT_NAME_C " ") + - g_version_hash); - - if (append != "") - { - toset += L" / "; - toset += utf8_to_wide(append); - } - - m_toplefttext = toset; + m_toplefttext = utf8_to_wide(text); updateTopLeftTextSize(); } diff --git a/src/guiEngine.h b/src/guiEngine.h index a59436953..897244808 100644 --- a/src/guiEngine.h +++ b/src/guiEngine.h @@ -270,10 +270,10 @@ private: void drawVersion(); /** - * specify text to be appended to version string + * specify text to appear as top left string * @param text to set */ - void setTopleftText(std::string append); + void setTopleftText(const std::string &text); /** pointer to gui element shown at topleft corner */ irr::gui::IGUIStaticText* m_irr_toplefttext; -- cgit v1.2.3 From 5d4d3f8366b74d9c3da892d94188defc49407ebf Mon Sep 17 00:00:00 2001 From: est31 Date: Mon, 4 Jul 2016 01:33:46 +0200 Subject: Finally set a window icon on X11 Since the creation of minetest, it had no window icon on X11. Now we have one. The misc/minetest-xorg-icon-128.png file is a rendering of the misc/minetest.svg file with inkscape, created with something like: inkscape -z -e misc/minetest-xorg-icon-128.png -w 128 -h 128 misc/minetest.svg --- misc/minetest-xorg-icon-128.png | Bin 0 -> 11241 bytes src/client/clientlauncher.cpp | 3 ++ src/porting.cpp | 87 ++++++++++++++++++++++++++++++++++++++++ src/porting.h | 3 ++ 4 files changed, 93 insertions(+) create mode 100644 misc/minetest-xorg-icon-128.png (limited to 'src/client') diff --git a/misc/minetest-xorg-icon-128.png b/misc/minetest-xorg-icon-128.png new file mode 100644 index 000000000..0241a911c Binary files /dev/null and b/misc/minetest-xorg-icon-128.png differ diff --git a/src/client/clientlauncher.cpp b/src/client/clientlauncher.cpp index a0781ef37..aa3c2d548 100644 --- a/src/client/clientlauncher.cpp +++ b/src/client/clientlauncher.cpp @@ -114,6 +114,9 @@ bool ClientLauncher::run(GameParams &game_params, const Settings &cmd_args) porting::setXorgClassHint(video_driver->getExposedVideoData(), PROJECT_NAME_C); + porting::setXorgWindowIcon(device, + porting::path_share + "/misc/minetest-xorg-icon-128.png"); + /* This changes the minimum allowed number of vertices in a VBO. Default is 500. diff --git a/src/porting.cpp b/src/porting.cpp index 02ce6174b..ddf8139ea 100644 --- a/src/porting.cpp +++ b/src/porting.cpp @@ -611,6 +611,93 @@ void setXorgClassHint(const video::SExposedVideoData &video_data, #endif } +bool setXorgWindowIcon(IrrlichtDevice *device, + const std::string &icon_file) +{ +#ifdef XORG_USED + + video::IVideoDriver *v_driver = device->getVideoDriver(); + + video::IImageLoader *image_loader = NULL; + for (u32 i = v_driver->getImageLoaderCount() - 1; i >= 0; i--) { + if (v_driver->getImageLoader(i)->isALoadableFileExtension(icon_file.c_str())) { + image_loader = v_driver->getImageLoader(i); + break; + } + } + + if (!image_loader) { + warningstream << "Could not find image loader for file '" + << icon_file << "'" << std::endl; + return false; + } + + io::IReadFile *icon_f = device->getFileSystem()->createAndOpenFile(icon_file.c_str()); + + if (!icon_f) { + warningstream << "Could not load icon file '" + << icon_file << "'" << std::endl; + return false; + } + + video::IImage *img = image_loader->loadImage(icon_f); + + if (!img) { + warningstream << "Could not load icon file '" + << icon_file << "'" << std::endl; + icon_f->drop(); + return false; + } + + u32 height = img->getDimension().Height; + u32 width = img->getDimension().Width; + + size_t icon_buffer_len = 2 + height * width; + long *icon_buffer = new long[icon_buffer_len]; + + icon_buffer[0] = width; + icon_buffer[1] = height; + + for (u32 x = 0; x < width; x++) { + for (u32 y = 0; y < height; y++) { + video::SColor col = img->getPixel(x, y); + long pixel_val = 0; + pixel_val |= (u8)col.getAlpha() << 24; + pixel_val |= (u8)col.getRed() << 16; + pixel_val |= (u8)col.getGreen() << 8; + pixel_val |= (u8)col.getBlue(); + icon_buffer[2 + x + y * width] = pixel_val; + } + } + + img->drop(); + icon_f->drop(); + + const video::SExposedVideoData &video_data = v_driver->getExposedVideoData(); + + Display *x11_dpl = (Display *)video_data.OpenGLLinux.X11Display; + + if (x11_dpl == NULL) { + warningstream << "Could not find x11 display for setting its icon." + << std::endl; + delete [] icon_buffer; + return false; + } + + Window x11_win = (Window)video_data.OpenGLLinux.X11Window; + + Atom net_wm_icon = XInternAtom(x11_dpl, "_NET_WM_ICON", False); + Atom cardinal = XInternAtom(x11_dpl, "CARDINAL", False); + XChangeProperty(x11_dpl, x11_win, + net_wm_icon, cardinal, 32, + PropModeReplace, (const unsigned char *)icon_buffer, + icon_buffer_len); + + delete [] icon_buffer; + + return true; +#endif +} //// //// Video/Display Information (Client-only) diff --git a/src/porting.h b/src/porting.h index d101a7324..40f6b4dc3 100644 --- a/src/porting.h +++ b/src/porting.h @@ -367,6 +367,9 @@ inline const char *getPlatformName() void setXorgClassHint(const video::SExposedVideoData &video_data, const std::string &name); +bool setXorgWindowIcon(IrrlichtDevice *device, + const std::string &icon_file); + // This only needs to be called at the start of execution, since all future // threads in the process inherit this exception handler void setWin32ExceptionHandler(); -- cgit v1.2.3 From 2c31b79235dd83de753fce5890c5797e149048b8 Mon Sep 17 00:00:00 2001 From: "Esteban I. Ruiz Moreno" Date: Sun, 31 Mar 2013 00:30:32 -0300 Subject: Add zoom, tweakable with zoom_fov, default key: Z (like optifine) --- src/camera.cpp | 10 ++++++++-- src/camera.h | 1 + src/client/keys.h | 1 + src/defaultsettings.cpp | 2 ++ src/game.cpp | 2 ++ src/guiKeyChangeMenu.cpp | 2 ++ src/player.h | 4 ++++ 7 files changed, 20 insertions(+), 2 deletions(-) (limited to 'src/client') diff --git a/src/camera.cpp b/src/camera.cpp index 6893b8cbf..e1d6dd910 100644 --- a/src/camera.cpp +++ b/src/camera.cpp @@ -103,6 +103,7 @@ Camera::Camera(scene::ISceneManager* smgr, MapDrawControl& draw_control, m_cache_fall_bobbing_amount = g_settings->getFloat("fall_bobbing_amount"); m_cache_view_bobbing_amount = g_settings->getFloat("view_bobbing_amount"); m_cache_fov = g_settings->getFloat("fov"); + m_cache_zoom_fov = g_settings->getFloat("zoom_fov"); m_cache_view_bobbing = g_settings->getBool("view_bobbing"); m_nametags.clear(); } @@ -387,8 +388,13 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, if (m_camera_mode == CAMERA_MODE_THIRD_FRONT) m_camera_position = my_cp; - // Get FOV setting - f32 fov_degrees = m_cache_fov; + // Get FOV + f32 fov_degrees; + if (player->getPlayerControl().zoom) { + fov_degrees = m_cache_zoom_fov; + } else { + fov_degrees = m_cache_fov; + } fov_degrees = MYMAX(fov_degrees, 10.0); fov_degrees = MYMIN(fov_degrees, 170.0); diff --git a/src/camera.h b/src/camera.h index ce46c3190..cb0e9686d 100644 --- a/src/camera.h +++ b/src/camera.h @@ -231,6 +231,7 @@ private: f32 m_cache_fall_bobbing_amount; f32 m_cache_view_bobbing_amount; f32 m_cache_fov; + f32 m_cache_zoom_fov; bool m_cache_view_bobbing; std::list m_nametags; diff --git a/src/client/keys.h b/src/client/keys.h index 0921bc166..6467c443e 100644 --- a/src/client/keys.h +++ b/src/client/keys.h @@ -59,6 +59,7 @@ public: INCREASE_VIEWING_RANGE, DECREASE_VIEWING_RANGE, RANGESELECT, + ZOOM, QUICKTUNE_NEXT, QUICKTUNE_PREV, diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 3220b915a..42b232afc 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -40,6 +40,7 @@ void set_default_settings(Settings *settings) settings->setDefault("keymap_jump", "KEY_SPACE"); settings->setDefault("keymap_sneak", "KEY_LSHIFT"); settings->setDefault("keymap_drop", "KEY_KEY_Q"); + settings->setDefault("keymap_zoom", "KEY_KEY_Z"); settings->setDefault("keymap_inventory", "KEY_KEY_I"); settings->setDefault("keymap_special1", "KEY_KEY_E"); settings->setDefault("keymap_chat", "KEY_KEY_T"); @@ -74,6 +75,7 @@ void set_default_settings(Settings *settings) settings->setDefault("always_fly_fast", "true"); settings->setDefault("directional_colored_fog", "true"); settings->setDefault("tooltip_show_delay", "400"); + settings->setDefault("zoom_fov", "15"); // Some (temporary) keys for debugging settings->setDefault("keymap_print_debug_stacks", "KEY_KEY_P"); diff --git a/src/game.cpp b/src/game.cpp index 9b9f3a75f..1a036d03a 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -1371,6 +1371,7 @@ void KeyCache::populate() = getKeySetting("keymap_decrease_viewing_range_min"); key[KeyType::RANGESELECT] = getKeySetting("keymap_rangeselect"); + key[KeyType::ZOOM] = getKeySetting("keymap_zoom"); key[KeyType::QUICKTUNE_NEXT] = getKeySetting("keymap_quicktune_next"); key[KeyType::QUICKTUNE_PREV] = getKeySetting("keymap_quicktune_prev"); @@ -3270,6 +3271,7 @@ void Game::updatePlayerControl(const CameraOrientation &cam) isKeyDown(KeyType::JUMP), isKeyDown(KeyType::SPECIAL1), isKeyDown(KeyType::SNEAK), + isKeyDown(KeyType::ZOOM), isLeftPressed(), isRightPressed(), cam.camera_pitch, diff --git a/src/guiKeyChangeMenu.cpp b/src/guiKeyChangeMenu.cpp index 785e921f7..07137d1bc 100644 --- a/src/guiKeyChangeMenu.cpp +++ b/src/guiKeyChangeMenu.cpp @@ -59,6 +59,7 @@ enum GUI_ID_KEY_INVENTORY_BUTTON, GUI_ID_KEY_DUMP_BUTTON, GUI_ID_KEY_RANGE_BUTTON, + GUI_ID_KEY_ZOOM_BUTTON, // other GUI_ID_CB_AUX1_DESCENDS, GUI_ID_CB_DOUBLETAP_JUMP, @@ -414,5 +415,6 @@ void GUIKeyChangeMenu::init_keys() this->add_key(GUI_ID_KEY_NOCLIP_BUTTON, wgettext("Toggle noclip"), "keymap_noclip"); this->add_key(GUI_ID_KEY_RANGE_BUTTON, wgettext("Range select"), "keymap_rangeselect"); this->add_key(GUI_ID_KEY_DUMP_BUTTON, wgettext("Print stacks"), "keymap_print_debug_stacks"); + this->add_key(GUI_ID_KEY_ZOOM_BUTTON, wgettext("Zoom"), "keymap_zoom"); } diff --git a/src/player.h b/src/player.h index e6fcf388a..eab00bb04 100644 --- a/src/player.h +++ b/src/player.h @@ -49,6 +49,7 @@ struct PlayerControl sidew_move_joystick_axis = .0f; forw_move_joystick_axis = .0f; } + PlayerControl( bool a_up, bool a_down, @@ -57,6 +58,7 @@ struct PlayerControl bool a_jump, bool a_aux1, bool a_sneak, + bool a_zoom, bool a_LMB, bool a_RMB, float a_pitch, @@ -72,6 +74,7 @@ struct PlayerControl jump = a_jump; aux1 = a_aux1; sneak = a_sneak; + zoom = a_zoom; LMB = a_LMB; RMB = a_RMB; pitch = a_pitch; @@ -86,6 +89,7 @@ struct PlayerControl bool jump; bool aux1; bool sneak; + bool zoom; bool LMB; bool RMB; float pitch; -- cgit v1.2.3 From f21dae63390aa872062bcfc96fc6817b0fcfe601 Mon Sep 17 00:00:00 2001 From: Thomas--S Date: Sat, 2 Jul 2016 17:58:08 +0200 Subject: Add an [opacity: texture modifier. Makes the base image transparent according to the given ratio. r must be between 0 and 255. 0 means totally transparent. 255 means totally opaque. Useful for texture overlaying. --- doc/lua_api.txt | 10 ++++++++++ src/client/tile.cpp | 30 ++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) (limited to 'src/client') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index ce40e082c..182f5c821 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -292,6 +292,16 @@ Example: default_sandstone.png^[resize:16x16 +#### `[opacity:` + Makes the base image transparent according to the given ratio. + r must be between 0 and 255. + 0 means totally transparent. + 255 means totally opaque. + +Example: + + default_sandstone.png^[opacity:127 + #### `[brighten` Brightens the texture. diff --git a/src/client/tile.cpp b/src/client/tile.cpp index ec8c95f02..3b5d2a3ae 100644 --- a/src/client/tile.cpp +++ b/src/client/tile.cpp @@ -1735,6 +1735,36 @@ bool TextureSource::generateImagePart(std::string part_of_name, baseimg->drop(); baseimg = image; } + /* + [opacity:R + Makes the base image transparent according to the given ratio. + R must be between 0 and 255. + 0 means totally transparent. + 255 means totally opaque. + */ + else if (str_starts_with(part_of_name, "[opacity:")) { + if (baseimg == NULL) { + errorstream << "generateImagePart(): baseimg == NULL " + << "for part_of_name=\"" << part_of_name + << "\", cancelling." << std::endl; + return false; + } + + Strfnd sf(part_of_name); + sf.next(":"); + + u32 ratio = mystoi(sf.next(""), 0, 255); + + core::dimension2d dim = baseimg->getDimension(); + + for (u32 y = 0; y < dim.Height; y++) + for (u32 x = 0; x < dim.Width; x++) + { + video::SColor c = baseimg->getPixel(x,y); + c.setAlpha(floor((c.getAlpha() * ratio) / 255 + 0.5)); + baseimg->setPixel(x,y,c); + } + } else { errorstream << "generateImagePart(): Invalid " -- cgit v1.2.3 From f092dac9793b80c29a669b0d676ee3e4f55f682e Mon Sep 17 00:00:00 2001 From: est31 Date: Sat, 20 Aug 2016 21:26:44 +0200 Subject: Also support X11 icon for minetest copies installed via make install (#4407) Fixes #4323. --- CMakeLists.txt | 3 +++ src/client/clientlauncher.cpp | 3 +-- src/cmake_config.h.in | 1 + src/porting.cpp | 19 ++++++++++++++++++- src/porting.h | 4 +++- 5 files changed, 26 insertions(+), 4 deletions(-) (limited to 'src/client') diff --git a/CMakeLists.txt b/CMakeLists.txt index 592feb997..fbf6bb7fa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -175,6 +175,9 @@ if(UNIX AND NOT APPLE) install(FILES "misc/minetest.desktop" DESTINATION "${XDG_APPS_DIR}") install(FILES "misc/minetest.appdata.xml" DESTINATION "${APPDATADIR}") install(FILES "misc/minetest.svg" DESTINATION "${ICONDIR}/hicolor/scalable/apps") + install(FILES "misc/minetest-xorg-icon-128.png" + DESTINATION "${ICONDIR}/hicolor/128x128/apps" + RENAME "minetest.png") endif() if(APPLE) diff --git a/src/client/clientlauncher.cpp b/src/client/clientlauncher.cpp index aa3c2d548..6145e3dde 100644 --- a/src/client/clientlauncher.cpp +++ b/src/client/clientlauncher.cpp @@ -114,8 +114,7 @@ bool ClientLauncher::run(GameParams &game_params, const Settings &cmd_args) porting::setXorgClassHint(video_driver->getExposedVideoData(), PROJECT_NAME_C); - porting::setXorgWindowIcon(device, - porting::path_share + "/misc/minetest-xorg-icon-128.png"); + porting::setXorgWindowIcon(device); /* This changes the minimum allowed number of vertices in a VBO. diff --git a/src/cmake_config.h.in b/src/cmake_config.h.in index 50f34a0b8..4b731020a 100644 --- a/src/cmake_config.h.in +++ b/src/cmake_config.h.in @@ -14,6 +14,7 @@ #define STATIC_SHAREDIR "@SHAREDIR@" #define STATIC_LOCALEDIR "@LOCALEDIR@" #define BUILD_TYPE "@CMAKE_BUILD_TYPE@" +#define ICON_DIR "@ICONDIR@" #cmakedefine01 RUN_IN_PLACE #cmakedefine01 USE_GETTEXT #cmakedefine01 USE_CURL diff --git a/src/porting.cpp b/src/porting.cpp index acd047232..ae9114ac8 100644 --- a/src/porting.cpp +++ b/src/porting.cpp @@ -611,7 +611,24 @@ void setXorgClassHint(const video::SExposedVideoData &video_data, #endif } -bool setXorgWindowIcon(IrrlichtDevice *device, +bool setXorgWindowIcon(IrrlichtDevice *device) +{ +#if RUN_IN_PLACE + return setXorgWindowIconFromPath(device, + path_share + "/misc/" PROJECT_NAME "-xorg-icon-128.png"); +#else + // We have semi-support for reading in-place data if we are + // compiled with RUN_IN_PLACE. Don't break with this and + // also try the path_share location. + return + setXorgWindowIconFromPath(device, + ICON_DIR "/hicolor/128x128/apps/" PROJECT_NAME ".png") || + setXorgWindowIconFromPath(device, + path_share + "/misc/" PROJECT_NAME "-xorg-icon-128.png"); +#endif +} + +bool setXorgWindowIconFromPath(IrrlichtDevice *device, const std::string &icon_file) { #ifdef XORG_USED diff --git a/src/porting.h b/src/porting.h index 40f6b4dc3..f5c7efcb2 100644 --- a/src/porting.h +++ b/src/porting.h @@ -367,7 +367,9 @@ inline const char *getPlatformName() void setXorgClassHint(const video::SExposedVideoData &video_data, const std::string &name); -bool setXorgWindowIcon(IrrlichtDevice *device, +bool setXorgWindowIcon(IrrlichtDevice *device); + +bool setXorgWindowIconFromPath(IrrlichtDevice *device, const std::string &icon_file); // This only needs to be called at the start of execution, since all future -- cgit v1.2.3 From b77cee146b0029d377f1028b942857d062bc1374 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sat, 3 Sep 2016 17:53:15 +0200 Subject: Allow escaping of texture names when passed as an argument to a modifier --- doc/lua_api.txt | 14 ++++++++++-- src/client/tile.cpp | 65 ++++++++++++++++++++++++++++++++++++----------------- 2 files changed, 56 insertions(+), 23 deletions(-) (limited to 'src/client') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 1da45ad66..74d4b90d5 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -263,7 +263,17 @@ Textures can be grouped together by enclosing them in `(` and `)`. Example: `cobble.png^(thing1.png^thing2.png)` A texture for `thing1.png^thing2.png` is created and the resulting -texture is overlaid over `cobble.png`. +texture is overlaid on top of `cobble.png`. + +### Escaping +Modifiers that accept texture names (e.g. `[combine`) accept escaping to allow +passing complex texture names as arguments. Escaping is done with backslash and +is required for `^` and `:`. + +Example: `cobble.png^[lowpart:50:color.png\^[mask\:trans.png` + +The lower 50 percent of `color.png^[mask:trans.png` are overlaid +on top of `cobble.png`. ### Advanced texture modifiers @@ -351,7 +361,7 @@ Example: default_stone.png^[transformFXR90 #### `[inventorycube{{{` -`^` is replaced by `&` in texture names. +Escaping does not apply here and `^` is replaced by `&` in texture names instead. Create an inventory cube texture using the side textures. diff --git a/src/client/tile.cpp b/src/client/tile.cpp index 3b5d2a3ae..67d5d8d1a 100644 --- a/src/client/tile.cpp +++ b/src/client/tile.cpp @@ -948,11 +948,10 @@ video::ITexture* TextureSource::generateTextureFromMesh( video::IImage* TextureSource::generateImage(const std::string &name) { - /* - Get the base image - */ + // Get the base image const char separator = '^'; + const char escape = '\\'; const char paren_open = '('; const char paren_close = ')'; @@ -960,7 +959,9 @@ video::IImage* TextureSource::generateImage(const std::string &name) s32 last_separator_pos = -1; u8 paren_bal = 0; for (s32 i = name.size() - 1; i >= 0; i--) { - switch(name[i]) { + if (i > 0 && name[i-1] == escape) + continue; + switch (name[i]) { case separator: if (paren_bal == 0) { last_separator_pos = i; @@ -1028,10 +1029,12 @@ video::IImage* TextureSource::generateImage(const std::string &name) return NULL; } core::dimension2d dim = tmp->getDimension(); - if (!baseimg) - baseimg = driver->createImage(video::ECF_A8R8G8B8, dim); - blit_with_alpha(tmp, baseimg, v2s32(0, 0), v2s32(0, 0), dim); - tmp->drop(); + if (baseimg) { + blit_with_alpha(tmp, baseimg, v2s32(0, 0), v2s32(0, 0), dim); + tmp->drop(); + } else { + baseimg = tmp; + } } else if (!generateImagePart(last_part_of_name, baseimg)) { // Generate image according to part of name errorstream << "generateImage(): " @@ -1099,9 +1102,27 @@ video::IImage * Align2Npot2(video::IImage * image, #endif +static std::string unescape_string(const std::string &str, const char esc = '\\') +{ + std::string out; + size_t pos = 0, cpos; + out.reserve(str.size()); + while (1) { + cpos = str.find_first_of(esc, pos); + if (cpos == std::string::npos) { + out += str.substr(pos); + break; + } + out += str.substr(pos, cpos - pos) + str[cpos + 1]; + pos = cpos + 2; + } + return out; +} + bool TextureSource::generateImagePart(std::string part_of_name, video::IImage *& baseimg) { + const char escape = '\\'; // same as in generateImage() video::IVideoDriver* driver = m_device->getVideoDriver(); sanity_check(driver); @@ -1251,7 +1272,7 @@ bool TextureSource::generateImagePart(std::string part_of_name, } /* [combine:WxH:X,Y=filename:X,Y=filename2 - Creates a bigger texture from an amount of smaller ones + Creates a bigger texture from any amount of smaller ones */ else if (str_starts_with(part_of_name, "[combine")) { @@ -1259,7 +1280,6 @@ bool TextureSource::generateImagePart(std::string part_of_name, sf.next(":"); u32 w0 = stoi(sf.next("x")); u32 h0 = stoi(sf.next(":")); - //infostream<<"combined w="< dim = img->getDimension(); infostream<<"Size "<createImage(video::ECF_A8R8G8B8, v2u32(16,16)); - video::IImage *img = m_sourcecache.getOrLoad(filename, m_device); + video::IImage *img = generateImage(filename); if (img) { core::dimension2d dim = img->getDimension(); @@ -1628,9 +1647,9 @@ bool TextureSource::generateImagePart(std::string part_of_name, } Strfnd sf(part_of_name); sf.next(":"); - std::string filename = sf.next(":"); + std::string filename = unescape_string(sf.next_esc(":", escape), escape); - video::IImage *img = m_sourcecache.getOrLoad(filename, m_device); + video::IImage *img = generateImage(filename); if (img) { apply_mask(img, baseimg, v2s32(0, 0), v2s32(0, 0), img->getDimension()); @@ -1673,6 +1692,10 @@ bool TextureSource::generateImagePart(std::string part_of_name, apply_colorize(baseimg, v2u32(0, 0), baseimg->getDimension(), color, ratio, keep_alpha); } + /* + [applyfiltersformesh + Internal modifier + */ else if (str_starts_with(part_of_name, "[applyfiltersformesh")) { // Apply the "clean transparent" filter, if configured. -- cgit v1.2.3 From 1475c1b1c8bb1a2a2812d485d3590e1f817f7c7b Mon Sep 17 00:00:00 2001 From: Thomas--S Date: Fri, 12 Aug 2016 17:56:31 +0200 Subject: Add an [invert: texture modifier Inverts the given channels of the base image. Mode may contain the characters "r", "g", "b", "a". Only the channels that are mentioned in the mode string will be inverted. --- doc/lua_api.txt | 9 +++++++++ src/client/tile.cpp | 43 +++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 50 insertions(+), 2 deletions(-) (limited to 'src/client') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index aa4f129f0..41e7c00eb 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -318,6 +318,15 @@ Example: default_sandstone.png^[opacity:127 +#### `[invert:` +Inverts the given channels of the base image. +Mode may contain the characters "r", "g", "b", "a". +Only the channels that are mentioned in the mode string will be inverted. + +Example: + + default_apple.png^[invert:rgb + #### `[brighten` Brightens the texture. diff --git a/src/client/tile.cpp b/src/client/tile.cpp index 67d5d8d1a..8f0c39465 100644 --- a/src/client/tile.cpp +++ b/src/client/tile.cpp @@ -1783,9 +1783,48 @@ bool TextureSource::generateImagePart(std::string part_of_name, for (u32 y = 0; y < dim.Height; y++) for (u32 x = 0; x < dim.Width; x++) { - video::SColor c = baseimg->getPixel(x,y); + video::SColor c = baseimg->getPixel(x, y); c.setAlpha(floor((c.getAlpha() * ratio) / 255 + 0.5)); - baseimg->setPixel(x,y,c); + baseimg->setPixel(x, y, c); + } + } + /* + [invert:mode + Inverts the given channels of the base image. + Mode may contain the characters "r", "g", "b", "a". + Only the channels that are mentioned in the mode string + will be inverted. + */ + else if (str_starts_with(part_of_name, "[invert:")) { + if (baseimg == NULL) { + errorstream << "generateImagePart(): baseimg == NULL " + << "for part_of_name=\"" << part_of_name + << "\", cancelling." << std::endl; + return false; + } + + Strfnd sf(part_of_name); + sf.next(":"); + + std::string mode = sf.next(""); + u32 mask = 0; + if (mode.find("a") != std::string::npos) + mask |= 0xff000000UL; + if (mode.find("r") != std::string::npos) + mask |= 0x00ff0000UL; + if (mode.find("g") != std::string::npos) + mask |= 0x0000ff00UL; + if (mode.find("b") != std::string::npos) + mask |= 0x000000ffUL; + + core::dimension2d dim = baseimg->getDimension(); + + for (u32 y = 0; y < dim.Height; y++) + for (u32 x = 0; x < dim.Width; x++) + { + video::SColor c = baseimg->getPixel(x, y); + c.color ^= mask; + baseimg->setPixel(x, y, c); } } else -- cgit v1.2.3