diff --git a/modules/client_entergame/entergame.lua b/modules/client_entergame/entergame.lua index 1a11945b..ca78f4ca 100644 --- a/modules/client_entergame/entergame.lua +++ b/modules/client_entergame/entergame.lua @@ -10,9 +10,9 @@ local enterGameButton -- private functions local function clearAccountFields() - enterGame:getChildById('accountNameLineEdit'):clearText() - enterGame:getChildById('accountPasswordLineEdit'):clearText() - enterGame:getChildById('accountNameLineEdit'):focus() + enterGame:getChildById('accountNameTextEdit'):clearText() + enterGame:getChildById('accountPasswordTextEdit'):clearText() + enterGame:getChildById('accountNameTextEdit'):focus() Settings.remove('account') Settings.remove('password') end @@ -71,13 +71,13 @@ function EnterGame.init() local port = Settings.get('port') local autologin = Settings.getBoolean('autologin') - enterGame:getChildById('accountNameLineEdit'):setText(account) - enterGame:getChildById('accountPasswordLineEdit'):setText(password) - enterGame:getChildById('serverHostLineEdit'):setText(host) - enterGame:getChildById('serverPortLineEdit'):setText(port) + enterGame:getChildById('accountNameTextEdit'):setText(account) + enterGame:getChildById('accountPasswordTextEdit'):setText(password) + enterGame:getChildById('serverHostTextEdit'):setText(host) + enterGame:getChildById('serverPortTextEdit'):setText(port) enterGame:getChildById('autoLoginBox'):setChecked(autologin) enterGame:getChildById('rememberPasswordBox'):setChecked(#account > 0) - enterGame:getChildById('accountNameLineEdit'):focus() + enterGame:getChildById('accountNameTextEdit'):focus() -- only open entergame when app starts enterGame:hide() @@ -120,10 +120,10 @@ function EnterGame.openWindow() end function EnterGame.doLogin() - EnterGame.account = enterGame:getChildById('accountNameLineEdit'):getText() - EnterGame.password = enterGame:getChildById('accountPasswordLineEdit'):getText() - EnterGame.host = enterGame:getChildById('serverHostLineEdit'):getText() - EnterGame.port = tonumber(enterGame:getChildById('serverPortLineEdit'):getText()) + EnterGame.account = enterGame:getChildById('accountNameTextEdit'):getText() + EnterGame.password = enterGame:getChildById('accountPasswordTextEdit'):getText() + EnterGame.host = enterGame:getChildById('serverHostTextEdit'):getText() + EnterGame.port = tonumber(enterGame:getChildById('serverPortTextEdit'):getText()) EnterGame.hide() Settings.set('host', EnterGame.host) diff --git a/modules/client_entergame/entergame.otui b/modules/client_entergame/entergame.otui index bf60297c..756786d6 100644 --- a/modules/client_entergame/entergame.otui +++ b/modules/client_entergame/entergame.otui @@ -10,8 +10,8 @@ MainWindow anchors.left: parent.left anchors.top: parent.top - LineEdit - id: accountNameLineEdit + TextEdit + id: accountNameTextEdit anchors.left: parent.left anchors.right: parent.right anchors.top: prev.bottom @@ -23,8 +23,8 @@ MainWindow anchors.top: prev.bottom margin-top: 8 - PasswordLineEdit - id: accountPasswordLineEdit + PasswordTextEdit + id: accountPasswordTextEdit anchors.left: parent.left anchors.right: parent.right anchors.top: prev.bottom @@ -38,8 +38,8 @@ MainWindow anchors.top: prev.bottom margin-top: 8 - LineEdit - id: serverHostLineEdit + TextEdit + id: serverHostTextEdit tooltip: |- Make sure that your client uses the correct game protocol version @@ -56,8 +56,8 @@ MainWindow anchors.top: serverLabel.top margin-left: 10 - LineEdit - id: serverPortLineEdit + TextEdit + id: serverPortTextEdit text: 7171 anchors.left: portLabel.left anchors.right: portLabel.right diff --git a/modules/client_terminal/terminal.lua b/modules/client_terminal/terminal.lua index 69d3dfa4..5ca9c0b1 100644 --- a/modules/client_terminal/terminal.lua +++ b/modules/client_terminal/terminal.lua @@ -13,7 +13,7 @@ local terminalWindow local terminalButton local logLocked = false local commandEnv = newenv() -local commandLineEdit +local commandTextEdit local terminalBuffer local commandHistory = { } local currentHistoryIndex = 0 @@ -25,18 +25,18 @@ local function navigateCommand(step) currentHistoryIndex = math.min(math.max(currentHistoryIndex + step, 0), numCommands) if currentHistoryIndex > 0 then local command = commandHistory[numCommands - currentHistoryIndex + 1] - commandLineEdit:setText(command) + commandTextEdit:setText(command) else - commandLineEdit:clearText() + commandTextEdit:clearText() end end end local function completeCommand() - local cursorPos = commandLineEdit:getCursorPos() + local cursorPos = commandTextEdit:getCursorPos() if cursorPos == 0 then return end - local commandBegin = commandLineEdit:getText():sub(1, cursorPos) + local commandBegin = commandTextEdit:getText():sub(1, cursorPos) local possibleCommands = {} -- create a list containing all globals @@ -52,7 +52,7 @@ local function completeCommand() -- complete command with one match if #possibleCommands == 1 then - commandLineEdit:setText(possibleCommands[1]) + commandTextEdit:setText(possibleCommands[1]) -- show command matches elseif #possibleCommands > 0 then print('>> ' .. commandBegin) @@ -75,7 +75,7 @@ local function completeCommand() commandBegin = expandedComplete end end - commandLineEdit:setText(commandBegin) + commandTextEdit:setText(commandBegin) for i,v in ipairs(possibleCommands) do print(v) @@ -84,11 +84,11 @@ local function completeCommand() end local function doCommand() - local currentCommand = commandLineEdit:getText() + local currentCommand = commandTextEdit:getText() Terminal.executeCommand(currentCommand) - if commandLineEdit then - commandLineEdit:clearText() + if commandTextEdit then + commandTextEdit:clearText() end return true end @@ -133,11 +133,11 @@ function Terminal.init() commandHistory = Settings.getList('terminal-history') - commandLineEdit = terminalWindow:getChildById('commandLineEdit') - Keyboard.bindKeyPress('Up', function() navigateCommand(1) end, commandLineEdit) - Keyboard.bindKeyPress('Down', function() navigateCommand(-1) end, commandLineEdit) - Keyboard.bindKeyDown('Tab', completeCommand, commandLineEdit) - Keyboard.bindKeyDown('Enter', doCommand, commandLineEdit) + commandTextEdit = terminalWindow:getChildById('commandTextEdit') + Keyboard.bindKeyPress('Up', function() navigateCommand(1) end, commandTextEdit) + Keyboard.bindKeyPress('Down', function() navigateCommand(-1) end, commandTextEdit) + Keyboard.bindKeyDown('Tab', completeCommand, commandTextEdit) + Keyboard.bindKeyDown('Enter', doCommand, commandTextEdit) Keyboard.bindKeyDown('Escape', Terminal.hide, terminalWindow) terminalBuffer = terminalWindow:getChildById('terminalBuffer') @@ -151,7 +151,7 @@ function Terminal.terminate() g_logger.setOnLog(nil) terminalButton:destroy() terminalButton = nil - commandLineEdit = nil + commandTextEdit = nil terminalBuffer = nil terminalWindow:destroy() terminalWindow = nil diff --git a/modules/client_terminal/terminal.otui b/modules/client_terminal/terminal.otui index 7d306e39..c711712b 100644 --- a/modules/client_terminal/terminal.otui +++ b/modules/client_terminal/terminal.otui @@ -32,8 +32,8 @@ UIWindow font: terminus-14px-bold text: >> - UILineEdit - id: commandLineEdit + UITextEdit + id: commandTextEdit height: 16 anchors.bottom: parent.bottom anchors.left: commandSymbolLabel.right diff --git a/modules/core_lib/widgets/uispinbox.lua b/modules/core_lib/widgets/uispinbox.lua index 68e4e11f..e805f119 100644 --- a/modules/core_lib/widgets/uispinbox.lua +++ b/modules/core_lib/widgets/uispinbox.lua @@ -1,4 +1,4 @@ -UISpinBox = extends(UILineEdit) +UISpinBox = extends(UITextEdit) function UISpinBox.create() local spinbox = UISpinBox.internalCreate() diff --git a/modules/core_styles/core_styles.otmod b/modules/core_styles/core_styles.otmod index bc2ed829..e45e903b 100644 --- a/modules/core_styles/core_styles.otmod +++ b/modules/core_styles/core_styles.otmod @@ -18,7 +18,7 @@ Module importStyle 'styles/labels.otui' importStyle 'styles/panels.otui' importStyle 'styles/separators.otui' - importStyle 'styles/lineedits.otui' + importStyle 'styles/textedits.otui' importStyle 'styles/checkboxes.otui' importStyle 'styles/progressbars.otui' importStyle 'styles/tabbars.otui' diff --git a/modules/core_styles/styles/textedits.otui b/modules/core_styles/styles/textedits.otui new file mode 100644 index 00000000..a3e2fe60 --- /dev/null +++ b/modules/core_styles/styles/textedits.otui @@ -0,0 +1,17 @@ +TextEdit < UITextEdit + font: verdana-11px-antialised + color: #aaaaaa + size: 86 20 + text-offset: 0 3 + text-margin: 3 + image-source: /core_styles/styles/images/panel_flat.png + image-border: 1 + + $disabled: + color: #aaaaaa88 + +PasswordTextEdit < TextEdit + text-hidden: true + +MultilineTextEdit < TextEdit + multiline: true diff --git a/modules/game_console/channelswindow.otui b/modules/game_console/channelswindow.otui index e118f42a..1612bb16 100644 --- a/modules/game_console/channelswindow.otui +++ b/modules/game_console/channelswindow.otui @@ -31,7 +31,7 @@ MainWindow text-align: center margin-bottom: 2 - LineEdit + TextEdit id: openPrivateChannelWith anchors.left: parent.left anchors.right: parent.right diff --git a/modules/game_console/console.lua b/modules/game_console/console.lua index 384d0e08..1f0fb9c8 100644 --- a/modules/game_console/console.lua +++ b/modules/game_console/console.lua @@ -45,7 +45,7 @@ local SayModes = { local consolePanel local consoleContentPanel local consoleTabBar -local consoleLineEdit +local consoleTextEdit local channels local messageHistory = { } local currentMessageIndex = 0 @@ -60,9 +60,9 @@ local function navigateMessageHistory(step) currentMessageIndex = math.min(math.max(currentMessageIndex + step, 0), numCommands) if currentMessageIndex > 0 then local command = messageHistory[numCommands - currentMessageIndex + 1] - consoleLineEdit:setText(command) + consoleTextEdit:setText(command) else - consoleLineEdit:clearText() + consoleTextEdit:clearText() end end end @@ -159,7 +159,7 @@ function Console.init() onGameEnd = Console.clear }) consolePanel = displayUI('console.otui', GameInterface.getBottomPanel()) - consoleLineEdit = consolePanel:getChildById('consoleLineEdit') + consoleTextEdit = consolePanel:getChildById('consoleTextEdit') consoleContentPanel = consolePanel:getChildById('consoleContentPanel') consoleTabBar = consolePanel:getChildById('consoleTabBar') consoleTabBar:setContentWidget(consoleContentPanel) @@ -208,7 +208,7 @@ function Console.terminate() consolePanel:destroy() consolePanel = nil - consoleLineEdit = nil + consoleTextEdit = nil consoleContentPanel = nil consoleTabBar = nil @@ -233,7 +233,7 @@ function Console.clear() consoleTabBar:removeTab(npcTab) end - consoleLineEdit:clearText() + consoleTextEdit:clearText() if channelsWindow then channelsWindow:destroy() @@ -241,8 +241,8 @@ function Console.clear() end end -function Console.setLineEditText(text) - consoleLineEdit:setText(text) +function Console.setTextEditText(text) + consoleTextEdit:setText(text) end function Console.addTab(name, focus) @@ -339,9 +339,9 @@ function Console.addTabText(text, speaktype, tab) end function Console.sendCurrentMessage() - local message = consoleLineEdit:getText() + local message = consoleTextEdit:getText() if #message == 0 then return end - consoleLineEdit:clearText() + consoleTextEdit:clearText() -- get current channel local tab = Console.getCurrentTab() @@ -365,7 +365,7 @@ function Console.sendCurrentMessage() if chatCommandInitial == chatCommandEnd then chatCommandPrivateRepeat = false if chatCommandInitial == "*" then - consoleLineEdit:setText('*'.. chatCommandPrivate .. '* ') + consoleTextEdit:setText('*'.. chatCommandPrivate .. '* ') end message = chatCommandMessage:trim() chatCommandPrivateReady = true diff --git a/modules/game_console/console.otui b/modules/game_console/console.otui index 33aaf4a6..aa41aa48 100644 --- a/modules/game_console/console.otui +++ b/modules/game_console/console.otui @@ -88,7 +88,7 @@ Panel anchors.top: prev.bottom anchors.left: parent.left anchors.right: parent.right - anchors.bottom: consoleLineEdit.top + anchors.bottom: consoleTextEdit.top margin-left: 6 margin-right: 6 margin-bottom: 4 @@ -108,8 +108,8 @@ Panel margin-bottom: 6 @onClick: Console.sayModeChange() - LineEdit - id: consoleLineEdit + TextEdit + id: consoleTextEdit anchors.left: sayModeButton.right anchors.right: parent.right anchors.bottom: parent.bottom diff --git a/modules/game_hotkeys/hotkeys_manager.lua b/modules/game_hotkeys/hotkeys_manager.lua index e611c6d1..22cc4906 100644 --- a/modules/game_hotkeys/hotkeys_manager.lua +++ b/modules/game_hotkeys/hotkeys_manager.lua @@ -290,7 +290,7 @@ function HotkeysManager.call(keyCombo) if hotKey.autoSend then g_game.talk(hotKey.value) else - Console.setLineEditText(hotKey.value) + Console.setTextEditText(hotKey.value) end elseif hotKey.itemId ~= nil then if hotKey.useType == HOTKEY_MANAGER_USEONSELF then diff --git a/modules/game_hotkeys/hotkeys_manager.otui b/modules/game_hotkeys/hotkeys_manager.otui index 52ba6325..122b044b 100644 --- a/modules/game_hotkeys/hotkeys_manager.otui +++ b/modules/game_hotkeys/hotkeys_manager.otui @@ -68,7 +68,7 @@ MainWindow anchors.top: prev.bottom margin-top: 20 - LineEdit + TextEdit id: hotkeyText enabled: false anchors.left: parent.left diff --git a/modules/game_viplist/addvip.otui b/modules/game_viplist/addvip.otui index 8c79afa0..4b0c1712 100644 --- a/modules/game_viplist/addvip.otui +++ b/modules/game_viplist/addvip.otui @@ -10,7 +10,7 @@ MainWindow anchors.left: parent.left anchors.right: parent.right - LineEdit + TextEdit id: name anchors.top: prev.bottom anchors.left: parent.left diff --git a/src/framework/CMakeLists.txt b/src/framework/CMakeLists.txt index df2c51c5..01e3d4f7 100644 --- a/src/framework/CMakeLists.txt +++ b/src/framework/CMakeLists.txt @@ -209,7 +209,7 @@ SET(framework_SOURCES ${framework_SOURCES} ${CMAKE_CURRENT_LIST_DIR}/ui/uiwidgetimage.cpp ${CMAKE_CURRENT_LIST_DIR}/ui/uiwidgettext.cpp ${CMAKE_CURRENT_LIST_DIR}/ui/uiwidgetbasestyle.cpp - ${CMAKE_CURRENT_LIST_DIR}/ui/uilineedit.cpp + ${CMAKE_CURRENT_LIST_DIR}/ui/uitextedit.cpp ${CMAKE_CURRENT_LIST_DIR}/ui/uiboxlayout.cpp ${CMAKE_CURRENT_LIST_DIR}/ui/uihorizontallayout.cpp ${CMAKE_CURRENT_LIST_DIR}/ui/uiverticallayout.cpp diff --git a/src/framework/graphics/font.cpp b/src/framework/graphics/font.cpp index 687f34ba..6f77eaab 100644 --- a/src/framework/graphics/font.cpp +++ b/src/framework/graphics/font.cpp @@ -45,6 +45,9 @@ void Font::load(const OTMLNodePtr& fontNode) } else calculateGlyphsWidthsAutomatically(glyphSize); + // new line actually has a size that will be useful in multiline algorithm + m_glyphsSize[(uchar)'\n'] = Size(1, m_glyphHeight); + // read custom widths if(OTMLNodePtr node = fontNode->get("glyph-widths")) { for(const OTMLNodePtr& child : node->children()) diff --git a/src/framework/luafunctions.cpp b/src/framework/luafunctions.cpp index 8ef58841..2a6b3450 100644 --- a/src/framework/luafunctions.cpp +++ b/src/framework/luafunctions.cpp @@ -363,27 +363,29 @@ void Application::registerLuaFunctions() g_lua.bindClassMemberFunction("centerIn", &UIAnchorLayout::centerIn); g_lua.bindClassMemberFunction("fill", &UIAnchorLayout::fill); - // UILineEdit - g_lua.registerClass(); - g_lua.bindClassStaticFunction("create", []{ return UILineEditPtr(new UILineEdit); } ); - g_lua.bindClassMemberFunction("setTextHorizontalMargin", &UILineEdit::setTextHorizontalMargin); - g_lua.bindClassMemberFunction("setCursorPos", &UILineEdit::setCursorPos); - g_lua.bindClassMemberFunction("setCursorEnabled", &UILineEdit::setCursorEnabled); - g_lua.bindClassMemberFunction("setTextHidden", &UILineEdit::setTextHidden); - g_lua.bindClassMemberFunction("setAlwaysActive", &UILineEdit::setAlwaysActive); - g_lua.bindClassMemberFunction("setValidCharacters", &UILineEdit::setValidCharacters); - g_lua.bindClassMemberFunction("setShiftNavigation", &UILineEdit::setShiftNavigation); - g_lua.bindClassMemberFunction("moveCursor", &UILineEdit::moveCursor); - g_lua.bindClassMemberFunction("appendText", &UILineEdit::appendText); - g_lua.bindClassMemberFunction("removeCharacter", &UILineEdit::removeCharacter); - g_lua.bindClassMemberFunction("getDisplayedText", &UILineEdit::getDisplayedText); - g_lua.bindClassMemberFunction("getTextPos", &UILineEdit::getTextPos); - g_lua.bindClassMemberFunction("getTextHorizontalMargin", &UILineEdit::getTextHorizontalMargin); - g_lua.bindClassMemberFunction("getCursorPos", &UILineEdit::getCursorPos); - g_lua.bindClassMemberFunction("isCursorEnabled", &UILineEdit::isCursorEnabled); - g_lua.bindClassMemberFunction("isAlwaysActive", &UILineEdit::isAlwaysActive); - g_lua.bindClassMemberFunction("isTextHidden", &UILineEdit::isTextHidden); - g_lua.bindClassMemberFunction("isShiftNavigation", &UILineEdit::isShiftNavigation); + // UITextEdit + g_lua.registerClass(); + g_lua.bindClassStaticFunction("create", []{ return UITextEditPtr(new UITextEdit); } ); + g_lua.bindClassMemberFunction("setTextHorizontalMargin", &UITextEdit::setTextHorizontalMargin); + g_lua.bindClassMemberFunction("setCursorPos", &UITextEdit::setCursorPos); + g_lua.bindClassMemberFunction("setCursorEnabled", &UITextEdit::setCursorEnabled); + g_lua.bindClassMemberFunction("setTextHidden", &UITextEdit::setTextHidden); + g_lua.bindClassMemberFunction("setAlwaysActive", &UITextEdit::setAlwaysActive); + g_lua.bindClassMemberFunction("setValidCharacters", &UITextEdit::setValidCharacters); + g_lua.bindClassMemberFunction("setShiftNavigation", &UITextEdit::setShiftNavigation); + g_lua.bindClassMemberFunction("setMultiline", &UITextEdit::setMultiline); + g_lua.bindClassMemberFunction("moveCursor", &UITextEdit::moveCursor); + g_lua.bindClassMemberFunction("appendText", &UITextEdit::appendText); + g_lua.bindClassMemberFunction("removeCharacter", &UITextEdit::removeCharacter); + g_lua.bindClassMemberFunction("getDisplayedText", &UITextEdit::getDisplayedText); + g_lua.bindClassMemberFunction("getTextPos", &UITextEdit::getTextPos); + g_lua.bindClassMemberFunction("getTextHorizontalMargin", &UITextEdit::getTextHorizontalMargin); + g_lua.bindClassMemberFunction("getCursorPos", &UITextEdit::getCursorPos); + g_lua.bindClassMemberFunction("isCursorEnabled", &UITextEdit::isCursorEnabled); + g_lua.bindClassMemberFunction("isAlwaysActive", &UITextEdit::isAlwaysActive); + g_lua.bindClassMemberFunction("isTextHidden", &UITextEdit::isTextHidden); + g_lua.bindClassMemberFunction("isShiftNavigation", &UITextEdit::isShiftNavigation); + g_lua.bindClassMemberFunction("isMultiline", &UITextEdit::isMultiline); // UIFrameCounter g_lua.registerClass(); diff --git a/src/framework/ui/declarations.h b/src/framework/ui/declarations.h index 94f8f997..74adaa5c 100644 --- a/src/framework/ui/declarations.h +++ b/src/framework/ui/declarations.h @@ -27,7 +27,7 @@ class UIManager; class UIWidget; -class UILineEdit; +class UITextEdit; class UIFrameCounter; class UILayout; class UIBoxLayout; @@ -39,7 +39,7 @@ class UIAnchorLayout; typedef std::shared_ptr UIWidgetPtr; typedef std::weak_ptr UIWidgetWeakPtr; -typedef std::shared_ptr UILineEditPtr; +typedef std::shared_ptr UITextEditPtr; typedef std::shared_ptr UIFrameCounterPtr; typedef std::shared_ptr UILayoutPtr; typedef std::shared_ptr UIBoxLayoutPtr; diff --git a/src/framework/ui/ui.h b/src/framework/ui/ui.h index 909e1c26..bfbb4007 100644 --- a/src/framework/ui/ui.h +++ b/src/framework/ui/ui.h @@ -25,7 +25,7 @@ #include "uimanager.h" #include "uiwidget.h" -#include "uilineedit.h" +#include "uitextedit.h" #include "uiframecounter.h" #include "uilayout.h" #include "uihorizontallayout.h" diff --git a/src/framework/ui/uitextedit.cpp b/src/framework/ui/uitextedit.cpp new file mode 100644 index 00000000..32c0cb3d --- /dev/null +++ b/src/framework/ui/uitextedit.cpp @@ -0,0 +1,512 @@ +/* + * Copyright (c) 2010-2012 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "uitextedit.h" +#include +#include +#include +#include +#include + +UITextEdit::UITextEdit() +{ + m_cursorPos = 0; + m_textAlign = Fw::AlignTopLeft; + m_startRenderPos = 0; + m_textHorizontalMargin = 0; + m_textHidden = false; + m_alwaysActive = false; + m_shiftNavigation = false; + m_multiline = false; + blinkCursor(); +} + +void UITextEdit::drawSelf() +{ + drawBackground(m_rect); + drawBorder(m_rect); + drawImage(m_rect); + drawIcon(m_rect); + + //TODO: text rendering could be much optimized by using vertex buffer or caching the render into a texture + + int textLength = m_text.length(); + const TexturePtr& texture = m_font->getTexture(); + + g_painter.setColor(m_color); + for(int i=0;i= 0) { + assert(m_cursorPos <= textLength); + // draw every 333ms + const int delay = 333; + if(g_clock.ticksElapsed(m_cursorTicks) <= delay) { + Rect cursorRect; + // when cursor is at 0 or is the first visible element + if(m_cursorPos == 0 || m_cursorPos == m_startRenderPos) + cursorRect = Rect(m_drawArea.left()-1, m_drawArea.top(), 1, m_font->getGlyphHeight()); + else + cursorRect = Rect(m_glyphsCoords[m_cursorPos-1].right(), m_glyphsCoords[m_cursorPos-1].top(), 1, m_font->getGlyphHeight()); + g_painter.drawFilledRect(cursorRect); + } else if(g_clock.ticksElapsed(m_cursorTicks) >= 2*delay) { + m_cursorTicks = g_clock.ticks(); + } + } +} + +void UITextEdit::update() +{ + std::string text = getDisplayedText(); + int textLength = text.length(); + + // prevent glitches + if(m_rect.isEmpty()) + return; + + // map glyphs positions + Size textBoxSize; + const std::vector& glyphsPositions = m_font->calculateGlyphsPositions(text, m_textAlign, &textBoxSize); + const Rect *glyphsTextureCoords = m_font->getGlyphsTextureCoords(); + const Size *glyphsSize = m_font->getGlyphsSize(); + int glyph; + + // resize just on demand + if(textLength > (int)m_glyphsCoords.size()) { + m_glyphsCoords.resize(textLength); + m_glyphsTexCoords.resize(textLength); + } + + // readjust start view area based on cursor position + if(m_cursorPos >= 0 && textLength > 0) { + assert(m_cursorPos <= textLength); + if(m_cursorPos < m_startRenderPos) // cursor is before the previous first rendered glyph, so we need to update + { + m_startInternalPos.x = glyphsPositions[m_cursorPos].x; + m_startInternalPos.y = glyphsPositions[m_cursorPos].y - m_font->getYOffset(); + m_startRenderPos = m_cursorPos; + } else if(m_cursorPos > m_startRenderPos || // cursor is after the previous first rendered glyph + (m_cursorPos == m_startRenderPos && textLength == m_cursorPos)) // cursor is at the previous rendered element, and is the last text element + { + Rect virtualRect(m_startInternalPos, m_rect.size() - Size(2*m_textHorizontalMargin, 0) ); // previous rendered virtual rect + int pos = m_cursorPos - 1; // element before cursor + glyph = (uchar)text[pos]; // glyph of the element before cursor + Rect glyphRect(glyphsPositions[pos], glyphsSize[glyph]); + + // if the cursor is not on the previous rendered virtual rect we need to update it + if(!virtualRect.contains(glyphRect.topLeft()) || !virtualRect.contains(glyphRect.bottomRight())) { + // calculate where is the first glyph visible + Point startGlyphPos; + startGlyphPos.y = std::max(glyphRect.bottom() - virtualRect.height(), 0); + startGlyphPos.x = std::max(glyphRect.right() - virtualRect.width(), 0); + + // find that glyph + for(pos = 0; pos < textLength; ++pos) { + glyph = (uchar)text[pos]; + glyphRect = Rect(glyphsPositions[pos], glyphsSize[glyph]); + glyphRect.setTop(std::max(glyphRect.top() - m_font->getYOffset() - m_font->getGlyphSpacing().height(), 0)); + glyphRect.setLeft(std::max(glyphRect.left() - m_font->getGlyphSpacing().width(), 0)); + + // first glyph entirely visible found + if(glyphRect.topLeft() >= startGlyphPos) { + m_startInternalPos.x = glyphsPositions[pos].x; + m_startInternalPos.y = glyphsPositions[pos].y - m_font->getYOffset(); + m_startRenderPos = pos; + break; + } + } + } + } + } else { + m_startInternalPos = Point(0,0); + } + + Rect textScreenCoords = m_rect; + textScreenCoords.expandLeft(-m_textHorizontalMargin); + textScreenCoords.expandRight(-m_textHorizontalMargin); + textScreenCoords.expandLeft(-m_textOffset.x); + textScreenCoords.translate(0, m_textOffset.y); + m_drawArea = textScreenCoords; + + if(m_textAlign & Fw::AlignBottom) { + m_drawArea.translate(0, textScreenCoords.height() - textBoxSize.height()); + } else if(m_textAlign & Fw::AlignVerticalCenter) { + m_drawArea.translate(0, (textScreenCoords.height() - textBoxSize.height()) / 2); + } else { // AlignTop + } + + if(m_textAlign & Fw::AlignRight) { + m_drawArea.translate(textScreenCoords.width() - textBoxSize.width(), 0); + } else if(m_textAlign & Fw::AlignHorizontalCenter) { + m_drawArea.translate((textScreenCoords.width() - textBoxSize.width()) / 2, 0); + } else { // AlignLeft + + } + + for(int i = 0; i < textLength; ++i) { + glyph = (uchar)text[i]; + m_glyphsCoords[i].clear(); + + // skip invalid glyphs + if(glyph < 32 && glyph != (uchar)'\n') + continue; + + // calculate initial glyph rect and texture coords + Rect glyphScreenCoords(glyphsPositions[i], glyphsSize[glyph]); + Rect glyphTextureCoords = glyphsTextureCoords[glyph]; + + // first translate to align position + if(m_textAlign & Fw::AlignBottom) { + glyphScreenCoords.translate(0, textScreenCoords.height() - textBoxSize.height()); + } else if(m_textAlign & Fw::AlignVerticalCenter) { + glyphScreenCoords.translate(0, (textScreenCoords.height() - textBoxSize.height()) / 2); + } else { // AlignTop + // nothing to do + } + + if(m_textAlign & Fw::AlignRight) { + glyphScreenCoords.translate(textScreenCoords.width() - textBoxSize.width(), 0); + } else if(m_textAlign & Fw::AlignHorizontalCenter) { + glyphScreenCoords.translate((textScreenCoords.width() - textBoxSize.width()) / 2, 0); + } else { // AlignLeft + // nothing to do + } + + // only render glyphs that are after startRenderPosition + if(glyphScreenCoords.bottom() < m_startInternalPos.y || glyphScreenCoords.right() < m_startInternalPos.x) + continue; + + // bound glyph topLeft to startRenderPosition + if(glyphScreenCoords.top() < m_startInternalPos.y) { + glyphTextureCoords.setTop(glyphTextureCoords.top() + (m_startInternalPos.y - glyphScreenCoords.top())); + glyphScreenCoords.setTop(m_startInternalPos.y); + } + if(glyphScreenCoords.left() < m_startInternalPos.x) { + glyphTextureCoords.setLeft(glyphTextureCoords.left() + (m_startInternalPos.x - glyphScreenCoords.left())); + glyphScreenCoords.setLeft(m_startInternalPos.x); + } + + // subtract startInternalPos + glyphScreenCoords.translate(-m_startInternalPos); + + // translate rect to screen coords + glyphScreenCoords.translate(textScreenCoords.topLeft()); + + // only render if glyph rect is visible on screenCoords + if(!textScreenCoords.intersects(glyphScreenCoords)) + continue; + + // bound glyph bottomRight to screenCoords bottomRight + if(glyphScreenCoords.bottom() > textScreenCoords.bottom()) { + glyphTextureCoords.setBottom(glyphTextureCoords.bottom() + (textScreenCoords.bottom() - glyphScreenCoords.bottom())); + glyphScreenCoords.setBottom(textScreenCoords.bottom()); + } + if(glyphScreenCoords.right() > textScreenCoords.right()) { + glyphTextureCoords.setRight(glyphTextureCoords.right() + (textScreenCoords.right() - glyphScreenCoords.right())); + glyphScreenCoords.setRight(textScreenCoords.right()); + } + + // render glyph + m_glyphsCoords[i] = glyphScreenCoords; + m_glyphsTexCoords[i] = glyphTextureCoords; + } +} + +void UITextEdit::setTextHorizontalMargin(int margin) +{ + m_textHorizontalMargin = margin; + update(); +} + +void UITextEdit::setCursorPos(int pos) +{ + if(pos != m_cursorPos) { + if(pos < 0) + m_cursorPos = 0; + else if((uint)pos >= m_text.length()) + m_cursorPos = m_text.length(); + else + m_cursorPos = pos; + update(); + } +} + +void UITextEdit::setCursorEnabled(bool enable) +{ + if(enable) { + m_cursorPos = 0; + blinkCursor(); + } else + m_cursorPos = -1; + update(); +} + +void UITextEdit::setTextHidden(bool hidden) +{ + m_textHidden = true; + update(); +} + +void UITextEdit::setAlwaysActive(bool enable) +{ + m_alwaysActive = enable; +} + +void UITextEdit::appendText(std::string text) +{ + if(m_cursorPos >= 0) { + // replace characters that are now allowed + if(!m_multiline) + boost::replace_all(text, "\n", ""); + boost::replace_all(text, "\r", " "); + + if(text.length() > 0) { + + // only ignore text append if it contains invalid characters + if(m_validCharacters.size() > 0) { + for(uint i = 0; i < text.size(); ++i) { + if(m_validCharacters.find(text[i]) == std::string::npos) + return; + } + } + + std::string oldText = m_text; + m_text.insert(m_cursorPos, text); + m_cursorPos += text.length(); + blinkCursor(); + update(); + UIWidget::onTextChange(m_text, oldText); + } + } +} + +void UITextEdit::appendCharacter(char c) +{ + if((c == '\n' && !m_multiline) || c == '\r') + return; + + if(m_cursorPos >= 0) { + if(m_validCharacters.size() > 0 && m_validCharacters.find(c) == std::string::npos) + return; + + std::string tmp; + tmp = c; + std::string oldText = m_text; + m_text.insert(m_cursorPos, tmp); + m_cursorPos++; + blinkCursor(); + update(); + UIWidget::onTextChange(m_text, oldText); + } +} + +void UITextEdit::removeCharacter(bool right) +{ + std::string oldText = m_text; + if(m_cursorPos >= 0 && m_text.length() > 0) { + if((uint)m_cursorPos >= m_text.length()) { + m_text.erase(m_text.begin() + (--m_cursorPos)); + } else { + if(right) + m_text.erase(m_text.begin() + m_cursorPos); + else if(m_cursorPos > 0) + m_text.erase(m_text.begin() + --m_cursorPos); + } + blinkCursor(); + update(); + UIWidget::onTextChange(m_text, oldText); + } +} + +void UITextEdit::moveCursor(bool right) +{ + if(right) { + if((uint)m_cursorPos+1 <= m_text.length()) { + m_cursorPos++; + blinkCursor(); + } + } else { + if(m_cursorPos-1 >= 0) { + m_cursorPos--; + blinkCursor(); + } + } + update(); +} + +int UITextEdit::getTextPos(Point pos) +{ + int textLength = m_text.length(); + + // find any glyph that is actually on the + int candidatePos = -1; + for(int i=0;igetYOffset() + m_font->getGlyphSpacing().height()); + clickGlyphRect.expandLeft(m_font->getGlyphSpacing().width()+1); + if(clickGlyphRect.contains(pos)) + return i; + else if(pos.y >= clickGlyphRect.top() && pos.y <= clickGlyphRect.bottom()) { + if(pos.x <= clickGlyphRect.left()) + candidatePos = i; + else if(pos.x >= clickGlyphRect.right()) + candidatePos = i+1; + } + } + return candidatePos; +} + +std::string UITextEdit::getDisplayedText() +{ + if(m_textHidden) + return std::string(m_text.length(), '*'); + else + return m_text; +} + +void UITextEdit::onTextChange(const std::string& text, const std::string& oldText) +{ + m_cursorPos = text.length(); + blinkCursor(); + update(); + UIWidget::onTextChange(text, oldText); +} + +void UITextEdit::onFontChange(const std::string& font) +{ + update(); + UIWidget::onFontChange(font); +} + +void UITextEdit::onStyleApply(const std::string& styleName, const OTMLNodePtr& styleNode) +{ + UIWidget::onStyleApply(styleName, styleNode); + + for(const OTMLNodePtr& node : styleNode->children()) { + if(node->tag() == "text") { + setText(node->value()); + setCursorPos(m_text.length()); + } else if(node->tag() == "text-hidden") + setTextHidden(node->value()); + else if(node->tag() == "text-margin") + setTextHorizontalMargin(node->value()); + else if(node->tag() == "always-active") + setAlwaysActive(node->value()); + else if(node->tag() == "shift-navigation") + setShiftNavigation(node->value()); + else if(node->tag() == "multiline") + setMultiline(node->value()); + } +} + +void UITextEdit::onGeometryChange(const Rect& oldRect, const Rect& newRect) +{ + update(); + UIWidget::onGeometryChange(oldRect, newRect); +} + +void UITextEdit::onFocusChange(bool focused, Fw::FocusReason reason) +{ + if(focused && !m_alwaysActive) { + if(reason == Fw::KeyboardFocusReason) + setCursorPos(m_text.length()); + else + blinkCursor(); + } + UIWidget::onFocusChange(focused, reason); +} + +bool UITextEdit::onKeyPress(uchar keyCode, int keyboardModifiers, int autoRepeatTicks) +{ + if(UIWidget::onKeyPress(keyCode, keyboardModifiers, autoRepeatTicks)) + return true; + + if(keyboardModifiers == Fw::KeyboardNoModifier) { + if(keyCode == Fw::KeyDelete) { // erase right character + removeCharacter(true); + return true; + } else if(keyCode == Fw::KeyBackspace) { // erase left character { + removeCharacter(false); + return true; + } else if(keyCode == Fw::KeyRight && !m_shiftNavigation) { // move cursor right + moveCursor(true); + return true; + } else if(keyCode == Fw::KeyLeft && !m_shiftNavigation) { // move cursor left + moveCursor(false); + return true; + } else if(keyCode == Fw::KeyHome) { // move cursor to first character + setCursorPos(0); + return true; + } else if(keyCode == Fw::KeyEnd) { // move cursor to last character + setCursorPos(m_text.length()); + return true; + } else if(keyCode == Fw::KeyTab && !m_shiftNavigation) { + if(UIWidgetPtr parent = getParent()) + parent->focusNextChild(Fw::KeyboardFocusReason); + return true; + } else if(keyCode == Fw::KeyEnter && m_multiline) { + appendCharacter('\n'); + return true; + } else if(keyCode == Fw::KeyUp && !m_shiftNavigation && m_multiline) { + + } else if(keyCode == Fw::KeyDown && !m_shiftNavigation && m_multiline) { + + } + } else if(keyboardModifiers == Fw::KeyboardCtrlModifier) { + if(keyCode == Fw::KeyV) { + appendText(g_window.getClipboardText()); + return true; + } + } else if(keyboardModifiers == Fw::KeyboardShiftModifier) { + if(keyCode == Fw::KeyRight && m_shiftNavigation) { // move cursor right + moveCursor(true); + return true; + } else if(keyCode == Fw::KeyLeft && m_shiftNavigation) { // move cursor left + moveCursor(false); + return true; + } + } + + return false; +} + +bool UITextEdit::onKeyText(const std::string& keyText) +{ + appendText(keyText); + return true; +} + +bool UITextEdit::onMousePress(const Point& mousePos, Fw::MouseButton button) +{ + if(button == Fw::MouseLeftButton) { + int pos = getTextPos(mousePos); + if(pos >= 0) + setCursorPos(pos); + } + return true; +} + +void UITextEdit::blinkCursor() +{ + m_cursorTicks = g_clock.ticks(); +} diff --git a/src/framework/ui/uitextedit.h b/src/framework/ui/uitextedit.h new file mode 100644 index 00000000..413d0a76 --- /dev/null +++ b/src/framework/ui/uitextedit.h @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2010-2012 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef UILINEEDIT_H +#define UILINEEDIT_H + +#include "uiwidget.h" + +class UITextEdit : public UIWidget +{ +public: + UITextEdit(); + + virtual void drawSelf(); + +private: + void update(); + +public: + void setTextHorizontalMargin(int margin); + void setCursorPos(int pos); + void setCursorEnabled(bool enable); + void setTextHidden(bool hidden); + void setAlwaysActive(bool enable); + void setValidCharacters(const std::string validCharacters) { m_validCharacters = validCharacters; } + void setShiftNavigation(bool enable) { m_shiftNavigation = enable; } + void setMultiline(bool enable) { m_multiline = enable; } + + void moveCursor(bool right); + void appendText(std::string text); + void appendCharacter(char c); + void removeCharacter(bool right); + + std::string getDisplayedText(); + int getTextPos(Point pos); + int getTextHorizontalMargin() { return m_textHorizontalMargin; } + int getCursorPos() { return m_cursorPos; } + bool isCursorEnabled() { return m_cursorPos != -1; } + bool isAlwaysActive() { return m_alwaysActive; } + bool isTextHidden() { return m_textHidden; } + bool isShiftNavigation() { return m_shiftNavigation; } + bool isMultiline() { return m_multiline; } + +protected: + virtual void onTextChange(const std::string& text, const std::string& oldText); + virtual void onFontChange(const std::string& font); + virtual void onStyleApply(const std::string& styleName, const OTMLNodePtr& styleNode); + virtual void onGeometryChange(const Rect& oldRect, const Rect& newRect); + virtual void onFocusChange(bool focused, Fw::FocusReason reason); + virtual bool onKeyText(const std::string& keyText); + virtual bool onKeyPress(uchar keyCode, int keyboardModifiers, int autoRepeatTicks); + virtual bool onMousePress(const Point& mousePos, Fw::MouseButton button); + +private: + void blinkCursor(); + + Rect m_drawArea; + int m_cursorPos; + Point m_startInternalPos; + int m_startRenderPos; + ticks_t m_cursorTicks; + int m_textHorizontalMargin; + bool m_textHidden; + bool m_alwaysActive; + bool m_shiftNavigation; + bool m_multiline; + std::string m_validCharacters; + + std::vector m_glyphsCoords; + std::vector m_glyphsTexCoords; +}; + +#endif diff --git a/src/otclient/core/game.cpp b/src/otclient/core/game.cpp index d6eafed3..faa66826 100644 --- a/src/otclient/core/game.cpp +++ b/src/otclient/core/game.cpp @@ -47,6 +47,8 @@ void Game::resetGameStates() m_safeFight = true; m_followingCreature = nullptr; m_attackingCreature = nullptr; + m_containers.clear(); + m_worldName = ""; } void Game::processConnectionError(const boost::system::error_code& error)