From 2e753802184b8cb2942abe6d2e0759f9118cdbe0 Mon Sep 17 00:00:00 2001 From: Eduardo Bart Date: Wed, 16 Jan 2013 14:20:17 -0200 Subject: [PATCH] Reimplement text edit scrolling in C++ * And update some corelib APIs --- .../skins/default/styles/labels.otui | 1 - .../skins/default/styles/textedits.otui | 2 +- modules/corelib/const.lua | 10 +- modules/corelib/inputmessage.lua | 23 +- modules/corelib/mouse.lua | 7 + modules/corelib/outputmessage.lua | 33 ++- modules/corelib/table.lua | 18 ++ modules/corelib/ui/tooltip.lua | 2 +- modules/corelib/ui/uicombobox.lua | 22 ++ modules/corelib/ui/uiinputbox.lua | 114 ++++++++++ modules/corelib/ui/uipopupmenu.lua | 6 +- modules/corelib/ui/uiprogressbar.lua | 29 ++- modules/corelib/ui/uispinbox.lua | 22 +- modules/corelib/ui/uitabbar.lua | 3 + modules/corelib/ui/uitextedit.lua | 77 +++++++ modules/corelib/util.lua | 14 +- modules/game_textwindow/textwindow.lua | 139 +----------- modules/game_textwindow/textwindow.otui | 53 ++--- src/framework/luafunctions.cpp | 13 +- src/framework/ui/uitextedit.cpp | 208 ++++++++++-------- src/framework/ui/uitextedit.h | 29 ++- 21 files changed, 535 insertions(+), 290 deletions(-) create mode 100644 modules/corelib/ui/uiinputbox.lua create mode 100644 modules/corelib/ui/uitextedit.lua diff --git a/modules/client_skins/skins/default/styles/labels.otui b/modules/client_skins/skins/default/styles/labels.otui index 97d9ef2e..3d083bc3 100644 --- a/modules/client_skins/skins/default/styles/labels.otui +++ b/modules/client_skins/skins/default/styles/labels.otui @@ -10,7 +10,6 @@ FlatLabel < UILabel color: #aaaaaa size: 86 20 text-offset: 3 3 - text-margin: 3 image-source: /images/panel_flat.png image-border: 1 diff --git a/modules/client_skins/skins/default/styles/textedits.otui b/modules/client_skins/skins/default/styles/textedits.otui index f0d064ad..f07bc09c 100644 --- a/modules/client_skins/skins/default/styles/textedits.otui +++ b/modules/client_skins/skins/default/styles/textedits.otui @@ -3,7 +3,7 @@ TextEdit < UITextEdit color: #aaaaaa size: 86 20 text-offset: 0 3 - text-margin: 3 + padding: 3 image-source: /images/textedit.png image-border: 1 diff --git a/modules/corelib/const.lua b/modules/corelib/const.lua index fbce722f..00ffde65 100644 --- a/modules/corelib/const.lua +++ b/modules/corelib/const.lua @@ -301,7 +301,11 @@ KeyCodeDescs = { NetworkMessageTypes = { Boolean = 1, - Number = 2, - String = 3, - Table = 4 + U8 = 2, + U16 = 3, + U32 = 4, + U64 = 5, + NumberString = 6, + String = 7, + Table = 8 } diff --git a/modules/corelib/inputmessage.lua b/modules/corelib/inputmessage.lua index 29fd713b..ec668d8f 100644 --- a/modules/corelib/inputmessage.lua +++ b/modules/corelib/inputmessage.lua @@ -2,8 +2,16 @@ function InputMessage:getData() local dataType = self:getU8() if dataType == NetworkMessageTypes.Boolean then return numbertoboolean(self:getU8()) - elseif dataType == NetworkMessageTypes.Number then + elseif dataType == NetworkMessageTypes.U8 then + return self:getU8() + elseif dataType == NetworkMessageTypes.U16 then + return self:getU16() + elseif dataType == NetworkMessageTypes.U32 then + return self:getU32() + elseif dataType == NetworkMessageTypes.U64 then return self:getU64() + elseif dataType == NetworkMessageTypes.NumberString then + return tonumber(self:getString()) elseif dataType == NetworkMessageTypes.String then return self:getString() elseif dataType == NetworkMessageTypes.Table then @@ -16,11 +24,20 @@ end function InputMessage:getTable() local ret = {} - local size = self:getU32() + local size = self:getU16() for i=1,size do local index = self:getData() local value = self:getData() ret[index] = value end return ret -end \ No newline at end of file +end + +function InputMessage:getColor() + local color = {} + color.r = msg:getU8() + color.g = msg:getU8() + color.b = msg:getU8() + color.a = msg:getU8() + return color +end diff --git a/modules/corelib/mouse.lua b/modules/corelib/mouse.lua index 83d51c06..03e2603f 100644 --- a/modules/corelib/mouse.lua +++ b/modules/corelib/mouse.lua @@ -24,3 +24,10 @@ function g_mouse.bindPressMove(widget, callback) return true end }) end + +function g_mouse.bindPress(widget, callback) + connect(widget, { onMousePress = function(widget, mousePos, mouseButton) + callback(mousePos, mouseButton) + return true + end }) +end diff --git a/modules/corelib/outputmessage.lua b/modules/corelib/outputmessage.lua index 27d797da..04211580 100644 --- a/modules/corelib/outputmessage.lua +++ b/modules/corelib/outputmessage.lua @@ -3,8 +3,22 @@ function OutputMessage:addData(data) self:addU8(NetworkMessageTypes.Boolean) self:addU8(booleantonumber(data)) elseif type(data) == 'number' then - self:addU8(NetworkMessageTypes.Number) - self:addU64(data) + if math.isu8(data) then + self:addU8(NetworkMessageTypes.U8) + self:addU8(data) + elseif math.isu16(data) then + self:addU8(NetworkMessageTypes.U16) + self:addU16(data) + elseif math.isu32(data) then + self:addU8(NetworkMessageTypes.U32) + self:addU32(data) + elseif math.isu64(data) then + self:addU8(NetworkMessageTypes.U64) + self:addU64(data) + else -- negative or non integer numbers + self:addU8(NetworkMessageTypes.NumberString) + self:addString(tostring(data)) + end elseif type(data) == 'string' then self:addU8(NetworkMessageTypes.String) self:addString(data) @@ -19,9 +33,9 @@ end function OutputMessage:addTable(data) local size = 0 - -- reserve for size + -- reserve for size (should be addData, find a way to use it further) local sizePos = self:getWritePos() - self:addU32(size) + self:addU16(size) local sizeSize = self:getWritePos() - sizePos -- add values @@ -34,9 +48,16 @@ function OutputMessage:addTable(data) -- write size local currentPos = self:getWritePos() self:setWritePos(sizePos) - self:addU32(size) + self:addU16(size) -- fix msg size and go back to end self:setMessageSize(self:getMessageSize() - sizeSize) self:setWritePos(currentPos) -end \ No newline at end of file +end + +function OutputMessage:addColor(color) + self:addU8(color.r) + self:addU8(color.g) + self:addU8(color.b) + self:addU8(color.a) +end diff --git a/modules/corelib/table.lua b/modules/corelib/table.lua index 82db00ea..c23dbe46 100644 --- a/modules/corelib/table.lua +++ b/modules/corelib/table.lua @@ -93,6 +93,24 @@ function table.empty(t) return true end +function table.permute(t, n, count) + n = n or #t + for i=1,count or n do + local j = math.random(i, n) + t[i], t[j] = t[j], t[i] + end + return t +end + +function table.findbyfield(t, fieldname, fieldvalue) + for _i,subt in pairs(t) do + if subt[fieldname] == fieldvalue then + return subt + end + end + return nil +end + function table.toString(t) local maxn = #t local str = "" diff --git a/modules/corelib/ui/tooltip.lua b/modules/corelib/ui/tooltip.lua index ad5b24eb..203d0bc6 100644 --- a/modules/corelib/ui/tooltip.lua +++ b/modules/corelib/ui/tooltip.lua @@ -12,7 +12,7 @@ local function moveToolTip(first) local pos = g_window.getMousePosition() pos.y = pos.y + 1 local xdif = g_window.getSize().width - (pos.x + toolTipLabel:getWidth()) - if xdif < 2 then + if xdif < 10 then pos.x = pos.x - toolTipLabel:getWidth() - 3 else pos.x = pos.x + 10 diff --git a/modules/corelib/ui/uicombobox.lua b/modules/corelib/ui/uicombobox.lua index be1b3b17..e0bf89b6 100644 --- a/modules/corelib/ui/uicombobox.lua +++ b/modules/corelib/ui/uicombobox.lua @@ -3,6 +3,7 @@ UIComboBox = extends(UIWidget) function UIComboBox.create() local combobox = UIComboBox.internalCreate() + combobox:setFocusable(false) combobox.options = {} combobox.currentIndex = -1 combobox.mouseScroll = true @@ -15,6 +16,15 @@ function UIComboBox:clearOptions() self:clearText() end +function UIComboBox:getOption(text) + if not self.options then return nil end + for i,v in ipairs(self.options) do + if v.text == text then + return nil + end + end +end + function UIComboBox:setCurrentOption(text) if not self.options then return end for i,v in ipairs(self.options) do @@ -27,6 +37,18 @@ function UIComboBox:setCurrentOption(text) end end +function UIComboBox:setCurrentOptionByData(data) + if not self.options then return end + for i,v in ipairs(self.options) do + if v.data == data and self.currentIndex ~= i then + self.currentIndex = i + self:setText(v.text) + self:onOptionChange(v.text, v.data) + return + end + end +end + function UIComboBox:setCurrentIndex(index) if index >= 1 and index <= #self.options then local v = self.options[index] diff --git a/modules/corelib/ui/uiinputbox.lua b/modules/corelib/ui/uiinputbox.lua new file mode 100644 index 00000000..04c0a3ae --- /dev/null +++ b/modules/corelib/ui/uiinputbox.lua @@ -0,0 +1,114 @@ +if not UIWindow then dofile 'uiwindow' end + +-- @docclass +UIInputBox = extends(UIWindow) + +function UIInputBox.create(title, okCallback, cancelCallback) + local inputBox = UIInputBox.internalCreate() + + inputBox:setText(title) + inputBox.inputs = {} + inputBox.onEnter = function() + local results = {} + for _,func in pairs(inputBox.inputs) do + table.insert(results, func()) + end + okCallback(unpack(results)) + inputBox:destroy() + end + inputBox.onEscape = function() + if cancelCallback then + cancelCallback() + end + inputBox:destroy() + end + + return inputBox +end + +function UIInputBox:addLabel(text) + local label = g_ui.createWidget('InputBoxLabel', self) + label:setText(text) + return label +end + +function UIInputBox:addLineEdit(labelText, defaultText, maxLength) + if labelText then self:addLabel(labelText) end + local lineEdit = g_ui.createWidget('InputBoxLineEdit', self) + if defaultText then lineEdit:setText(defaultText) end + if maxLength then lineEdit:setMaxLength(maxLength) end + table.insert(self.inputs, function() return lineEdit:getText() end) + return lineEdit +end + +function UIInputBox:addTextEdit(labelText, defaultText, maxLength, visibleLines) + if labelText then self:addLabel(labelText) end + local textEdit = g_ui.createWidget('InputBoxTextEdit', self) + if defaultText then textEdit:setText(defaultText) end + if maxLength then textEdit:setMaxLength(maxLength) end + visibleLines = visibleLines or 1 + textEdit:setHeight(textEdit:getHeight() * visibleLines) + table.insert(self.inputs, function() return textEdit:getText() end) + return textEdit +end + +function UIInputBox:addCheckBox(text, checked) + local checkBox = g_ui.createWidget('InputBoxCheckBox', self) + checkBox:setText(text) + checkBox:setChecked(checked) + table.insert(self.inputs, function() return checkBox:isChecked() end) + return checkBox +end + +function UIInputBox:addComboBox(labelText, ...) + if labelText then self:addLabel(labelText) end + local comboBox = g_ui.createWidget('InputBoxComboBox', self) + local options = {...} + for i=1,#options do + comboBox:addOption(options[i]) + end + table.insert(self.inputs, function() return comboBox:getCurrentOption() end) + return comboBox +end + +function UIInputBox:addSpinBox(labelText, minimum, maximum, value, step) + if labelText then self:addLabel(labelText) end + local spinBox = g_ui.createWidget('InputBoxSpinBox', self) + spinBox:setMinimum(minimum) + spinBox:setMaximum(maximum) + spinBox:setValue(value) + spinBox:setStep(step) + table.insert(self.inputs, function() return spinBox:getValue() end) + return spinBox +end + +function UIInputBox:display(okButtonText, cancelButtonText) + okButtonText = okButtonText or tr('Ok') + cancelButtonText = cancelButtonText or tr('Cancel') + + local buttonsWidget = g_ui.createWidget('InputBoxButtonsPanel', self) + local okButton = g_ui.createWidget('InputBoxButton', buttonsWidget) + okButton:setText(okButtonText) + okButton.onClick = self.onEnter + + local cancelButton = g_ui.createWidget('InputBoxButton', buttonsWidget) + cancelButton:setText(cancelButtonText) + cancelButton.onClick = self.onEscape + + buttonsWidget:setHeight(okButton:getHeight()) + + rootWidget:addChild(self) + self:setStyle('InputBoxWindow') +end + +function displayTextInputBox(title, label, okCallback, cancelCallback) + local inputBox = UIInputBox.create(title, okCallback, cancelCallback) + inputBox:addLineEdit(label) + inputBox:display() +end + +function displayNumberInputBox(title, label, okCallback, cancelCallback, min, max, value, step) + local inputBox = UIInputBox.create(title, okCallback, cancelCallback) + inputBox:addSpinBox(label, min, max, value, step) + inputBox:display() +end diff --git a/modules/corelib/ui/uipopupmenu.lua b/modules/corelib/ui/uipopupmenu.lua index 55b98bd4..c02ed2e9 100644 --- a/modules/corelib/ui/uipopupmenu.lua +++ b/modules/corelib/ui/uipopupmenu.lua @@ -22,10 +22,14 @@ function UIPopupMenu:display(pos) currentMenu:destroy() end + if pos == nil then + pos = g_window.getMousePosition() + end + rootWidget:addChild(self) self:setPosition(pos) self:grabMouse() - self:grabKeyboard() + --self:grabKeyboard() currentMenu = self end diff --git a/modules/corelib/ui/uiprogressbar.lua b/modules/corelib/ui/uiprogressbar.lua index a84ad6f5..5ee6ba3d 100644 --- a/modules/corelib/ui/uiprogressbar.lua +++ b/modules/corelib/ui/uiprogressbar.lua @@ -6,6 +6,10 @@ function UIProgressBar.create() progressbar:setFocusable(false) progressbar:setPhantom(true) progressbar.percent = 0 + progressbar.bgBorderLeft = 0 + progressbar.bgBorderRight = 0 + progressbar.bgBorderTop = 0 + progressbar.bgBorderBottom = 0 progressbar:updateBackground() return progressbar end @@ -25,11 +29,30 @@ function UIProgressBar:getPercentPixels() end function UIProgressBar:updateBackground() - local width = math.round(math.max((self.percent * self:getWidth())/100, 1)) - local height = self:getHeight() - self:setBackgroundSize({width=width, height=height}) + local width = math.round(math.max((self.percent * (self:getWidth() - self.bgBorderLeft - self.bgBorderRight))/100, 1)) + local height = self:getHeight() - self.bgBorderTop - self.bgBorderBottom + local rect = { x = self.bgBorderLeft, y = self.bgBorderTop, width = width, height = height } + self:setBackgroundRect(rect) end +function UIProgressBar:onStyleApply(name, node) + for name,value in pairs(node) do + if name == 'background-border-left' then + self.bgBorderLeft = tonumber(value) + elseif name == 'background-border-right' then + self.bgBorderRight = tonumber(value) + elseif name == 'background-border-top' then + self.bgBorderTop = tonumber(value) + elseif name == 'background-border-bottom' then + self.bgBorderBottom = tonumber(value) + elseif name == 'background-border' then + self.bgBorderLeft = tonumber(value) + self.bgBorderRight = tonumber(value) + self.bgBorderTop = tonumber(value) + self.bgBorderBottom = tonumber(value) + end + end +end function UIProgressBar:onGeometryChange(oldRect, newRect) self:updateBackground() diff --git a/modules/corelib/ui/uispinbox.lua b/modules/corelib/ui/uispinbox.lua index d3b4aca7..7f410aac 100644 --- a/modules/corelib/ui/uispinbox.lua +++ b/modules/corelib/ui/uispinbox.lua @@ -3,11 +3,14 @@ UISpinBox = extends(UITextEdit) function UISpinBox.create() local spinbox = UISpinBox.internalCreate() + spinbox:setFocusable(false) spinbox:setValidCharacters('0123456789') spinbox.displayButtons = true spinbox.minimum = 0 spinbox.maximum = 0 spinbox.value = 0 + spinbox.step = 1 + spinbox.firstchange = true spinbox:setText("0") return spinbox end @@ -21,6 +24,14 @@ function UISpinBox:onMouseWheel(mousePos, direction) return true end +function UISpinBox:onKeyPress() + if self.firstchange then + self.firstchange = false + self:setText('') + end + return false +end + function UISpinBox:onTextChange(text, oldText) if text:len() == 0 then self:setValue(self.minimum) @@ -79,14 +90,15 @@ function UISpinBox:hideButtons() end function UISpinBox:up() - self:setValue(self.value + 1) + self:setValue(self.value + self.step) end function UISpinBox:down() - self:setValue(self.value - 1) + self:setValue(self.value - self.step) end function UISpinBox:setValue(value) + value = value or 0 value = math.max(math.min(self.maximum, value), self.minimum) if value == self.value then return end if self:getText():len() > 0 then @@ -107,6 +119,7 @@ function UISpinBox:setValue(value) end function UISpinBox:setMinimum(minimum) + minimum = minimum or -9223372036854775808 self.minimum = minimum if self.minimum > self.maximum then self.maximum = self.minimum @@ -117,12 +130,17 @@ function UISpinBox:setMinimum(minimum) end function UISpinBox:setMaximum(maximum) + maximum = maximum or 9223372036854775807 self.maximum = maximum if self.value > maximum then self:setValue(maximum) end end +function UISpinBox:setStep(step) + self.step = step or 1 +end + function UISpinBox:getValue() return self.value end diff --git a/modules/corelib/ui/uitabbar.lua b/modules/corelib/ui/uitabbar.lua index 4e191396..e36173b7 100644 --- a/modules/corelib/ui/uitabbar.lua +++ b/modules/corelib/ui/uitabbar.lua @@ -193,6 +193,9 @@ function UITabBar:selectTab(tab) tab:setChecked(true) tab:setOn(false) tab.blinking = false + + local parent = tab:getParent() + parent:focusChild(tab, MouseFocusReason) end function UITabBar:selectNextTab() diff --git a/modules/corelib/ui/uitextedit.lua b/modules/corelib/ui/uitextedit.lua new file mode 100644 index 00000000..bad14028 --- /dev/null +++ b/modules/corelib/ui/uitextedit.lua @@ -0,0 +1,77 @@ +function UITextEdit:onStyleApply(styleName, styleNode) + for name,value in pairs(styleNode) do + if name == 'vertical-scrollbar' then + addEvent(function() + self:setVerticalScrollBar(self:getParent():getChildById(value)) + end) + elseif name == 'horizontal-scrollbar' then + addEvent(function() + self:setHorizontalScrollBar(self:getParent():getChildById(value)) + end) + end + end +end + +function UITextEdit:onMouseWheel(mousePos, mouseWheel) + if self.verticalScrollBar and self:isMultiline() then + if mouseWheel == MouseWheelUp then + self.verticalScrollBar:decrement() + else + self.verticalScrollBar:increment() + end + elseif self.horizontalScrollBar then + if mouseWheel == MouseWheelUp then + self.horizontalScrollBar:increment() + else + self.horizontalScrollBar:decrement() + end + end + return true +end + +function UITextEdit:onTextAreaUpdate(virtualOffset, virtualSize, totalSize) + self:updateScrollBars() +end + +function UITextEdit:setVerticalScrollBar(scrollbar) + self.verticalScrollBar = scrollbar + self.verticalScrollBar.onValueChange = function(scrollbar, value) + local virtualOffset = self:getTextVirtualOffset() + virtualOffset.y = value + self:setTextVirtualOffset(virtualOffset) + end + self:updateScrollBars() +end + +function UITextEdit:setHorizontalScrollBar(scrollbar) + self.horizontalScrollBar = scrollbar + self.horizontalScrollBar.onValueChange = function(scrollbar, value) + local virtualOffset = self:getTextVirtualOffset() + virtualOffset.x = value + self:setTextVirtualOffset(virtualOffset) + end + self:updateScrollBars() +end + +function UITextEdit:updateScrollBars() + local scrollSize = self:getTextTotalSize() + local scrollWidth = math.max(scrollSize.width - self:getTextVirtualSize().width, 0) + local scrollHeight = math.max(scrollSize.height - self:getTextVirtualSize().height, 0) + + local scrollbar = self.verticalScrollBar + if scrollbar then + scrollbar:setMinimum(0) + scrollbar:setMaximum(scrollHeight) + scrollbar:setValue(self:getTextVirtualOffset().y) + end + + local scrollbar = self.horizontalScrollBar + if scrollbar then + scrollbar:setMinimum(0) + scrollbar:setMaximum(scrollWidth) + scrollbar:setValue(self:getTextVirtualOffset().x) + end + +end + +-- todo: ontext change, focus to cursor \ No newline at end of file diff --git a/modules/corelib/util.lua b/modules/corelib/util.lua index 6861bccd..adef9f49 100644 --- a/modules/corelib/util.lua +++ b/modules/corelib/util.lua @@ -228,10 +228,16 @@ function resolvepath(filePath, depth) end end -function toboolean(str) - str = str:trim():lower() - if str == '1' or str == 'true' then - return true +function toboolean(v) + if type(v) == 'string' then + v = v:trim():lower() + if v == '1' or v == 'true' then + return true + end + elseif type(v) == 'number' then + if v == 1 then + return true + end end return false end diff --git a/modules/game_textwindow/textwindow.lua b/modules/game_textwindow/textwindow.lua index 3258da8f..6353f855 100644 --- a/modules/game_textwindow/textwindow.lua +++ b/modules/game_textwindow/textwindow.lua @@ -23,46 +23,6 @@ function destroy() end end -function getCursorPosByNewLine(str, count) - if count <= 1 then return 0 end - - local i = 0 - for n = 1, count-1 do - local tPos = string.find(str, '\n', i) - if tPos then - i = tPos+1 - end - end - - return i - 1 -end - -function getLineByCursorPos(str, pos, maxLine) - for i = 1, maxLine do - if pos <= getCursorPosByNewLine(str, i) then - return i - end - end - - return maxLine + 1 -end - - -function getLineSizeByCursorPos(str, pos, maxLine) - for i = 1, maxLine + 1 do - if pos < getCursorPosByNewLine(str, i) then - return {minPos = getCursorPosByNewLine(str, i-1), maxPos = (getCursorPosByNewLine(str, i) - 1)} - end - end - - return {minPos = getCursorPosByNewLine(str, maxLine + 1), maxPos = str:len()} -end - -function string.count(str, pattern) - local _, _count = string.gsub(str, pattern, pattern) - return _count -end - function onGameEditText(id, itemId, maxLength, text, writter, time) if textWindow then return end textWindow = g_ui.createWidget('TextWindow', rootWidget) @@ -118,111 +78,17 @@ function onGameEditText(id, itemId, maxLength, text, writter, time) end destroy() end - - local newLineCount = string.count(textEdit:getText(), '\n') - if newLineCount >= 9 then - textScroll:setMaximum(newLineCount-9) - end - - local _prev, _next = 0, 11 - local scrollOnValueChange = function(widget, value, delta) - local line = getLineByCursorPos(textEdit:getText(), textEdit:getCursorPos(), newLineCount) - if delta > 0 then - textEdit:setCursorPos(getCursorPosByNewLine(textEdit:getText(), _next + delta - 1)) - if writeable then textEdit:setCursorPos(getCursorPosByNewLine(textEdit:getText(), line + delta)) end - else - textEdit:setCursorPos(getCursorPosByNewLine(textEdit:getText(), _prev + delta + 1) - 1) - if writeable then textEdit:setCursorPos(getCursorPosByNewLine(textEdit:getText(), line + delta)) end - end - - _next = _next + delta - _prev = _prev + delta - end - - textScroll.onValueChange = scrollOnValueChange - - local navigateVertical = function(up) -- Pressing Up / Down when scrollbar is at min / max value - local line = getLineByCursorPos(textEdit:getText(), textEdit:getCursorPos(), newLineCount) - if up and textScroll:getValue() == textScroll:getMinimum() then - textEdit:setCursorPos(getCursorPosByNewLine(textEdit:getText(), line - 1)) - elseif not up and textScroll:getValue() == textScroll:getMaximum() then - textEdit:setCursorPos(getCursorPosByNewLine(textEdit:getText(), line + 1)) - end - end - - local navigateHorizontal = function(right) -- Pressing Left / Right to navigate in a line - local currentCursor = textEdit:getCursorPos() - local lineSize = getLineSizeByCursorPos(textEdit:getText(), currentCursor, newLineCount) - if right and currentCursor < lineSize.maxPos then - textEdit:setCursorPos(currentCursor+1) - elseif not right and currentCursor > lineSize.minPos then - textEdit:setCursorPos(currentCursor-1) - end - end - - local onKeyPress = function(widget, keyCode, keyModifiers) - if keyModifiers ~= 0 then - return false - end - if keyCode == 16 or keyCode == 17 then -- Left / Right - navigateHorizontal((keyCode == 17)) - return true - elseif keyCode == 14 or keyCode == 15 then -- Up / Down - local up = (keyCode == 14) - navigateVertical(up) - - if up then - textScroll:setValue(textScroll:getValue() - 1) - else - textScroll:setValue(textScroll:getValue() + 1) - end - return true - end - return false - end - - if not writeable then - textEdit:setCursorPos(0) - textWindow.onKeyPress = onKeyPress -- textEdit won't receive focus - else - textScroll:setValue(textScroll:getMaximum()) - textEdit:setCursorPos(text:len()) - textEdit.onKeyPress = onKeyPress - end - okButton.onClick = doneFunc cancelButton.onClick = destroy - --textWindow.onEnter = doneFunc -- this should be '\n' - textWindow.onEscape = destroy -end - -function onGameEditList(id, doorId, text) - if textWindow then return end - textWindow = g_ui.createWidget('TextWindow', rootWidget) - - local textEdit = textWindow:getChildById('text') - local description = textWindow:getChildById('description') - local okButton = textWindow:getChildById('okButton') - local cancelButton = textWindow:getChildById('cancelButton') - - textEdit:setMaxLength(8192) - textEdit:setText(text) - textEdit:setEnabled(true) - description:setText(tr('Enter one name per line.')) - textWindow:setText(tr('Edit List')) - doneFunc = function() - g_game.editList(id, doorId, textEdit:getText()) - destroy() + if not writeable then + textWindow.onEnter = doneFunc end - okButton.onClick = doneFunc - textWindow.onEnter = doneFunc textWindow.onEscape = destroy end - function onGameEditList(id, doorId, text) if textWindow then return end textWindow = g_ui.createWidget('TextWindow', rootWidget) @@ -247,3 +113,4 @@ function onGameEditList(id, doorId, text) textWindow.onEnter = doneFunc textWindow.onEscape = destroy end + diff --git a/modules/game_textwindow/textwindow.otui b/modules/game_textwindow/textwindow.otui index ac535e32..78bfe8ba 100644 --- a/modules/game_textwindow/textwindow.otui +++ b/modules/game_textwindow/textwindow.otui @@ -1,12 +1,10 @@ TextWindow < MainWindow id: textWindow - size: 280 280 - @onEscape: self:destroy() + size: 300 280 - UIItem + Item id: textItem virtual: true - size: 32 32 anchors.top: parent.top anchors.left: parent.left @@ -15,43 +13,40 @@ TextWindow < MainWindow anchors.top: parent.top anchors.left: textItem.right anchors.right: parent.right - anchors.bottom: text.top - text-align: left margin-left: 8 - margin-bottom: 10 + text-auto-resize: true + text-align: left + text-wrap: true MultilineTextEdit id: text - anchors.fill: parent - anchors.top: textItem.bottom - margin-right: 10 - margin-top: 30 - margin-bottom: 30 + anchors.top: textScroll.top + anchors.left: parent.left + anchors.right: textScroll.left + anchors.bottom: textScroll.bottom + vertical-scrollbar: textScroll VerticalScrollBar id: textScroll - anchors.left: prev.right - anchors.top: prev.top - anchors.bottom: prev.bottom - minimum: 0 - maximum: 0 - step: 1 - value: 0 + anchors.top: description.bottom + anchors.bottom: okButton.top + anchors.right: parent.right + margin-top: 10 + margin-bottom: 10 + step: 16 pixels-scroll: true Button - id: cancelButton - !text: tr('Cancel') - anchors.top: next.top + id: okButton + !text: tr('Ok') + anchors.bottom: parent.bottom anchors.right: next.left - margin-right: 8 + margin-right: 10 width: 60 - @onClick: self:getParent():destroy() Button - id: okButton - !text: tr('Ok') - anchors.top: text.bottom - anchors.right: text.right - margin-top: 10 + id: cancelButton + !text: tr('Cancel') + anchors.bottom: parent.bottom + anchors.right: parent.right width: 60 diff --git a/src/framework/luafunctions.cpp b/src/framework/luafunctions.cpp index d8c62a66..46cdce33 100644 --- a/src/framework/luafunctions.cpp +++ b/src/framework/luafunctions.cpp @@ -637,23 +637,28 @@ void Application::registerLuaFunctions() // 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("setCursorVisible", &UITextEdit::setCursorVisible); g_lua.bindClassMemberFunction("setTextHidden", &UITextEdit::setTextHidden); g_lua.bindClassMemberFunction("setValidCharacters", &UITextEdit::setValidCharacters); g_lua.bindClassMemberFunction("setShiftNavigation", &UITextEdit::setShiftNavigation); g_lua.bindClassMemberFunction("setMultiline", &UITextEdit::setMultiline); + g_lua.bindClassMemberFunction("setEditable", &UITextEdit::setEditable); g_lua.bindClassMemberFunction("setMaxLength", &UITextEdit::setMaxLength); + g_lua.bindClassMemberFunction("setTextVirtualOffset", &UITextEdit::setTextVirtualOffset); + g_lua.bindClassMemberFunction("getTextVirtualOffset", &UITextEdit::getTextVirtualOffset); + g_lua.bindClassMemberFunction("getTextVirtualSize", &UITextEdit::getTextVirtualSize); + g_lua.bindClassMemberFunction("getTextTotalSize", &UITextEdit::getTextTotalSize); g_lua.bindClassMemberFunction("moveCursor", &UITextEdit::moveCursor); g_lua.bindClassMemberFunction("appendText", &UITextEdit::appendText); + g_lua.bindClassMemberFunction("wrapText", &UITextEdit::wrapText); 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("getMaxLength", &UITextEdit::getMaxLength); - g_lua.bindClassMemberFunction("isCursorEnabled", &UITextEdit::isCursorEnabled); + g_lua.bindClassMemberFunction("isEditable", &UITextEdit::isEditable); + g_lua.bindClassMemberFunction("isCursorVisible", &UITextEdit::isCursorVisible); g_lua.bindClassMemberFunction("isTextHidden", &UITextEdit::isTextHidden); g_lua.bindClassMemberFunction("isShiftNavigation", &UITextEdit::isShiftNavigation); g_lua.bindClassMemberFunction("isMultiline", &UITextEdit::isMultiline); diff --git a/src/framework/ui/uitextedit.cpp b/src/framework/ui/uitextedit.cpp index 343026e1..3c43baef 100644 --- a/src/framework/ui/uitextedit.cpp +++ b/src/framework/ui/uitextedit.cpp @@ -33,12 +33,13 @@ UITextEdit::UITextEdit() { m_cursorPos = 0; m_textAlign = Fw::AlignTopLeft; - m_startRenderPos = 0; - m_textHorizontalMargin = 0; m_textHidden = false; m_shiftNavigation = false; m_multiline = false; + m_cursorVisible = true; + m_cursorInRange = true; m_maxLength = 0; + m_editable = true; blinkCursor(); } @@ -62,16 +63,16 @@ void UITextEdit::drawSelf(Fw::DrawPane drawPane) g_painter->drawTexturedRect(m_glyphsCoords[i], texture, m_glyphsTexCoords[i]); // render cursor - if(isExplicitlyEnabled() && isActive() && m_cursorPos >= 0) { + if(isExplicitlyEnabled() && m_cursorVisible && m_cursorInRange && isActive() && m_cursorPos >= 0) { assert(m_cursorPos <= textLength); // draw every 333ms const int delay = 333; int elapsed = g_clock.millis() - m_cursorTicks; if(elapsed <= 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()); + // when cursor is at 0 + if(m_cursorPos == 0) + cursorRect = Rect(m_rect.left()+m_padding.left, m_rect.top()+m_padding.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); @@ -81,7 +82,7 @@ void UITextEdit::drawSelf(Fw::DrawPane drawPane) } } -void UITextEdit::update() +void UITextEdit::update(bool focusCursor) { std::string text = getDisplayedText(); int textLength = text.length(); @@ -103,57 +104,83 @@ void UITextEdit::update() 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]); + Point oldTextAreaOffset = m_textVirtualOffset; - // 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; + // readjust start view area based on cursor position + m_cursorInRange = false; + if(focusCursor) { + if(m_cursorPos >= 0 && textLength > 0) { + assert(m_cursorPos <= textLength); + Rect virtualRect(m_textVirtualOffset, m_rect.size() - Size(m_padding.left+m_padding.right, 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_textVirtualOffset.x = glyphsPositions[pos].x; + m_textVirtualOffset.y = glyphsPositions[pos].y - m_font->getYOffset(); + break; + } } } - } + } else { + m_textVirtualOffset = Point(0,0); } + m_cursorInRange = true; } else { - m_startInternalPos = Point(0,0); + if(m_cursorPos >= 0 && textLength > 0) { + Rect virtualRect(m_textVirtualOffset, m_rect.size() - Size(2*m_padding.left+m_padding.right, 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(virtualRect.contains(glyphRect.topLeft()) && virtualRect.contains(glyphRect.bottomRight())) + m_cursorInRange = true; + } else { + m_cursorInRange = true; + } } + bool fireAreaUpdate = false; + if(oldTextAreaOffset != m_textVirtualOffset) + fireAreaUpdate = true; + Rect textScreenCoords = m_rect; - textScreenCoords.expandLeft(-m_textHorizontalMargin); - textScreenCoords.expandRight(-m_textHorizontalMargin); - textScreenCoords.expandLeft(-m_textOffset.x); - textScreenCoords.translate(0, m_textOffset.y); + textScreenCoords.expandLeft(-m_padding.left); + textScreenCoords.expandRight(-m_padding.right); + textScreenCoords.expandBottom(-m_padding.bottom); + textScreenCoords.expandTop(-m_padding.top); m_drawArea = textScreenCoords; + if(textScreenCoords.size() != m_textVirtualSize) { + m_textVirtualSize = textScreenCoords.size(); + fireAreaUpdate = true; + } + + Size totalSize = textBoxSize; + if(totalSize.width() < m_textVirtualSize.width()) + totalSize.setWidth(m_textVirtualSize.height()); + if(totalSize.height() < m_textVirtualSize.height()) + totalSize.setHeight(m_textVirtualSize.height()); + if(m_textTotalSize != totalSize) { + m_textTotalSize = totalSize; + fireAreaUpdate = true; + } + if(m_textAlign & Fw::AlignBottom) { m_drawArea.translate(0, textScreenCoords.height() - textBoxSize.height()); } else if(m_textAlign & Fw::AlignVerticalCenter) { @@ -199,21 +226,21 @@ void UITextEdit::update() } // only render glyphs that are after startRenderPosition - if(glyphScreenCoords.bottom() < m_startInternalPos.y || glyphScreenCoords.right() < m_startInternalPos.x) + if(glyphScreenCoords.bottom() < m_textVirtualOffset.y || glyphScreenCoords.right() < m_textVirtualOffset.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.top() < m_textVirtualOffset.y) { + glyphTextureCoords.setTop(glyphTextureCoords.top() + (m_textVirtualOffset.y - glyphScreenCoords.top())); + glyphScreenCoords.setTop(m_textVirtualOffset.y); } - if(glyphScreenCoords.left() < m_startInternalPos.x) { - glyphTextureCoords.setLeft(glyphTextureCoords.left() + (m_startInternalPos.x - glyphScreenCoords.left())); - glyphScreenCoords.setLeft(m_startInternalPos.x); + if(glyphScreenCoords.left() < m_textVirtualOffset.x) { + glyphTextureCoords.setLeft(glyphTextureCoords.left() + (m_textVirtualOffset.x - glyphScreenCoords.left())); + glyphScreenCoords.setLeft(m_textVirtualOffset.x); } // subtract startInternalPos - glyphScreenCoords.translate(-m_startInternalPos); + glyphScreenCoords.translate(-m_textVirtualOffset); // translate rect to screen coords glyphScreenCoords.translate(textScreenCoords.topLeft()); @@ -236,12 +263,9 @@ void UITextEdit::update() m_glyphsCoords[i] = glyphScreenCoords; m_glyphsTexCoords[i] = glyphTextureCoords; } -} -void UITextEdit::setTextHorizontalMargin(int margin) -{ - m_textHorizontalMargin = margin; - update(); + if(fireAreaUpdate) + onTextAreaUpdate(m_textVirtualOffset, m_textVirtualSize, m_textTotalSize); } void UITextEdit::setCursorPos(int pos) @@ -253,24 +277,20 @@ void UITextEdit::setCursorPos(int pos) m_cursorPos = m_text.length(); else m_cursorPos = pos; - update(); + update(true); g_app.repaint(); } } -void UITextEdit::setCursorEnabled(bool enable) +void UITextEdit::setTextHidden(bool hidden) { - if(enable) { - m_cursorPos = 0; - blinkCursor(); - } else - m_cursorPos = -1; - update(); + m_textHidden = true; + update(true); } -void UITextEdit::setTextHidden(bool hidden) +void UITextEdit::setTextVirtualOffset(const Point& offset) { - m_textHidden = true; + m_textVirtualOffset = offset; update(); } @@ -299,7 +319,7 @@ void UITextEdit::appendText(std::string text) m_text.insert(m_cursorPos, text); m_cursorPos += text.length(); blinkCursor(); - update(); + update(true); UIWidget::onTextChange(m_text, oldText); } } @@ -323,7 +343,7 @@ void UITextEdit::appendCharacter(char c) m_text.insert(m_cursorPos, tmp); m_cursorPos++; blinkCursor(); - update(); + update(true); UIWidget::onTextChange(m_text, oldText); } } @@ -341,11 +361,16 @@ void UITextEdit::removeCharacter(bool right) m_text.erase(m_text.begin() + --m_cursorPos); } blinkCursor(); - update(); + update(true); UIWidget::onTextChange(m_text, oldText); } } +void UITextEdit::wrapText() +{ + m_text = m_font->wrapText(m_text, getPaddingRect().width()); +} + void UITextEdit::moveCursor(bool right) { if(right) { @@ -359,7 +384,7 @@ void UITextEdit::moveCursor(bool right) blinkCursor(); } } - update(); + update(true); } int UITextEdit::getTextPos(Point pos) @@ -404,7 +429,7 @@ void UITextEdit::onTextChange(const std::string& text, const std::string& oldTex { m_cursorPos = text.length(); blinkCursor(); - update(); + update(true); UIWidget::onTextChange(text, oldText); } @@ -424,14 +449,16 @@ void UITextEdit::onStyleApply(const std::string& styleName, const OTMLNodePtr& s 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() == "shift-navigation") setShiftNavigation(node->value()); else if(node->tag() == "multiline") setMultiline(node->value()); else if(node->tag() == "max-length") setMaxLength(node->value()); + else if(node->tag() == "editable") + setEditable(node->value()); + else if(node->tag() == "cursor-visible") + setCursorVisible(node->value()); } } @@ -448,6 +475,7 @@ void UITextEdit::onFocusChange(bool focused, Fw::FocusReason reason) setCursorPos(m_text.length()); else blinkCursor(); + update(true); } UIWidget::onFocusChange(focused, reason); } @@ -458,10 +486,10 @@ bool UITextEdit::onKeyPress(uchar keyCode, int keyboardModifiers, int autoRepeat return true; if(keyboardModifiers == Fw::KeyboardNoModifier) { - if(keyCode == Fw::KeyDelete) { // erase right character + if(keyCode == Fw::KeyDelete && m_editable) { // erase right character removeCharacter(true); return true; - } else if(keyCode == Fw::KeyBackspace) { // erase left character { + } else if(keyCode == Fw::KeyBackspace && m_editable) { // erase left character { removeCharacter(false); return true; } else if(keyCode == Fw::KeyRight && !m_shiftNavigation) { // move cursor right @@ -480,16 +508,16 @@ bool UITextEdit::onKeyPress(uchar keyCode, int keyboardModifiers, int autoRepeat if(UIWidgetPtr parent = getParent()) parent->focusNextChild(Fw::KeyboardFocusReason); return true; - } else if(keyCode == Fw::KeyEnter && m_multiline) { + } else if(keyCode == Fw::KeyEnter && m_multiline && m_editable) { appendCharacter('\n'); return true; } else if(keyCode == Fw::KeyUp && !m_shiftNavigation && m_multiline) { - + return true; } else if(keyCode == Fw::KeyDown && !m_shiftNavigation && m_multiline) { - + return true; } } else if(keyboardModifiers == Fw::KeyboardCtrlModifier) { - if(keyCode == Fw::KeyV) { + if(keyCode == Fw::KeyV && m_editable) { appendText(g_window.getClipboardText()); return true; } @@ -512,8 +540,11 @@ bool UITextEdit::onKeyPress(uchar keyCode, int keyboardModifiers, int autoRepeat bool UITextEdit::onKeyText(const std::string& keyText) { - appendText(keyText); - return true; + if(m_editable) { + appendText(keyText); + return true; + } + return false; } bool UITextEdit::onMousePress(const Point& mousePos, Fw::MouseButton button) @@ -526,6 +557,11 @@ bool UITextEdit::onMousePress(const Point& mousePos, Fw::MouseButton button) return true; } +void UITextEdit::onTextAreaUpdate(const Point& offset, const Size& visibleSize, const Size& totalSize) +{ + callLuaField("onTextAreaUpdate", offset, visibleSize, totalSize); +} + void UITextEdit::blinkCursor() { m_cursorTicks = g_clock.millis(); diff --git a/src/framework/ui/uitextedit.h b/src/framework/ui/uitextedit.h index d4d6e589..01aa22d8 100644 --- a/src/framework/ui/uitextedit.h +++ b/src/framework/ui/uitextedit.h @@ -20,8 +20,8 @@ * THE SOFTWARE. */ -#ifndef UILINEEDIT_H -#define UILINEEDIT_H +#ifndef UITEXTEDIT_H +#define UITEXTEDIT_H #include "uiwidget.h" @@ -34,32 +34,37 @@ public: void drawSelf(Fw::DrawPane drawPane); private: - void update(); + void update(bool focusCursor = false); public: - void setTextHorizontalMargin(int margin); void setCursorPos(int pos); - void setCursorEnabled(bool enable); + void setCursorVisible(bool enable) { m_cursorVisible = false; } void setTextHidden(bool hidden); void setValidCharacters(const std::string validCharacters) { m_validCharacters = validCharacters; } void setShiftNavigation(bool enable) { m_shiftNavigation = enable; } void setMultiline(bool enable) { m_multiline = enable; } void setMaxLength(uint maxLength) { m_maxLength = maxLength; } + void setTextVirtualOffset(const Point& offset); + void setEditable(bool editable) { m_editable = editable; } void moveCursor(bool right); void appendText(std::string text); void appendCharacter(char c); void removeCharacter(bool right); + void wrapText(); std::string getDisplayedText(); int getTextPos(Point pos); - int getTextHorizontalMargin() { return m_textHorizontalMargin; } int getCursorPos() { return m_cursorPos; } + Point getTextVirtualOffset() { return m_textVirtualOffset; } + Size getTextVirtualSize() { return m_textVirtualSize; } + Size getTextTotalSize() { return m_textTotalSize; } uint getMaxLength() { return m_maxLength; } - bool isCursorEnabled() { return m_cursorPos != -1; } + bool isCursorVisible() { return m_cursorVisible; } bool isTextHidden() { return m_textHidden; } bool isShiftNavigation() { return m_shiftNavigation; } bool isMultiline() { return m_multiline; } + bool isEditable() { return m_editable; } protected: virtual void onHoverChange(bool hovered); @@ -71,19 +76,23 @@ protected: 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); + virtual void onTextAreaUpdate(const Point& vitualOffset, const Size& virtualSize, const Size& totalSize); private: void blinkCursor(); Rect m_drawArea; int m_cursorPos; - Point m_startInternalPos; - int m_startRenderPos; + Point m_textVirtualOffset; + Size m_textVirtualSize; + Size m_textTotalSize; ticks_t m_cursorTicks; - int m_textHorizontalMargin; bool m_textHidden; bool m_shiftNavigation; bool m_multiline; + bool m_cursorInRange; + bool m_cursorVisible; + bool m_editable; std::string m_validCharacters; uint m_maxLength;