mirror of
https://github.com/koreader/koreader
synced 2024-11-10 01:10:34 +00:00
textboxwidget(fix): handle onHoldWord event
This commit is contained in:
parent
adf5ffdd26
commit
301925e34a
@ -1,3 +1,17 @@
|
|||||||
|
--[[--
|
||||||
|
A TextWidget that handles long text wrapping
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
local Foo = TextBoxWidget:new{
|
||||||
|
face = Font:getFace("cfont", 25),
|
||||||
|
text = 'We can show multiple lines.\nFoo.\nBar.',
|
||||||
|
-- width = Screen:getWidth()*2/3,
|
||||||
|
}
|
||||||
|
UIManager:show(Foo)
|
||||||
|
|
||||||
|
]]
|
||||||
|
|
||||||
local Blitbuffer = require("ffi/blitbuffer")
|
local Blitbuffer = require("ffi/blitbuffer")
|
||||||
local Widget = require("ui/widget/widget")
|
local Widget = require("ui/widget/widget")
|
||||||
local LineWidget = require("ui/widget/linewidget")
|
local LineWidget = require("ui/widget/linewidget")
|
||||||
@ -5,11 +19,7 @@ local RenderText = require("ui/rendertext")
|
|||||||
local Screen = require("device").screen
|
local Screen = require("device").screen
|
||||||
local Geom = require("ui/geometry")
|
local Geom = require("ui/geometry")
|
||||||
local util = require("util")
|
local util = require("util")
|
||||||
local DEBUG = require("dbg")
|
|
||||||
|
|
||||||
--[[
|
|
||||||
A TextWidget that handles long text wrapping
|
|
||||||
--]]
|
|
||||||
local TextBoxWidget = Widget:new{
|
local TextBoxWidget = Widget:new{
|
||||||
text = nil,
|
text = nil,
|
||||||
charlist = nil,
|
charlist = nil,
|
||||||
@ -29,11 +39,11 @@ local TextBoxWidget = Widget:new{
|
|||||||
}
|
}
|
||||||
|
|
||||||
function TextBoxWidget:init()
|
function TextBoxWidget:init()
|
||||||
local line_height = (1 + self.line_height) * self.face.size
|
self.line_height_px = (1 + self.line_height) * self.face.size
|
||||||
self.cursor_line = LineWidget:new{
|
self.cursor_line = LineWidget:new{
|
||||||
dimen = Geom:new{
|
dimen = Geom:new{
|
||||||
w = Screen:scaleBySize(1),
|
w = Screen:scaleBySize(1),
|
||||||
h = line_height,
|
h = self.line_height_px,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self:_evalCharWidthList()
|
self:_evalCharWidthList()
|
||||||
@ -51,7 +61,7 @@ function TextBoxWidget:init()
|
|||||||
self.dimen = Geom:new(self:getSize())
|
self.dimen = Geom:new(self:getSize())
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Evaluate the width of each char in `self.charlist`.
|
-- Split `self.text` into `self.charlist` and evaluate the width of each char in it.
|
||||||
function TextBoxWidget:_evalCharWidthList()
|
function TextBoxWidget:_evalCharWidthList()
|
||||||
if self.charlist == nil then
|
if self.charlist == nil then
|
||||||
self.charlist = util.splitToChars(self.text)
|
self.charlist = util.splitToChars(self.text)
|
||||||
@ -66,8 +76,9 @@ end
|
|||||||
|
|
||||||
-- Split the text into logical lines to fit into the text box.
|
-- Split the text into logical lines to fit into the text box.
|
||||||
function TextBoxWidget:_splitCharWidthList()
|
function TextBoxWidget:_splitCharWidthList()
|
||||||
self.vertical_string_list = {}
|
self.vertical_string_list = {
|
||||||
self.vertical_string_list[1] = {text = "Demo hint", offset = 1, width = 0} -- hint for empty string
|
{text = "Demo hint", offset = 1, width = 0} -- hint for empty string
|
||||||
|
}
|
||||||
|
|
||||||
local idx = 1
|
local idx = 1
|
||||||
local size = #self.char_width_list
|
local size = #self.char_width_list
|
||||||
@ -113,9 +124,14 @@ function TextBoxWidget:_splitCharWidthList()
|
|||||||
end
|
end
|
||||||
end -- endif util.isSplitable(c)
|
end -- endif util.isSplitable(c)
|
||||||
end -- endif cur_line_width > self.width
|
end -- endif cur_line_width > self.width
|
||||||
self.vertical_string_list[ln] = {text = cur_line_text, offset = offset, width = cur_line_width}
|
self.vertical_string_list[ln] = {
|
||||||
|
text = cur_line_text,
|
||||||
|
offset = offset,
|
||||||
|
width = cur_line_width
|
||||||
|
}
|
||||||
if hard_newline then
|
if hard_newline then
|
||||||
idx = idx + 1
|
idx = idx + 1
|
||||||
|
-- FIXME: reuse newline entry
|
||||||
self.vertical_string_list[ln+1] = {text = "", offset = idx, width = 0}
|
self.vertical_string_list[ln+1] = {text = "", offset = idx, width = 0}
|
||||||
end
|
end
|
||||||
ln = ln + 1
|
ln = ln + 1
|
||||||
@ -125,11 +141,10 @@ end
|
|||||||
|
|
||||||
function TextBoxWidget:_renderText(start_row_idx, end_row_idx)
|
function TextBoxWidget:_renderText(start_row_idx, end_row_idx)
|
||||||
local font_height = self.face.size
|
local font_height = self.face.size
|
||||||
local line_height = (1 + self.line_height) * font_height
|
|
||||||
if start_row_idx < 1 then start_row_idx = 1 end
|
if start_row_idx < 1 then start_row_idx = 1 end
|
||||||
if end_row_idx > #self.vertical_string_list then end_row_idx = #self.vertical_string_list end
|
if end_row_idx > #self.vertical_string_list then end_row_idx = #self.vertical_string_list end
|
||||||
local row_count = end_row_idx == 0 and 1 or end_row_idx - start_row_idx + 1
|
local row_count = end_row_idx == 0 and 1 or end_row_idx - start_row_idx + 1
|
||||||
local h = line_height * row_count
|
local h = self.line_height_px * row_count
|
||||||
self._bb = Blitbuffer.new(self.width, h)
|
self._bb = Blitbuffer.new(self.width, h)
|
||||||
self._bb:fill(Blitbuffer.COLOR_WHITE)
|
self._bb:fill(Blitbuffer.COLOR_WHITE)
|
||||||
local y = font_height
|
local y = font_height
|
||||||
@ -139,7 +154,7 @@ function TextBoxWidget:_renderText(start_row_idx, end_row_idx)
|
|||||||
--@TODO Don't use kerning for monospaced fonts. (houqp)
|
--@TODO Don't use kerning for monospaced fonts. (houqp)
|
||||||
-- refert to cb25029dddc42693cc7aaefbe47e9bd3b7e1a750 in master tree
|
-- refert to cb25029dddc42693cc7aaefbe47e9bd3b7e1a750 in master tree
|
||||||
RenderText:renderUtf8Text(self._bb, pen_x, y, self.face, line.text, true, self.bold, self.fgcolor)
|
RenderText:renderUtf8Text(self._bb, pen_x, y, self.face, line.text, true, self.bold, self.fgcolor)
|
||||||
y = y + line_height
|
y = y + self.line_height_px
|
||||||
end
|
end
|
||||||
-- -- if text is shorter than one line, shrink to text's width
|
-- -- if text is shorter than one line, shrink to text's width
|
||||||
-- if #v_list == 1 then
|
-- if #v_list == 1 then
|
||||||
@ -162,17 +177,15 @@ function TextBoxWidget:_findCharPos()
|
|||||||
x = x + self.char_width_list[offset].width
|
x = x + self.char_width_list[offset].width
|
||||||
offset = offset + 1
|
offset = offset + 1
|
||||||
end
|
end
|
||||||
local line_height = (1 + self.line_height) * self.face.size
|
return x + 1, (ln - 1) * self.line_height_px -- offset `x` by 1 to avoid overlap
|
||||||
return x + 1, (ln - 1) * line_height -- offset `x` by 1 to avoid overlap
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Click event: Move the cursor to a new location with (x, y), in pixels.
|
-- Click event: Move the cursor to a new location with (x, y), in pixels.
|
||||||
-- Be aware of virtual line number of the scorllTextWidget.
|
-- Be aware of virtual line number of the scorllTextWidget.
|
||||||
function TextBoxWidget:moveCursor(x, y)
|
function TextBoxWidget:moveCursor(x, y)
|
||||||
local w = 0
|
local w = 0
|
||||||
local line_height = (1 + self.line_height) * self.face.size
|
|
||||||
local ln = self.height == nil and 1 or self.virtual_line_num
|
local ln = self.height == nil and 1 or self.virtual_line_num
|
||||||
ln = ln + math.ceil(y / line_height) - 1
|
ln = ln + math.ceil(y / self.line_height_px) - 1
|
||||||
if ln > #self.vertical_string_list then
|
if ln > #self.vertical_string_list then
|
||||||
ln = #self.vertical_string_list
|
ln = #self.vertical_string_list
|
||||||
x = self.width
|
x = self.width
|
||||||
@ -191,13 +204,13 @@ function TextBoxWidget:moveCursor(x, y)
|
|||||||
end
|
end
|
||||||
self:free()
|
self:free()
|
||||||
self:_renderText(1, #self.vertical_string_list)
|
self:_renderText(1, #self.vertical_string_list)
|
||||||
self.cursor_line:paintTo(self._bb, w + 1, (ln - self.virtual_line_num) * line_height)
|
self.cursor_line:paintTo(self._bb, w + 1,
|
||||||
|
(ln - self.virtual_line_num) * self.line_height_px)
|
||||||
return offset
|
return offset
|
||||||
end
|
end
|
||||||
|
|
||||||
function TextBoxWidget:getVisLineCount()
|
function TextBoxWidget:getVisLineCount()
|
||||||
local line_height = (1 + self.line_height) * self.face.size
|
return math.floor(self.height / self.line_height_px)
|
||||||
return math.floor(self.height / line_height)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function TextBoxWidget:getAllLineCount()
|
function TextBoxWidget:getAllLineCount()
|
||||||
@ -252,21 +265,48 @@ function TextBoxWidget:free()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function TextBoxWidget:onHoldWord(callback, ges)
|
function TextBoxWidget:onHoldWord(callback, ges)
|
||||||
|
if not callback then return end
|
||||||
|
|
||||||
local x, y = ges.pos.x - self.dimen.x, ges.pos.y - self.dimen.y
|
local x, y = ges.pos.x - self.dimen.x, ges.pos.y - self.dimen.y
|
||||||
for _, l in ipairs(self.rendering_vlist) do
|
local line_num = math.ceil(y / self.line_height_px)
|
||||||
for _, w in ipairs(l) do
|
local line = self.vertical_string_list[line_num]
|
||||||
local box = w.box
|
|
||||||
if x > box.x and x < box.x + box.w and
|
if line then
|
||||||
y > box.y and y < box.y + box.h then
|
local char_start = line.offset
|
||||||
DEBUG("found word", w, "at", x, y)
|
local char_end -- char_end is non-inclusive
|
||||||
if callback then
|
if line_num >= #self.vertical_string_list then
|
||||||
callback(w.word)
|
char_end = #self.char_width_list + 1
|
||||||
|
else
|
||||||
|
char_end = self.vertical_string_list[line_num+1].offset
|
||||||
|
end
|
||||||
|
local char_probe_x = 0
|
||||||
|
local idx = char_start
|
||||||
|
-- find which character the touch is holding
|
||||||
|
while idx < char_end do
|
||||||
|
local c = self.char_width_list[idx]
|
||||||
|
-- FIXME: this might break if kerning is enabled
|
||||||
|
char_probe_x = char_probe_x + c.width
|
||||||
|
if char_probe_x > x then
|
||||||
|
-- ignore spaces
|
||||||
|
if c.char == " " then break end
|
||||||
|
-- now find which word the character is in
|
||||||
|
local words = util.splitToWords(line.text)
|
||||||
|
local probe_idx = char_start
|
||||||
|
for _,w in ipairs(words) do
|
||||||
|
-- +1 for word separtor
|
||||||
|
probe_idx = probe_idx + string.len(w)
|
||||||
|
if idx <= probe_idx then
|
||||||
|
callback(w)
|
||||||
|
return
|
||||||
|
end
|
||||||
end
|
end
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
|
idx = idx + 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return true
|
|
||||||
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
return TextBoxWidget
|
return TextBoxWidget
|
||||||
|
@ -1,16 +1,18 @@
|
|||||||
local BaseUtil = require("ffi/util")
|
|
||||||
|
|
||||||
--[[--
|
--[[--
|
||||||
Miscellaneous helper functions for KOReader frontend.
|
Miscellaneous helper functions for KOReader frontend.
|
||||||
]]
|
]]
|
||||||
|
|
||||||
|
local BaseUtil = require("ffi/util")
|
||||||
local util = {}
|
local util = {}
|
||||||
|
|
||||||
function util.stripePunctuations(word)
|
--- Strip all punctuations and spaces in a string.
|
||||||
if not word then return end
|
---- @string text the string to be stripped
|
||||||
-- strip ASCII punctuation characters around word
|
---- @treturn string stripped text
|
||||||
-- and strip any generic punctuation (U+2000 - U+206F) in the word
|
function util.stripePunctuations(text)
|
||||||
return word:gsub("\226[\128-\131][\128-\191]", ''):gsub("^%p+", ''):gsub("%p+$", '')
|
if not text then return end
|
||||||
|
-- strip ASCII punctuation characters around text
|
||||||
|
-- and strip any generic punctuation (U+2000 - U+206F) in the text
|
||||||
|
return text:gsub("\226[\128-\131][\128-\191]", ''):gsub("^%p+", ''):gsub("%p+$", '')
|
||||||
end
|
end
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
@ -74,7 +76,7 @@ end
|
|||||||
|
|
||||||
--- Returns number of keys in a table.
|
--- Returns number of keys in a table.
|
||||||
---- @param T Lua table
|
---- @param T Lua table
|
||||||
---- @return number of keys in table T
|
---- @treturn int number of keys in table T
|
||||||
function util.tableSize(T)
|
function util.tableSize(T)
|
||||||
local count = 0
|
local count = 0
|
||||||
for _ in pairs(T) do count = count + 1 end
|
for _ in pairs(T) do count = count + 1 end
|
||||||
@ -97,8 +99,9 @@ function util.lastIndexOf(string, ch)
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
-- Split string into a list of UTF-8 chars.
|
--- Split string into a list of UTF-8 chars.
|
||||||
-- @text: the string to be splitted.
|
---- @string text the string to be splitted.
|
||||||
|
---- @treturn table list of UTF-8 chars
|
||||||
function util.splitToChars(text)
|
function util.splitToChars(text)
|
||||||
local tab = {}
|
local tab = {}
|
||||||
if text ~= nil then
|
if text ~= nil then
|
||||||
@ -114,6 +117,20 @@ function util.splitToChars(text)
|
|||||||
return tab
|
return tab
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Split text into a list of words, spaces and punctuations.
|
||||||
|
---- @string text text to split
|
||||||
|
---- @treturn table list of words, spaces and punctuations
|
||||||
|
function util.splitToWords(text)
|
||||||
|
-- TODO: write test
|
||||||
|
local wlist = {}
|
||||||
|
for words in text:gmatch("[\32-\127\192-\255]+[\128-\191]*") do
|
||||||
|
for word in util.gsplit(words, "[%s%p]+", true) do
|
||||||
|
table.insert(wlist, word)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return wlist
|
||||||
|
end
|
||||||
|
|
||||||
-- Test whether a string could be separated by a char for multi-line rendering
|
-- Test whether a string could be separated by a char for multi-line rendering
|
||||||
function util.isSplitable(c)
|
function util.isSplitable(c)
|
||||||
return #c > 1 or c == " " or string.match(c, "%p") ~= nil
|
return #c > 1 or c == " " or string.match(c, "%p") ~= nil
|
||||||
|
@ -37,4 +37,19 @@ describe("util module", function()
|
|||||||
end
|
end
|
||||||
assert.are_same(argv, {"./sdcv", "-nj", "words", "a lot", "more or less", "--data-dir=dict"})
|
assert.are_same(argv, {"./sdcv", "-nj", "words", "a lot", "more or less", "--data-dir=dict"})
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it("should split line into words", function()
|
||||||
|
local words = util.splitToWords("one two,three four . five")
|
||||||
|
assert.are_same(words, {
|
||||||
|
"one",
|
||||||
|
" ",
|
||||||
|
"two",
|
||||||
|
",",
|
||||||
|
"three",
|
||||||
|
" ",
|
||||||
|
"four",
|
||||||
|
" . ",
|
||||||
|
"five",
|
||||||
|
})
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
|
Loading…
Reference in New Issue
Block a user