mirror of
https://github.com/koreader/koreader.git
synced 2025-08-10 00:52:38 +00:00
Text input fixes and enhancements (#4084)
InputText, ScrollTextWidget, TextBoxWidget: - proper line scrolling when moving cursor or inserting/deleting text to behave like most text editors do - fix cursor navigation, optimize refreshes when moving only the cursor, don't recreate the textwidget when moving cursor up/down - optimize refresh areas, stick to "ui" to avoid a "partial" black flash every 6 appended or deleted chars InputText: - fix issue when toggling Show password multiple times - new option: InputText.cursor_at_end (default: true) - if no InputText.height provided, measure the text widget height that we would start with, and use a ScrollTextWidget with that fixed height, so widget does not overflow container if we extend the text and increase the number of lines - as we are using "ui" refreshes while text editing, allows refreshing the InputText with a diagonal swipe on it (actually, refresh the whole screen, which allows refreshing the keyboard too if needed) ScrollTextWidget: - properly align scrollbar with its TextBoxWidget TextBoxWidget: - some cleanup (added new properties to avoid many method calls), added proxy methods for upper widgets to get them - reordered/renamed/refactored the *CharPos* methods for easier reading (sorry for the diff that won't help reviewing, but that was needed) InputDialog: - new options: allow_newline = false, -- allow entering new lines cursor_at_end = true, -- starts with cursor at end of text, ready to append fullscreen = false, -- adjust to full screen minus keyboard condensed = false, -- true will prevent adding air and balance between elements add_scroll_buttons = false, -- add scroll Up/Down buttons to first row of buttons add_nav_bar = false, -- append a row of page navigation buttons - find the most adequate text height, when none provided or fullscreen, to not overflow screen (and not be stuck with Cancel/Save buttons hidden) - had to disable the use of a MovableContainer (many issues like becoming transparent when a PathChooser comes in front, Hold to paste from clipboard, moving the InputDialog under the keyboard and getting stuck...) GestureRange: fix possible crash (when event processed after widget destruction ?) LoginDialog: fix some ui stack increase and possible crash when switching focus many times.
This commit is contained in:
@@ -450,6 +450,9 @@ function ReaderBookmark:renameBookmark(item, from_highlight)
|
||||
title = _("Rename bookmark"),
|
||||
input = item.text,
|
||||
input_type = "text",
|
||||
allow_newline = true,
|
||||
cursor_at_end = false,
|
||||
add_scroll_buttons = true,
|
||||
buttons = {
|
||||
{
|
||||
{
|
||||
|
||||
@@ -35,7 +35,7 @@ function GestureRange:match(gs)
|
||||
else
|
||||
range = self.range
|
||||
end
|
||||
if not range:contains(gs.pos) then
|
||||
if not range or not range:contains(gs.pos) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
@@ -34,6 +34,7 @@ local Size = {
|
||||
thin = Screen:scaleBySize(0.5),
|
||||
button = Screen:scaleBySize(1.5),
|
||||
window = Screen:scaleBySize(1.5),
|
||||
inputtext = Screen:scaleBySize(2),
|
||||
},
|
||||
margin = {
|
||||
default = Screen:scaleBySize(5),
|
||||
|
||||
@@ -555,6 +555,7 @@ function BookStatusWidget:onSwitchFocus(inputbox)
|
||||
input_hint = "",
|
||||
input_type = "text",
|
||||
scroll = true,
|
||||
allow_newline = true,
|
||||
text_height = Screen:scaleBySize(150),
|
||||
buttons = {
|
||||
{
|
||||
|
||||
@@ -37,6 +37,15 @@ Example:
|
||||
UIManager:show(sample_input)
|
||||
sample_input:onShowKeyboard()
|
||||
|
||||
To get a full screen text editor, use:
|
||||
fullscreen = true, -- no need to provide any height and width
|
||||
condensed = true,
|
||||
allow_newline = true,
|
||||
cursor_at_end = false,
|
||||
-- and one of these:
|
||||
add_scroll_buttons = true,
|
||||
add_nav_bar = true,
|
||||
|
||||
If it would take the user more than half a minute to recover from a mistake,
|
||||
a "Cancel" button <em>must</em> be added to the dialog. The cancellation button
|
||||
should be kept on the left and the button executing the action on the right.
|
||||
@@ -76,6 +85,19 @@ local InputDialog = InputContainer:new{
|
||||
buttons = nil,
|
||||
input_type = nil,
|
||||
enter_callback = nil,
|
||||
allow_newline = false, -- allow entering new lines (this disables any enter_callback)
|
||||
cursor_at_end = true, -- starts with cursor at end of text, ready for appending
|
||||
fullscreen = false, -- adjust to full screen minus keyboard
|
||||
condensed = false, -- true will prevent adding air and balance between elements
|
||||
add_scroll_buttons = false, -- add scroll Up/Down buttons to first row of buttons
|
||||
add_nav_bar = false, -- append a row of page navigation buttons
|
||||
-- note that the text widget can be scrolled with Swipe North/South even when no button
|
||||
|
||||
-- movable = true, -- set to false if movable gestures conflicts with subwidgets gestures
|
||||
-- for now, too much conflicts between InputText and MovableContainer, and
|
||||
-- there's the keyboard to exclude from move area (the InputDialog could
|
||||
-- be moved under the keyboard, and the user would be locked)
|
||||
movable = false,
|
||||
|
||||
width = nil,
|
||||
|
||||
@@ -88,13 +110,29 @@ local InputDialog = InputContainer:new{
|
||||
|
||||
title_padding = Size.padding.default,
|
||||
title_margin = Size.margin.title,
|
||||
input_padding = Size.padding.large,
|
||||
desc_padding = Size.padding.default, -- Use the same as title for their
|
||||
desc_margin = Size.margin.title, -- texts to be visually aligned
|
||||
input_padding = Size.padding.default,
|
||||
input_margin = Size.margin.default,
|
||||
button_padding = Size.padding.default,
|
||||
border_size = Size.border.window,
|
||||
}
|
||||
|
||||
function InputDialog:init()
|
||||
self.width = self.width or Screen:getWidth() * 0.8
|
||||
if self.fullscreen then
|
||||
self.movable = false
|
||||
self.border_size = 0
|
||||
self.width = Screen:getWidth() - 2*self.border_size
|
||||
else
|
||||
self.width = self.width or Screen:getWidth() * 0.8
|
||||
end
|
||||
if self.condensed then
|
||||
self.text_width = self.width - 2*(self.border_size + self.input_padding + self.input_margin)
|
||||
else
|
||||
self.text_width = self.text_width or self.width * 0.9
|
||||
end
|
||||
|
||||
-- Title & description
|
||||
local title_width = RenderText:sizeUtf8Text(0, self.width,
|
||||
self.title_face, self.title, true).x
|
||||
if title_width > self.width then
|
||||
@@ -114,28 +152,159 @@ function InputDialog:init()
|
||||
width = self.width,
|
||||
}
|
||||
}
|
||||
|
||||
self.title_bar = LineWidget:new{
|
||||
dimen = Geom:new{
|
||||
w = self.width,
|
||||
h = Size.line.thick,
|
||||
}
|
||||
}
|
||||
if self.description then
|
||||
self.description = FrameContainer:new{
|
||||
padding = self.title_padding,
|
||||
margin = self.title_margin,
|
||||
self.description_widget = FrameContainer:new{
|
||||
padding = self.desc_padding,
|
||||
margin = self.desc_margin,
|
||||
bordersize = 0,
|
||||
TextBoxWidget:new{
|
||||
text = self.description,
|
||||
face = self.description_face,
|
||||
width = self.width - 2*self.title_padding - 2*self.title_margin,
|
||||
width = self.width - 2*self.desc_padding - 2*self.desc_margin,
|
||||
}
|
||||
}
|
||||
else
|
||||
self.description = VerticalSpan:new{ width = self.title_margin + self.title_padding }
|
||||
self.description_widget = VerticalSpan:new{ width = 0 }
|
||||
end
|
||||
|
||||
-- Vertical spaces added before and after InputText
|
||||
-- (these will be adjusted later to center the input text if needed)
|
||||
local vspan_before_input_text = VerticalSpan:new{ width = 0 }
|
||||
local vspan_after_input_text = VerticalSpan:new{ width = 0 }
|
||||
-- We add the same vertical space used under description after the input widget
|
||||
-- (can be disabled by setting condensed=true)
|
||||
if not self.condensed then
|
||||
local desc_pad_height = self.desc_margin + self.desc_padding
|
||||
if self.description then
|
||||
vspan_before_input_text.width = 0 -- already provided by description_widget
|
||||
vspan_after_input_text.width = desc_pad_height
|
||||
else
|
||||
vspan_before_input_text.width = desc_pad_height
|
||||
vspan_after_input_text.width = desc_pad_height
|
||||
end
|
||||
end
|
||||
|
||||
-- Buttons
|
||||
if self.add_nav_bar then
|
||||
if not self.buttons then
|
||||
self.buttons = {}
|
||||
end
|
||||
local nav_bar = {}
|
||||
table.insert(self.buttons, nav_bar)
|
||||
table.insert(nav_bar, {
|
||||
text = "⇱",
|
||||
callback = function()
|
||||
self._input_widget:scrollToTop()
|
||||
end,
|
||||
})
|
||||
table.insert(nav_bar, {
|
||||
text = "⇲",
|
||||
callback = function()
|
||||
self._input_widget:scrollToBottom()
|
||||
end,
|
||||
})
|
||||
table.insert(nav_bar, {
|
||||
text = "△",
|
||||
callback = function()
|
||||
self._input_widget:scrollUp()
|
||||
end,
|
||||
})
|
||||
table.insert(nav_bar, {
|
||||
text = "▽",
|
||||
callback = function()
|
||||
self._input_widget:scrollDown()
|
||||
end,
|
||||
})
|
||||
elseif self.add_scroll_buttons then
|
||||
if not self.buttons then
|
||||
self.buttons = {{}}
|
||||
end
|
||||
-- Add them to the end of first row
|
||||
table.insert(self.buttons[1], {
|
||||
text = "△",
|
||||
callback = function()
|
||||
self._input_widget:scrollUp()
|
||||
end,
|
||||
})
|
||||
table.insert(self.buttons[1], {
|
||||
text = "▽",
|
||||
callback = function()
|
||||
self._input_widget:scrollDown()
|
||||
end,
|
||||
})
|
||||
end
|
||||
self.button_table = ButtonTable:new{
|
||||
width = self.width - 2*self.button_padding,
|
||||
button_font_face = "cfont",
|
||||
button_font_size = 20,
|
||||
buttons = self.buttons,
|
||||
zero_sep = true,
|
||||
show_parent = self,
|
||||
}
|
||||
local buttons_container = CenterContainer:new{
|
||||
dimen = Geom:new{
|
||||
w = self.width,
|
||||
h = self.button_table:getSize().h,
|
||||
},
|
||||
self.button_table,
|
||||
}
|
||||
|
||||
-- InputText
|
||||
if not self.text_height or self.fullscreen then
|
||||
-- We need to find the best height to avoid screen overflow
|
||||
-- Create a dummy input widget to get some metrics
|
||||
local input_widget = InputText:new{
|
||||
text = self.fullscreen and "-" or self.input,
|
||||
face = self.input_face,
|
||||
width = self.text_width,
|
||||
padding = self.input_padding,
|
||||
margin = self.input_margin,
|
||||
}
|
||||
local text_height = input_widget:getTextHeight()
|
||||
local line_height = input_widget:getLineHeight()
|
||||
local input_pad_height = input_widget:getSize().h - text_height
|
||||
local keyboard_height = input_widget:getKeyboardDimen().h
|
||||
input_widget:free()
|
||||
-- Find out available height
|
||||
local available_height = Screen:getHeight()
|
||||
- 2*self.border_size
|
||||
- self.title:getSize().h
|
||||
- self.title_bar:getSize().h
|
||||
- self.description_widget:getSize().h
|
||||
- vspan_before_input_text:getSize().h
|
||||
- input_pad_height
|
||||
- vspan_after_input_text:getSize().h
|
||||
- buttons_container:getSize().h
|
||||
- keyboard_height
|
||||
if self.fullscreen or text_height > available_height then
|
||||
-- Don't leave unusable space in the text widget, as the user could think
|
||||
-- it's an empty line: move that space in pads after and below (for centering)
|
||||
self.text_height = math.floor(available_height / line_height) * line_height
|
||||
local pad_height = available_height - self.text_height
|
||||
local pad_before = math.ceil(pad_height / 2)
|
||||
local pad_after = pad_height - pad_before
|
||||
vspan_before_input_text.width = vspan_before_input_text.width + pad_before
|
||||
vspan_after_input_text.width = vspan_after_input_text.width + pad_after
|
||||
self.cursor_at_end = false -- stay at start if overflowed
|
||||
else
|
||||
-- Don't leave unusable space in the text widget
|
||||
self.text_height = text_height
|
||||
end
|
||||
end
|
||||
self._input_widget = InputText:new{
|
||||
text = self.input,
|
||||
hint = self.input_hint,
|
||||
face = self.input_face,
|
||||
width = self.text_width or self.width * 0.9,
|
||||
width = self.text_width,
|
||||
height = self.text_height or nil,
|
||||
padding = self.input_padding,
|
||||
margin = self.input_margin,
|
||||
input_type = self.input_type,
|
||||
text_type = self.text_type,
|
||||
enter_callback = self.enter_callback or function()
|
||||
@@ -148,68 +317,54 @@ function InputDialog:init()
|
||||
end
|
||||
end
|
||||
end,
|
||||
scroll = false,
|
||||
scroll = true,
|
||||
cursor_at_end = self.cursor_at_end,
|
||||
parent = self,
|
||||
}
|
||||
self.button_table = ButtonTable:new{
|
||||
width = self.width - 2*self.button_padding,
|
||||
button_font_face = "cfont",
|
||||
button_font_size = 20,
|
||||
buttons = self.buttons,
|
||||
zero_sep = true,
|
||||
show_parent = self,
|
||||
}
|
||||
|
||||
self.title_bar = LineWidget:new{
|
||||
dimen = Geom:new{
|
||||
w = self.width,
|
||||
h = Size.line.thick,
|
||||
}
|
||||
}
|
||||
|
||||
self.dialog_frame = FrameContainer:new{
|
||||
radius = Size.radius.window,
|
||||
padding = 0,
|
||||
margin = 0,
|
||||
background = Blitbuffer.COLOR_WHITE,
|
||||
VerticalGroup:new{
|
||||
align = "left",
|
||||
self.title,
|
||||
self.title_bar,
|
||||
self.description,
|
||||
-- input
|
||||
CenterContainer:new{
|
||||
dimen = Geom:new{
|
||||
w = self.title_bar:getSize().w,
|
||||
h = self._input_widget:getSize().h,
|
||||
},
|
||||
self._input_widget,
|
||||
},
|
||||
-- Add same vertical space after than before InputText
|
||||
VerticalSpan:new{ width = self.title_margin + self.title_padding },
|
||||
-- buttons
|
||||
CenterContainer:new{
|
||||
dimen = Geom:new{
|
||||
w = self.title_bar:getSize().w,
|
||||
h = self.button_table:getSize().h,
|
||||
},
|
||||
self.button_table,
|
||||
}
|
||||
}
|
||||
}
|
||||
if self.allow_newline then -- remove any enter_callback
|
||||
self._input_widget.enter_callback = nil
|
||||
end
|
||||
if Device:hasKeys() then
|
||||
--little hack to piggyback on the layout of the button_table to handle the new InputText
|
||||
table.insert(self.button_table.layout, 1, {self._input_widget})
|
||||
end
|
||||
|
||||
-- Final widget
|
||||
self.dialog_frame = FrameContainer:new{
|
||||
radius = self.fullscreen and 0 or Size.radius.window,
|
||||
padding = 0,
|
||||
margin = 0,
|
||||
bordersize = self.border_size,
|
||||
background = Blitbuffer.COLOR_WHITE,
|
||||
VerticalGroup:new{
|
||||
align = "left",
|
||||
self.title,
|
||||
self.title_bar,
|
||||
self.description_widget,
|
||||
vspan_before_input_text,
|
||||
CenterContainer:new{
|
||||
dimen = Geom:new{
|
||||
w = self.width,
|
||||
h = self._input_widget:getSize().h,
|
||||
},
|
||||
self._input_widget,
|
||||
},
|
||||
vspan_after_input_text,
|
||||
buttons_container,
|
||||
}
|
||||
}
|
||||
local frame = self.dialog_frame
|
||||
if self.movable then
|
||||
frame = MovableContainer:new{
|
||||
self.dialog_frame,
|
||||
}
|
||||
end
|
||||
self[1] = CenterContainer:new{
|
||||
dimen = Geom:new{
|
||||
w = Screen:getWidth(),
|
||||
h = Screen:getHeight() - self._input_widget:getKeyboardDimen().h,
|
||||
},
|
||||
MovableContainer:new{
|
||||
self.dialog_frame,
|
||||
},
|
||||
frame
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ local Font = require("ui/font")
|
||||
local GestureRange = require("ui/gesturerange")
|
||||
local InputContainer = require("ui/widget/container/inputcontainer")
|
||||
local ScrollTextWidget = require("ui/widget/scrolltextwidget")
|
||||
local Size = require("ui/size")
|
||||
local TextBoxWidget = require("ui/widget/textboxwidget")
|
||||
local UIManager = require("ui/uimanager")
|
||||
local VerticalGroup = require("ui/widget/verticalgroup")
|
||||
@@ -18,24 +19,30 @@ local Keyboard
|
||||
local InputText = InputContainer:new{
|
||||
text = "",
|
||||
hint = "demo hint",
|
||||
charlist = nil, -- table to store input string
|
||||
charpos = nil, -- position to insert a new char, or the position of the cursor
|
||||
input_type = nil,
|
||||
text_type = nil,
|
||||
text_widget = nil, -- Text Widget for cursor movement
|
||||
input_type = nil, -- "number" or anything else
|
||||
text_type = nil, -- "password" or anything else
|
||||
show_password_toggle = true,
|
||||
cursor_at_end = true, -- starts with cursor at end of text, ready for appending
|
||||
scroll = false, -- whether to allow scrolling (will be set to true if no height provided)
|
||||
focused = true,
|
||||
parent = nil, -- parent dialog that will be set dirty
|
||||
|
||||
width = nil,
|
||||
height = nil,
|
||||
height = nil, -- when nil, will be set to original text height (possibly
|
||||
-- less if screen would be overflowed) and made scrollable to
|
||||
-- not overflow if some text is appended and add new lines
|
||||
|
||||
face = Font:getFace("smallinfofont"),
|
||||
padding = Size.padding.default,
|
||||
margin = Size.margin.default,
|
||||
bordersize = Size.border.inputtext,
|
||||
|
||||
padding = Screen:scaleBySize(5),
|
||||
margin = Screen:scaleBySize(5),
|
||||
bordersize = Screen:scaleBySize(2),
|
||||
|
||||
parent = nil, -- parent dialog that will be set dirty
|
||||
scroll = false,
|
||||
focused = true,
|
||||
-- for internal use
|
||||
text_widget = nil, -- Text Widget for cursor movement, possibly a ScrollTextWidget
|
||||
charlist = nil, -- table of individual chars from input string
|
||||
charpos = nil, -- position of the cursor, where a new char would be inserted
|
||||
top_line_num = nil, -- virtual_line_num of the text_widget (index of the displayed top line)
|
||||
is_password_type = false, -- set to true if original text_type == "password"
|
||||
}
|
||||
|
||||
-- only use PhysicalKeyboard if the device does not have touch screen
|
||||
@@ -56,39 +63,85 @@ if Device.isTouchDevice() or Device.hasDPad() then
|
||||
range = self.dimen
|
||||
}
|
||||
},
|
||||
SwipeTextBox = {
|
||||
GestureRange:new{
|
||||
ges = "swipe",
|
||||
range = self.dimen
|
||||
}
|
||||
},
|
||||
-- These are just to stop propagation of the event to
|
||||
-- parents in case there's a MovableContainer among them
|
||||
-- Commented for now, as this needs work
|
||||
-- HoldPanTextBox = {
|
||||
-- GestureRange:new{ ges = "hold_pan", range = self.dimen }
|
||||
-- },
|
||||
-- HoldReleaseTextBox = {
|
||||
-- GestureRange:new{ ges = "hold_release", range = self.dimen }
|
||||
-- },
|
||||
-- PanTextBox = {
|
||||
-- GestureRange:new{ ges = "pan", range = self.dimen }
|
||||
-- },
|
||||
-- PanReleaseTextBox = {
|
||||
-- GestureRange:new{ ges = "pan_release", range = self.dimen }
|
||||
-- },
|
||||
-- TouchTextBox = {
|
||||
-- GestureRange:new{ ges = "touch", range = self.dimen }
|
||||
-- },
|
||||
}
|
||||
end
|
||||
|
||||
-- For MovableContainer to work fully, some of these should
|
||||
-- do more check before disabling the event or not
|
||||
-- Commented for now, as this needs work
|
||||
-- local function _disableEvent() return true end
|
||||
-- InputText.onHoldPanTextBox = _disableEvent
|
||||
-- InputText.onHoldReleaseTextBox = _disableEvent
|
||||
-- InputText.onPanTextBox = _disableEvent
|
||||
-- InputText.onPanReleaseTextBox = _disableEvent
|
||||
-- InputText.onTouchTextBox = _disableEvent
|
||||
|
||||
function InputText:onTapTextBox(arg, ges)
|
||||
if self.parent.onSwitchFocus then
|
||||
self.parent:onSwitchFocus(self)
|
||||
end
|
||||
local x = ges.pos.x - self._frame_textwidget.dimen.x - self.bordersize - self.padding
|
||||
local y = ges.pos.y - self._frame_textwidget.dimen.y - self.bordersize - self.padding
|
||||
if x > 0 and y > 0 then
|
||||
self.charpos = self.text_widget:moveCursor(x, y)
|
||||
UIManager:setDirty(self.parent, function()
|
||||
return "ui", self.dimen
|
||||
end)
|
||||
end
|
||||
local textwidget_offset = self.margin + self.bordersize + self.padding
|
||||
local x = ges.pos.x - self._frame_textwidget.dimen.x - textwidget_offset
|
||||
local y = ges.pos.y - self._frame_textwidget.dimen.y - textwidget_offset
|
||||
self.text_widget:moveCursorToXY(x, y, true) -- restrict_to_view=true
|
||||
self.charpos, self.top_line_num = self.text_widget:getCharPos()
|
||||
return true
|
||||
end
|
||||
|
||||
function InputText:onHoldTextBox(arg, ges)
|
||||
if self.parent.onSwitchFocus then
|
||||
self.parent:onSwitchFocus(self)
|
||||
end
|
||||
local x = ges.pos.x - self._frame_textwidget.dimen.x - self.bordersize - self.padding
|
||||
local y = ges.pos.y - self._frame_textwidget.dimen.y - self.bordersize - self.padding
|
||||
if x > 0 and y > 0 then
|
||||
self.charpos = self.text_widget:moveCursor(x, y)
|
||||
if Device:hasClipboard() and Device.input.hasClipboardText() then
|
||||
self:addChars(Device.input.getClipboardText())
|
||||
end
|
||||
UIManager:setDirty(self.parent, function()
|
||||
return "ui", self.dimen
|
||||
end)
|
||||
local textwidget_offset = self.margin + self.bordersize + self.padding
|
||||
local x = ges.pos.x - self._frame_textwidget.dimen.x - textwidget_offset
|
||||
local y = ges.pos.y - self._frame_textwidget.dimen.y - textwidget_offset
|
||||
self.text_widget:moveCursorToXY(x, y, true) -- restrict_to_view=true
|
||||
self.charpos, self.top_line_num = self.text_widget:getCharPos()
|
||||
if Device:hasClipboard() and Device.input.hasClipboardText() then
|
||||
self:addChars(Device.input.getClipboardText())
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function InputText:onSwipeTextBox(arg, ges)
|
||||
-- Allow refreshing the widget (actually, the screen) with the classic
|
||||
-- Diagonal Swipe, as we're only using the quick "ui" mode while editing
|
||||
if ges.direction == "northeast" or ges.direction == "northwest"
|
||||
or ges.direction == "southeast" or ges.direction == "southwest" then
|
||||
if self.refresh_callback then self.refresh_callback() end
|
||||
-- Trigger a full-screen HQ flashing refresh so
|
||||
-- the keyboard can also be fully redrawn
|
||||
UIManager:setDirty(nil, "full")
|
||||
end
|
||||
-- Let it propagate in any case (a long diagonal swipe may also be
|
||||
-- used for taking a screenshot)
|
||||
return false
|
||||
end
|
||||
|
||||
end
|
||||
if Device.hasKeys() then
|
||||
if not InputText.initEventListener then
|
||||
@@ -115,6 +168,10 @@ else
|
||||
end
|
||||
|
||||
function InputText:init()
|
||||
if self.text_type == "password" then
|
||||
-- text_type changes from "password" to "text" when we toggle password
|
||||
self.is_password_type = true
|
||||
end
|
||||
self:initTextBox(self.text)
|
||||
if self.readonly ~= true then
|
||||
self:initKeyboard()
|
||||
@@ -122,11 +179,11 @@ function InputText:init()
|
||||
end
|
||||
end
|
||||
|
||||
function InputText:initTextBox(text, char_added, is_password_type)
|
||||
-- This will be called when we add or del chars, as we need to recreate
|
||||
-- the text widget to have the new text splittted into possibly different
|
||||
-- lines than before
|
||||
function InputText:initTextBox(text, char_added)
|
||||
self.text = text
|
||||
if self.text_type == "password" then
|
||||
is_password_type = true
|
||||
end
|
||||
local fgcolor
|
||||
local show_charlist
|
||||
local show_text = text
|
||||
@@ -147,11 +204,16 @@ function InputText:initTextBox(text, char_added, is_password_type)
|
||||
end
|
||||
end
|
||||
self.charlist = util.splitToChars(text)
|
||||
-- keep previous cursor position if charpos not nil
|
||||
if self.charpos == nil then
|
||||
self.charpos = #self.charlist + 1
|
||||
if self.cursor_at_end then
|
||||
self.charpos = #self.charlist + 1
|
||||
else
|
||||
self.charpos = 1
|
||||
end
|
||||
end
|
||||
end
|
||||
if is_password_type and self.show_password_toggle then
|
||||
if self.is_password_type and self.show_password_toggle then
|
||||
self._check_button = self._check_button or CheckButton:new{
|
||||
text = _("Show password"),
|
||||
callback = function()
|
||||
@@ -162,12 +224,9 @@ function InputText:initTextBox(text, char_added, is_password_type)
|
||||
self.text_type = "text"
|
||||
self._check_button:check()
|
||||
end
|
||||
self:setText(self:getText(), is_password_type)
|
||||
self:setText(self:getText())
|
||||
end,
|
||||
|
||||
width = self.width,
|
||||
height = self.height,
|
||||
|
||||
padding = self.padding,
|
||||
margin = self.margin,
|
||||
bordersize = self.bordersize,
|
||||
@@ -182,11 +241,28 @@ function InputText:initTextBox(text, char_added, is_password_type)
|
||||
self._password_toggle = nil
|
||||
end
|
||||
show_charlist = util.splitToChars(show_text)
|
||||
|
||||
if not self.height then
|
||||
-- If no height provided, measure the text widget height
|
||||
-- we would start with, and use a ScrollTextWidget with that
|
||||
-- height, so widget does not overflow container if we extend
|
||||
-- the text and increase the number of lines
|
||||
local text_widget = TextBoxWidget:new{
|
||||
text = show_text,
|
||||
charlist = show_charlist,
|
||||
face = self.face,
|
||||
width = self.width,
|
||||
}
|
||||
self.height = text_widget:getTextHeight()
|
||||
self.scroll = true
|
||||
text_widget:free()
|
||||
end
|
||||
if self.scroll then
|
||||
self.text_widget = ScrollTextWidget:new{
|
||||
text = show_text,
|
||||
charlist = show_charlist,
|
||||
charpos = self.charpos,
|
||||
top_line_num = self.top_line_num,
|
||||
editable = self.focused,
|
||||
face = self.face,
|
||||
fgcolor = fgcolor,
|
||||
@@ -199,13 +275,18 @@ function InputText:initTextBox(text, char_added, is_password_type)
|
||||
text = show_text,
|
||||
charlist = show_charlist,
|
||||
charpos = self.charpos,
|
||||
top_line_num = self.top_line_num,
|
||||
editable = self.focused,
|
||||
face = self.face,
|
||||
fgcolor = fgcolor,
|
||||
width = self.width,
|
||||
height = self.height,
|
||||
dialog = self.parent,
|
||||
}
|
||||
end
|
||||
-- Get back possibly modified charpos and virtual_line_num
|
||||
self.charpos, self.top_line_num = self.text_widget:getCharPos()
|
||||
|
||||
self._frame_textwidget = FrameContainer:new{
|
||||
bordersize = self.bordersize,
|
||||
padding = self.padding,
|
||||
@@ -266,17 +347,25 @@ function InputText:onCloseKeyboard()
|
||||
UIManager:close(self.keyboard)
|
||||
end
|
||||
|
||||
function InputText:getTextHeight()
|
||||
return self.text_widget:getTextHeight()
|
||||
end
|
||||
|
||||
function InputText:getLineHeight()
|
||||
return self.text_widget:getLineHeight()
|
||||
end
|
||||
|
||||
function InputText:getKeyboardDimen()
|
||||
return self.keyboard.dimen
|
||||
end
|
||||
|
||||
function InputText:addChars(char)
|
||||
if self.enter_callback and char == '\n' then
|
||||
function InputText:addChars(chars)
|
||||
if self.enter_callback and chars == "\n" then
|
||||
UIManager:scheduleIn(0.3, function() self.enter_callback() end)
|
||||
return
|
||||
end
|
||||
table.insert(self.charlist, self.charpos, char)
|
||||
self.charpos = self.charpos + #util.splitToChars(char)
|
||||
table.insert(self.charlist, self.charpos, chars)
|
||||
self.charpos = self.charpos + #util.splitToChars(chars)
|
||||
self:initTextBox(table.concat(self.charlist), true)
|
||||
end
|
||||
|
||||
@@ -287,48 +376,63 @@ function InputText:delChar()
|
||||
self:initTextBox(table.concat(self.charlist))
|
||||
end
|
||||
|
||||
-- For the following cursor/scroll methods, the text_widget deals
|
||||
-- itself with setDirty'ing the appropriate regions
|
||||
function InputText:leftChar()
|
||||
if self.charpos == 1 then return end
|
||||
self.charpos = self.charpos -1
|
||||
self:initTextBox(table.concat(self.charlist))
|
||||
self.text_widget:moveCursorLeft()
|
||||
self.charpos, self.top_line_num = self.text_widget:getCharPos()
|
||||
end
|
||||
|
||||
function InputText:rightChar()
|
||||
if self.charpos > #table.concat(self.charlist) then return end
|
||||
self.charpos = self.charpos +1
|
||||
self:initTextBox(table.concat(self.charlist))
|
||||
if self.charpos > #self.charlist then return end
|
||||
self.text_widget:moveCursorRight()
|
||||
self.charpos, self.top_line_num = self.text_widget:getCharPos()
|
||||
end
|
||||
|
||||
function InputText:upLine()
|
||||
if self.text_widget.moveCursorUp then
|
||||
self.charpos = self.text_widget:moveCursorUp()
|
||||
end
|
||||
self.text_widget:moveCursorUp()
|
||||
self.charpos, self.top_line_num = self.text_widget:getCharPos()
|
||||
end
|
||||
|
||||
function InputText:downLine()
|
||||
if self.text_widget.moveCursorDown then
|
||||
self.charpos = self.text_widget:moveCursorDown()
|
||||
end
|
||||
self.text_widget:moveCursorDown()
|
||||
self.charpos, self.top_line_num = self.text_widget:getCharPos()
|
||||
end
|
||||
|
||||
function InputText:scrollDown()
|
||||
self.text_widget:scrollDown()
|
||||
self.charpos, self.top_line_num = self.text_widget:getCharPos()
|
||||
end
|
||||
|
||||
function InputText:scrollUp()
|
||||
self.text_widget:scrollUp()
|
||||
self.charpos, self.top_line_num = self.text_widget:getCharPos()
|
||||
end
|
||||
|
||||
function InputText:scrollToTop()
|
||||
self.text_widget:scrollToTop()
|
||||
self.charpos, self.top_line_num = self.text_widget:getCharPos()
|
||||
end
|
||||
|
||||
function InputText:scrollToBottom()
|
||||
self.text_widget:scrollToBottom()
|
||||
self.charpos, self.top_line_num = self.text_widget:getCharPos()
|
||||
end
|
||||
|
||||
function InputText:clear()
|
||||
self.charpos = nil
|
||||
self.top_line_num = 1
|
||||
self:initTextBox("")
|
||||
UIManager:setDirty(self.parent, function()
|
||||
return "ui", self[1][1].dimen
|
||||
end)
|
||||
end
|
||||
|
||||
function InputText:getText()
|
||||
return self.text
|
||||
end
|
||||
|
||||
function InputText:setText(text, is_password_type)
|
||||
self.charpos = nil
|
||||
self:initTextBox(text, nil, is_password_type)
|
||||
UIManager:setDirty(self.parent, function()
|
||||
return "partial", self[1].dimen
|
||||
end)
|
||||
function InputText:setText(text)
|
||||
-- Keep previous charpos and top_line_num
|
||||
self:initTextBox(text)
|
||||
end
|
||||
|
||||
return InputText
|
||||
|
||||
@@ -102,6 +102,7 @@ function LoginDialog:onSwitchFocus(inputbox)
|
||||
-- unfocus current inputbox
|
||||
self._input_widget:unfocus()
|
||||
self._input_widget:onCloseKeyboard()
|
||||
UIManager:close(self)
|
||||
|
||||
-- focus new inputbox
|
||||
self._input_widget = inputbox
|
||||
|
||||
@@ -280,7 +280,7 @@ function MenuItem:init()
|
||||
local removed_char_width= 0
|
||||
while removed_char_width < ellipsis_size do
|
||||
-- the width of each char has already been calculated by TextBoxWidget
|
||||
removed_char_width = removed_char_width + item_name:geCharWidth(offset)
|
||||
removed_char_width = removed_char_width + item_name:getCharWidth(offset)
|
||||
offset = offset - 1
|
||||
end
|
||||
self.text = table.concat(item_name.charlist, '', 1, offset) .. "…"
|
||||
|
||||
@@ -8,6 +8,7 @@ local PathChooser = FileChooser:extend{
|
||||
title = _("Choose Path"),
|
||||
no_title = false,
|
||||
is_popout = false,
|
||||
covers_fullscreen = true, -- set it to false if you set is_popout = true
|
||||
is_borderless = true,
|
||||
show_filesize = false,
|
||||
file_filter = function() return false end, -- filter out regular files
|
||||
|
||||
@@ -19,6 +19,7 @@ local ScrollTextWidget = InputContainer:new{
|
||||
text = nil,
|
||||
charlist = nil,
|
||||
charpos = nil,
|
||||
top_line_num = nil,
|
||||
editable = false,
|
||||
justified = false,
|
||||
face = nil,
|
||||
@@ -36,6 +37,8 @@ function ScrollTextWidget:init()
|
||||
text = self.text,
|
||||
charlist = self.charlist,
|
||||
charpos = self.charpos,
|
||||
top_line_num = self.top_line_num,
|
||||
dialog = self.dialog,
|
||||
editable = self.editable,
|
||||
justified = self.justified,
|
||||
face = self.face,
|
||||
@@ -52,9 +55,10 @@ function ScrollTextWidget:init()
|
||||
low = 0,
|
||||
high = visible_line_count / total_line_count,
|
||||
width = self.scroll_bar_width,
|
||||
height = self.height,
|
||||
height = self.text_widget:getTextHeight(),
|
||||
}
|
||||
local horizontal_group = HorizontalGroup:new{}
|
||||
self:updateScrollBar()
|
||||
local horizontal_group = HorizontalGroup:new{ align = "top" }
|
||||
table.insert(horizontal_group, self.text_widget)
|
||||
table.insert(horizontal_group, HorizontalSpan:new{width=self.text_scroll_span})
|
||||
table.insert(horizontal_group, self.v_scroll_bar)
|
||||
@@ -92,38 +96,93 @@ function ScrollTextWidget:focus()
|
||||
self.text_widget:focus()
|
||||
end
|
||||
|
||||
function ScrollTextWidget:moveCursor(x, y)
|
||||
return self.text_widget:moveCursor(x, y)
|
||||
function ScrollTextWidget:getTextHeight()
|
||||
return self.text_widget:getTextHeight()
|
||||
end
|
||||
|
||||
function ScrollTextWidget:getLineHeight()
|
||||
return self.text_widget:getLineHeight()
|
||||
end
|
||||
|
||||
function ScrollTextWidget:getCharPos()
|
||||
return self.text_widget:getCharPos()
|
||||
end
|
||||
|
||||
function ScrollTextWidget:updateScrollBar()
|
||||
local low, high = self.text_widget:getVisibleHeightRatios()
|
||||
if low ~= self.prev_low or high ~= self.prev_high then
|
||||
self.prev_low = low
|
||||
self.prev_high = high
|
||||
self.v_scroll_bar:set(low, high)
|
||||
UIManager:setDirty(self.dialog, function()
|
||||
return "partial", self.dimen
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
function ScrollTextWidget:moveCursorToCharPos(charpos)
|
||||
self.text_widget:moveCursorToCharPos(charpos)
|
||||
self:updateScrollBar()
|
||||
end
|
||||
|
||||
function ScrollTextWidget:moveCursorToXY(x, y, no_overflow)
|
||||
self.text_widget:moveCursorToXY(x, y, no_overflow)
|
||||
self:updateScrollBar()
|
||||
end
|
||||
|
||||
function ScrollTextWidget:moveCursorLeft()
|
||||
self.text_widget:moveCursorLeft();
|
||||
self:updateScrollBar()
|
||||
end
|
||||
|
||||
function ScrollTextWidget:moveCursorRight()
|
||||
self.text_widget:moveCursorRight();
|
||||
self:updateScrollBar()
|
||||
end
|
||||
|
||||
function ScrollTextWidget:moveCursorUp()
|
||||
return self.text_widget:moveCursorUp();
|
||||
self.text_widget:moveCursorUp();
|
||||
self:updateScrollBar()
|
||||
end
|
||||
|
||||
function ScrollTextWidget:moveCursorDown()
|
||||
return self.text_widget:moveCursorDown();
|
||||
self.text_widget:moveCursorDown();
|
||||
self:updateScrollBar()
|
||||
end
|
||||
|
||||
function ScrollTextWidget:scrollDown()
|
||||
self.text_widget:scrollDown();
|
||||
self:updateScrollBar()
|
||||
end
|
||||
|
||||
function ScrollTextWidget:scrollUp()
|
||||
self.text_widget:scrollUp();
|
||||
self:updateScrollBar()
|
||||
end
|
||||
|
||||
function ScrollTextWidget:scrollToTop()
|
||||
self.text_widget:scrollToTop();
|
||||
self:updateScrollBar()
|
||||
end
|
||||
|
||||
function ScrollTextWidget:scrollToBottom()
|
||||
self.text_widget:scrollToBottom();
|
||||
self:updateScrollBar()
|
||||
end
|
||||
|
||||
function ScrollTextWidget:scrollText(direction)
|
||||
if direction == 0 then return end
|
||||
local low, high
|
||||
if direction > 0 then
|
||||
low, high = self.text_widget:scrollDown()
|
||||
self.text_widget:scrollDown()
|
||||
else
|
||||
low, high = self.text_widget:scrollUp()
|
||||
self.text_widget:scrollUp()
|
||||
end
|
||||
self.v_scroll_bar:set(low, high)
|
||||
UIManager:setDirty(self.dialog, function()
|
||||
return "partial", self.dimen
|
||||
end)
|
||||
self:updateScrollBar()
|
||||
end
|
||||
|
||||
function ScrollTextWidget:scrollToRatio(ratio)
|
||||
local low, high = self.text_widget:scrollToRatio(ratio)
|
||||
self.v_scroll_bar:set(low, high)
|
||||
UIManager:setDirty(self.dialog, function()
|
||||
return "partial", self.dimen
|
||||
end)
|
||||
self.text_widget:scrollToRatio(ratio)
|
||||
self:updateScrollBar()
|
||||
end
|
||||
|
||||
function ScrollTextWidget:onScrollText(arg, ges)
|
||||
@@ -139,6 +198,10 @@ function ScrollTextWidget:onScrollText(arg, ges)
|
||||
end
|
||||
|
||||
function ScrollTextWidget:onTapScrollText(arg, ges)
|
||||
if self.editable then
|
||||
-- Tap is used to position cursor
|
||||
return false
|
||||
end
|
||||
-- same tests as done in TextBoxWidget:scrollUp/Down
|
||||
if ges.pos.x < Screen:getWidth()/2 then
|
||||
if self.text_widget.virtual_line_num > 1 then
|
||||
|
||||
@@ -33,23 +33,31 @@ local Screen = require("device").screen
|
||||
|
||||
local TextBoxWidget = InputContainer:new{
|
||||
text = nil,
|
||||
charpos = nil,
|
||||
charlist = nil, -- idx => char
|
||||
char_width = nil, -- char => width
|
||||
idx_pad = nil, -- idx => pad for char at idx, if non zero
|
||||
vertical_string_list = nil,
|
||||
editable = false, -- Editable flag for whether drawing the cursor or not.
|
||||
justified = false, -- Should text be justified (spaces widened to fill width)
|
||||
alignment = "left", -- or "center", "right"
|
||||
cursor_line = nil, -- LineWidget to draw the vertical cursor.
|
||||
dialog = nil, -- parent dialog that will be set dirty
|
||||
face = nil,
|
||||
bold = nil,
|
||||
line_height = 0.3, -- in em
|
||||
fgcolor = Blitbuffer.COLOR_BLACK,
|
||||
width = Screen:scaleBySize(400), -- in pixels
|
||||
height = nil, -- nil value indicates unscrollable text widget
|
||||
virtual_line_num = 1, -- used by scroll bar
|
||||
top_line_num = nil, -- original virtual_line_num to scroll to
|
||||
charpos = nil, -- idx of char to draw the cursor on its left (can exceed #charlist by 1)
|
||||
|
||||
-- for internal use
|
||||
charlist = nil, -- idx => char
|
||||
char_width = nil, -- char => width
|
||||
idx_pad = nil, -- idx => pad for char at idx, if non zero
|
||||
vertical_string_list = nil,
|
||||
virtual_line_num = 1, -- index of the top displayed line
|
||||
line_height_px = nil, -- height of a line in px
|
||||
lines_per_page = nil, -- number of visible lines
|
||||
text_height = nil, -- adjusted height to visible text (lines_per_page*line_height_px)
|
||||
cursor_line = nil, -- LineWidget to draw the vertical cursor.
|
||||
_bb = nil,
|
||||
|
||||
-- We can provide a list of images: each image will be displayed on each
|
||||
-- scrolled page, in its top right corner (if more images than pages, remaining
|
||||
-- images will not be displayed at all - if more pages than images, remaining
|
||||
@@ -61,7 +69,7 @@ local TextBoxWidget = InputContainer:new{
|
||||
-- optional:
|
||||
-- hi_width same as previous for a high-resolution version of the
|
||||
-- hi_height image, to be displayed by ImageViewer when Hold on
|
||||
-- hi_bb the low-resolution image
|
||||
-- hi_bb blitbuffer of high-resolution image
|
||||
-- title ImageViewer title
|
||||
-- caption ImageViewer caption
|
||||
--
|
||||
@@ -77,28 +85,46 @@ local TextBoxWidget = InputContainer:new{
|
||||
}
|
||||
|
||||
function TextBoxWidget:init()
|
||||
self.line_height_px = (1 + self.line_height) * self.face.size
|
||||
self.line_height_px = Math.round( (1 + self.line_height) * self.face.size )
|
||||
self.cursor_line = LineWidget:new{
|
||||
dimen = Geom:new{
|
||||
w = Size.line.medium,
|
||||
h = self.line_height_px,
|
||||
}
|
||||
}
|
||||
if self.height then
|
||||
-- luajit may segfault if we were provided with a negative height
|
||||
-- also ensure we display at least one line
|
||||
if self.height < self.line_height_px then
|
||||
self.height = self.line_height_px
|
||||
end
|
||||
-- if no self.height, these will be set just after self:_splitCharWidthList()
|
||||
self.lines_per_page = math.floor(self.height / self.line_height_px)
|
||||
self.text_height = self.lines_per_page * self.line_height_px
|
||||
end
|
||||
self:_evalCharWidthList()
|
||||
self:_splitCharWidthList()
|
||||
if self.height == nil then
|
||||
self:_renderText(1, #self.vertical_string_list)
|
||||
else
|
||||
-- luajit may segfault if we were provided with a negative height
|
||||
if self.height < 0 then
|
||||
self.height = 0
|
||||
end
|
||||
self:_renderText(1, self:getVisLineCount())
|
||||
if self.charpos and self.charpos > #self.charlist+1 then
|
||||
self.charpos = #self.charlist+1
|
||||
end
|
||||
|
||||
if self.height == nil then
|
||||
self.lines_per_page = #self.vertical_string_list
|
||||
self.text_height = self.lines_per_page * self.line_height_px
|
||||
self.virtual_line_num = 1
|
||||
else
|
||||
-- Show the previous displayed area in case of re-init (focus/unfocus)
|
||||
-- InputText may have re-created us, while providing the previous charlist,
|
||||
-- charpos and top_line_num.
|
||||
-- We need to show the line containing charpos, while trying to
|
||||
-- keep the previous top_line_num
|
||||
if self.editable and self.charpos then
|
||||
self:scrollViewToCharPos()
|
||||
end
|
||||
end
|
||||
self:_renderText(self.virtual_line_num, self.virtual_line_num + self.lines_per_page - 1)
|
||||
if self.editable then
|
||||
local x, y
|
||||
x, y = self:_findCharPos()
|
||||
self.cursor_line:paintTo(self._bb, x, y)
|
||||
self:moveCursorToCharPos(self.charpos or 1)
|
||||
end
|
||||
self.dimen = Geom:new(self:getSize())
|
||||
if Device:isTouchDevice() then
|
||||
@@ -115,19 +141,21 @@ end
|
||||
|
||||
function TextBoxWidget:unfocus()
|
||||
self.editable = false
|
||||
self:free()
|
||||
self:init()
|
||||
end
|
||||
|
||||
function TextBoxWidget:focus()
|
||||
self.editable = true
|
||||
self:free()
|
||||
self:init()
|
||||
end
|
||||
|
||||
-- Split `self.text` into `self.charlist` and evaluate the width of each char in it.
|
||||
function TextBoxWidget:_evalCharWidthList()
|
||||
-- if self.charlist is provided, use it directly
|
||||
if self.charlist == nil then
|
||||
self.charlist = util.splitToChars(self.text)
|
||||
self.charpos = #self.charlist + 1
|
||||
end
|
||||
-- get width of each distinct char
|
||||
local char_width = {}
|
||||
@@ -149,10 +177,6 @@ function TextBoxWidget:_splitCharWidthList()
|
||||
local ln = 1
|
||||
local offset, end_offset, cur_line_width
|
||||
|
||||
local lines_per_page
|
||||
if self.height then
|
||||
lines_per_page = self:getVisLineCount()
|
||||
end
|
||||
local image_num = 0
|
||||
local targeted_width = self.width
|
||||
local image_lines_remaining = 0
|
||||
@@ -164,8 +188,8 @@ function TextBoxWidget:_splitCharWidthList()
|
||||
if self.line_num_to_image == nil then
|
||||
self.line_num_to_image = {}
|
||||
end
|
||||
if (lines_per_page and ln % lines_per_page == 1) -- first line of a scrolled page
|
||||
or (lines_per_page == nil and ln == 1) then -- first line if not scrollabled
|
||||
if (self.lines_per_page and ln % self.lines_per_page == 1) -- first line of a scrolled page
|
||||
or (self.lines_per_page == nil and ln == 1) then -- first line if not scrollabled
|
||||
image_num = image_num + 1
|
||||
if image_num <= #self.images then
|
||||
local image = self.images[image_num]
|
||||
@@ -326,10 +350,6 @@ function TextBoxWidget:_getLinePads(vertical_string)
|
||||
return pads
|
||||
end
|
||||
|
||||
function TextBoxWidget:geCharWidth(idx)
|
||||
return self.char_width[self.charlist[idx]]
|
||||
end
|
||||
|
||||
function TextBoxWidget:_renderText(start_row_idx, end_row_idx)
|
||||
local font_height = self.face.size
|
||||
if start_row_idx < 1 then start_row_idx = 1 end
|
||||
@@ -478,7 +498,7 @@ function TextBoxWidget:_renderImage(start_row_idx)
|
||||
if scheduled_for_linenum == self.virtual_line_num then
|
||||
-- we are still on the same page
|
||||
self:update(true)
|
||||
UIManager:setDirty("all", function()
|
||||
UIManager:setDirty(self.dialog or "all", function()
|
||||
-- return "ui", self.dimen
|
||||
-- We can refresh only the image area, even if we have just
|
||||
-- re-rendered the whole textbox as the text has been
|
||||
@@ -496,7 +516,7 @@ function TextBoxWidget:_renderImage(start_row_idx)
|
||||
-- Image loaded (or not if failure): call us again
|
||||
-- with scheduled_update = true so we can draw what we got
|
||||
self:update(true)
|
||||
UIManager:setDirty("all", function()
|
||||
UIManager:setDirty(self.dialog or "all", function()
|
||||
-- return "ui", self.dimen
|
||||
-- We can refresh only the image area, even if we have just
|
||||
-- re-rendered the whole textbox as the text has been
|
||||
@@ -517,87 +537,76 @@ function TextBoxWidget:_renderImage(start_row_idx)
|
||||
end
|
||||
end
|
||||
|
||||
-- Return the position of the cursor corresponding to `self.charpos`,
|
||||
-- Be aware of virtual line number of the scorllTextWidget.
|
||||
function TextBoxWidget:_findCharPos()
|
||||
if self.text == nil or string.len(self.text) == 0 then
|
||||
return 0, 0
|
||||
end
|
||||
-- Find the line number.
|
||||
local ln = self.height == nil and 1 or self.virtual_line_num
|
||||
while ln + 1 <= #self.vertical_string_list do
|
||||
if self.vertical_string_list[ln + 1].offset > self.charpos then
|
||||
break
|
||||
else
|
||||
ln = ln + 1
|
||||
end
|
||||
end
|
||||
-- Find the offset at the current line.
|
||||
local x = 0
|
||||
local offset = self.vertical_string_list[ln].offset
|
||||
while offset < self.charpos do
|
||||
x = x + self.char_width[self.charlist[offset]] + (self.idx_pad[offset] or 0)
|
||||
offset = offset + 1
|
||||
end
|
||||
return x + 1, (ln - 1) * self.line_height_px -- offset `x` by 1 to avoid overlap
|
||||
end
|
||||
|
||||
function TextBoxWidget:moveCursorToCharpos(charpos)
|
||||
self.charpos = charpos
|
||||
local x, y = self:_findCharPos()
|
||||
self.cursor_line:paintTo(self._bb, x, y)
|
||||
end
|
||||
|
||||
-- Click event: Move the cursor to a new location with (x, y), in pixels.
|
||||
-- Be aware of virtual line number of the scorllTextWidget.
|
||||
function TextBoxWidget:moveCursor(x, y)
|
||||
if x < 0 or y < 0 then return end
|
||||
if #self.vertical_string_list == 0 then
|
||||
-- if there's no text at all, nothing to do
|
||||
return 1
|
||||
end
|
||||
local w = 0
|
||||
local ln = self.height == nil and 1 or self.virtual_line_num
|
||||
ln = ln + math.ceil(y / self.line_height_px) - 1
|
||||
if ln > #self.vertical_string_list then
|
||||
ln = #self.vertical_string_list
|
||||
x = self.width
|
||||
end
|
||||
local offset = self.vertical_string_list[ln].offset
|
||||
local idx = ln == #self.vertical_string_list and #self.charlist or self.vertical_string_list[ln + 1].offset - 1
|
||||
while offset <= idx do
|
||||
w = w + self.char_width[self.charlist[offset]] + (self.idx_pad[offset] or 0)
|
||||
if w > x then break else offset = offset + 1 end
|
||||
end
|
||||
if w > x then
|
||||
local w_prev = w - self.char_width[self.charlist[offset]] - (self.idx_pad[offset] or 0)
|
||||
if x - w_prev < w - x then -- the previous one is more closer
|
||||
w = w_prev
|
||||
else
|
||||
offset = offset + 1
|
||||
end
|
||||
end
|
||||
self:free()
|
||||
self:_renderText(1, #self.vertical_string_list)
|
||||
self.cursor_line:paintTo(self._bb, w + 1,
|
||||
(ln - self.virtual_line_num) * self.line_height_px)
|
||||
return offset
|
||||
function TextBoxWidget:getCharWidth(idx)
|
||||
return self.char_width[self.charlist[idx]]
|
||||
end
|
||||
|
||||
function TextBoxWidget:getVisLineCount()
|
||||
return math.floor(self.height / self.line_height_px)
|
||||
return self.lines_per_page
|
||||
end
|
||||
|
||||
function TextBoxWidget:getAllLineCount()
|
||||
return #self.vertical_string_list
|
||||
end
|
||||
|
||||
function TextBoxWidget:getTextHeight()
|
||||
return self.text_height
|
||||
end
|
||||
|
||||
function TextBoxWidget:getLineHeight()
|
||||
return self.line_height_px
|
||||
end
|
||||
|
||||
function TextBoxWidget:getVisibleHeightRatios()
|
||||
local low = (self.virtual_line_num - 1) / #self.vertical_string_list
|
||||
local high = (self.virtual_line_num - 1 + self.lines_per_page) / #self.vertical_string_list
|
||||
return low, high
|
||||
end
|
||||
|
||||
function TextBoxWidget:getCharPos()
|
||||
-- returns virtual_line_num too
|
||||
return self.charpos, self.virtual_line_num
|
||||
end
|
||||
|
||||
function TextBoxWidget:getSize()
|
||||
if self.width and self.height then
|
||||
return Geom:new{ w = self.width, h = self.height}
|
||||
else
|
||||
return Geom:new{ w = self.width, h = self._bb:getHeight()}
|
||||
end
|
||||
end
|
||||
|
||||
function TextBoxWidget:paintTo(bb, x, y)
|
||||
self.dimen.x, self.dimen.y = x, y
|
||||
bb:blitFrom(self._bb, x, y, 0, 0, self.width, self._bb:getHeight())
|
||||
end
|
||||
|
||||
function TextBoxWidget:free()
|
||||
logger.dbg("TextBoxWidget:free called")
|
||||
-- :free() is called when our parent widget is closing, and
|
||||
-- here whenever :_renderText() is being called, to display
|
||||
-- a new page: cancel any scheduled image update, as it
|
||||
-- is no longer related to current page
|
||||
if self.image_update_action then
|
||||
logger.dbg("TextBoxWidget:free: cancelling self.image_update_action")
|
||||
UIManager:unschedule(self.image_update_action)
|
||||
end
|
||||
if self._bb then
|
||||
self._bb:free()
|
||||
self._bb = nil
|
||||
end
|
||||
if self.cursor_restore_bb then
|
||||
self.cursor_restore_bb:free()
|
||||
self.cursor_restore_bb = nil
|
||||
end
|
||||
end
|
||||
|
||||
function TextBoxWidget:update(scheduled_update)
|
||||
self:free()
|
||||
-- We set this flag so :_renderText() can know we were called from a
|
||||
-- scheduled update and so not schedule another one
|
||||
self.scheduled_update = scheduled_update
|
||||
self:_renderText(self.virtual_line_num, self.virtual_line_num + self:getVisLineCount() - 1)
|
||||
self:_renderText(self.virtual_line_num, self.virtual_line_num + self.lines_per_page - 1)
|
||||
self.scheduled_update = nil
|
||||
end
|
||||
|
||||
@@ -614,7 +623,7 @@ function TextBoxWidget:onTapImage(arg, ges)
|
||||
-- Toggle between image and alt_text
|
||||
self.image_show_alt_text = not self.image_show_alt_text
|
||||
self:update()
|
||||
UIManager:setDirty("all", function()
|
||||
UIManager:setDirty(self.dialog or "all", function()
|
||||
-- return "ui", self.dimen
|
||||
-- We can refresh only the image area, even if we have just
|
||||
-- re-rendered the whole textbox as the text has been
|
||||
@@ -632,99 +641,420 @@ function TextBoxWidget:onTapImage(arg, ges)
|
||||
end
|
||||
end
|
||||
|
||||
-- TODO: modify `charpos` so that it can render the cursor
|
||||
function TextBoxWidget:scrollDown()
|
||||
self.image_show_alt_text = nil -- reset image bb/alt state
|
||||
local visible_line_count = self:getVisLineCount()
|
||||
if self.virtual_line_num + visible_line_count <= #self.vertical_string_list then
|
||||
if self.virtual_line_num + self.lines_per_page <= #self.vertical_string_list then
|
||||
self:free()
|
||||
self.virtual_line_num = self.virtual_line_num + visible_line_count
|
||||
self:_renderText(self.virtual_line_num, self.virtual_line_num + visible_line_count - 1)
|
||||
self.virtual_line_num = self.virtual_line_num + self.lines_per_page
|
||||
-- If last line shown, set it to be the last line of view
|
||||
-- (only if editable, as this would be confusing when reading
|
||||
-- a dictionary result or a wikipedia page)
|
||||
if self.editable then
|
||||
if self.virtual_line_num > #self.vertical_string_list - self.lines_per_page + 1 then
|
||||
self.virtual_line_num = #self.vertical_string_list - self.lines_per_page + 1
|
||||
if self.virtual_line_num < 1 then
|
||||
self.virtual_line_num = 1
|
||||
end
|
||||
end
|
||||
end
|
||||
self:_renderText(self.virtual_line_num, self.virtual_line_num + self.lines_per_page - 1)
|
||||
end
|
||||
if self.editable then
|
||||
-- move cursor to first line of visible area
|
||||
local ln = self.height == nil and 1 or self.virtual_line_num
|
||||
self:moveCursorToCharPos(self.vertical_string_list[ln] and self.vertical_string_list[ln].offset or 1)
|
||||
end
|
||||
return (self.virtual_line_num - 1) / #self.vertical_string_list, (self.virtual_line_num - 1 + visible_line_count) / #self.vertical_string_list
|
||||
end
|
||||
|
||||
-- TODO: modify `charpos` so that it can render the cursor
|
||||
function TextBoxWidget:scrollUp()
|
||||
self.image_show_alt_text = nil
|
||||
local visible_line_count = self:getVisLineCount()
|
||||
if self.virtual_line_num > 1 then
|
||||
self:free()
|
||||
if self.virtual_line_num <= visible_line_count then
|
||||
if self.virtual_line_num <= self.lines_per_page then
|
||||
self.virtual_line_num = 1
|
||||
else
|
||||
self.virtual_line_num = self.virtual_line_num - visible_line_count
|
||||
self.virtual_line_num = self.virtual_line_num - self.lines_per_page
|
||||
end
|
||||
self:_renderText(self.virtual_line_num, self.virtual_line_num + visible_line_count - 1)
|
||||
self:_renderText(self.virtual_line_num, self.virtual_line_num + self.lines_per_page - 1)
|
||||
end
|
||||
if self.editable then
|
||||
-- move cursor to first line of visible area
|
||||
local ln = self.height == nil and 1 or self.virtual_line_num
|
||||
self:moveCursorToCharPos(self.vertical_string_list[ln] and self.vertical_string_list[ln].offset or 1)
|
||||
end
|
||||
return (self.virtual_line_num - 1) / #self.vertical_string_list, (self.virtual_line_num - 1 + visible_line_count) / #self.vertical_string_list
|
||||
end
|
||||
|
||||
function TextBoxWidget:scrollToTop()
|
||||
self.image_show_alt_text = nil
|
||||
if self.virtual_line_num > 1 then
|
||||
self:free()
|
||||
self.virtual_line_num = 1
|
||||
self:_renderText(self.virtual_line_num, self.virtual_line_num + self.lines_per_page - 1)
|
||||
end
|
||||
if self.editable then
|
||||
-- move cursor to first char
|
||||
self:moveCursorToCharPos(1)
|
||||
end
|
||||
end
|
||||
|
||||
function TextBoxWidget:scrollToBottom()
|
||||
self.image_show_alt_text = nil
|
||||
-- Show last line of text on last line of view
|
||||
local ln = #self.vertical_string_list - self.lines_per_page + 1
|
||||
if ln < 1 then
|
||||
ln = 1
|
||||
end
|
||||
if self.virtual_line_num ~= ln then
|
||||
self:free()
|
||||
self.virtual_line_num = ln
|
||||
self:_renderText(self.virtual_line_num, self.virtual_line_num + self.lines_per_page - 1)
|
||||
end
|
||||
if self.editable then
|
||||
-- move cursor to last char
|
||||
self:moveCursorToCharPos(#self.charlist + 1)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function TextBoxWidget:scrollToRatio(ratio)
|
||||
self.image_show_alt_text = nil
|
||||
ratio = math.max(0, math.min(1, ratio)) -- ensure ratio is between 0 and 1 (100%)
|
||||
local visible_line_count = self:getVisLineCount()
|
||||
local page_count = 1 + math.floor((#self.vertical_string_list - 1) / visible_line_count)
|
||||
local page_count = 1 + math.floor((#self.vertical_string_list - 1) / self.lines_per_page)
|
||||
local page_num = 1 + Math.round((page_count - 1) * ratio)
|
||||
local line_num = 1 + (page_num - 1) * visible_line_count
|
||||
local line_num = 1 + (page_num - 1) * self.lines_per_page
|
||||
if line_num ~= self.virtual_line_num then
|
||||
self:free()
|
||||
self.virtual_line_num = line_num
|
||||
self:_renderText(self.virtual_line_num, self.virtual_line_num + visible_line_count - 1)
|
||||
self:_renderText(self.virtual_line_num, self.virtual_line_num + self.lines_per_page - 1)
|
||||
end
|
||||
if self.editable then
|
||||
-- move cursor to first line of visible area
|
||||
local ln = self.height == nil and 1 or self.virtual_line_num
|
||||
self:moveCursorToCharPos(self.vertical_string_list[ln].offset)
|
||||
end
|
||||
return (self.virtual_line_num - 1) / #self.vertical_string_list, (self.virtual_line_num - 1 + visible_line_count) / #self.vertical_string_list
|
||||
end
|
||||
|
||||
function TextBoxWidget:getSize()
|
||||
if self.width and self.height then
|
||||
return Geom:new{ w = self.width, h = self.height}
|
||||
else
|
||||
return Geom:new{ w = self.width, h = self._bb:getHeight()}
|
||||
|
||||
--- Cursor management
|
||||
|
||||
-- Return the coordinates (relative to current view, so negative y is possible)
|
||||
-- of the left of char at charpos (use self.charpos if none provided)
|
||||
function TextBoxWidget:_getXYForCharPos(charpos)
|
||||
if not charpos then
|
||||
charpos = self.charpos
|
||||
end
|
||||
if self.text == nil or string.len(self.text) == 0 then
|
||||
return 0, 0
|
||||
end
|
||||
-- Find the line number: scan up/down from current virtual_line_num
|
||||
local ln = self.height == nil and 1 or self.virtual_line_num
|
||||
if charpos > self.vertical_string_list[ln].offset then -- after first line
|
||||
while ln < #self.vertical_string_list do
|
||||
if self.vertical_string_list[ln + 1].offset > charpos then
|
||||
break
|
||||
else
|
||||
ln = ln + 1
|
||||
end
|
||||
end
|
||||
elseif charpos < self.vertical_string_list[ln].offset then -- before first line
|
||||
while ln > 1 do
|
||||
ln = ln - 1
|
||||
if self.vertical_string_list[ln].offset <= charpos then
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
local y = (ln - self.virtual_line_num) * self.line_height_px
|
||||
-- Find the x offset in the current line.
|
||||
local x = 0
|
||||
local offset = self.vertical_string_list[ln].offset
|
||||
local nbchars = #self.charlist
|
||||
while offset < charpos do
|
||||
if offset <= nbchars then -- charpos may exceed #self.charlist
|
||||
x = x + self.char_width[self.charlist[offset]] + (self.idx_pad[offset] or 0)
|
||||
end
|
||||
offset = offset + 1
|
||||
end
|
||||
-- Cursor can be drawn at x, it will be on the left of the char pointed by charpos
|
||||
-- (x=0 for first char of line - for end of line, it will be before the \n, the \n
|
||||
-- itself being not displayed)
|
||||
return x, y
|
||||
end
|
||||
|
||||
-- Return the charpos at provided coordinates (relative to current view,
|
||||
-- so negative y is allowed)
|
||||
function TextBoxWidget:getCharPosAtXY(x, y)
|
||||
if #self.vertical_string_list == 0 then
|
||||
-- if there's no text at all, nothing to do
|
||||
return 1
|
||||
end
|
||||
local ln = self.height == nil and 1 or self.virtual_line_num
|
||||
ln = ln + math.floor(y / self.line_height_px)
|
||||
if ln < 1 then
|
||||
return 1 -- return start of first line
|
||||
elseif ln > #self.vertical_string_list then
|
||||
return #self.charlist + 1 -- return end of last line
|
||||
end
|
||||
if x > self.vertical_string_list[ln].width then -- no need to loop thru chars
|
||||
local pos = self.vertical_string_list[ln].end_offset
|
||||
if not pos then -- empty line
|
||||
pos = self.vertical_string_list[ln].offset
|
||||
end
|
||||
return pos + 1 -- after last char
|
||||
end
|
||||
local idx = self.vertical_string_list[ln].offset
|
||||
local end_offset = self.vertical_string_list[ln].end_offset
|
||||
if not end_offset then -- empty line
|
||||
return idx
|
||||
end
|
||||
local w = 0
|
||||
local w_prev
|
||||
while idx <= end_offset do
|
||||
w_prev = w
|
||||
w = w + self.char_width[self.charlist[idx]] + (self.idx_pad[idx] or 0)
|
||||
if w > x then -- we're on this char at idx
|
||||
if x - w_prev < w - x then -- nearest to char start
|
||||
return idx
|
||||
else -- nearest to char end: draw cursor before next char
|
||||
return idx + 1
|
||||
end
|
||||
break
|
||||
end
|
||||
idx = idx + 1
|
||||
end
|
||||
return end_offset + 1 -- should not happen
|
||||
end
|
||||
|
||||
-- Tunables for the next function: not sure yet which combination is
|
||||
-- best to get the less cursor trail - and initially got some crashes
|
||||
-- when using refresh funcs. It finally feels fine with both set to true,
|
||||
-- but one can turn them to false with a setting to check how some other
|
||||
-- combinations do.
|
||||
local CURSOR_COMBINE_REGIONS = G_reader_settings:nilOrTrue("ui_cursor_combine_regions")
|
||||
local CURSOR_USE_REFRESH_FUNCS = G_reader_settings:nilOrTrue("ui_cursor_use_refresh_funcs")
|
||||
|
||||
-- Update charpos to the one provided; if out of current view, update
|
||||
-- virtual_line_num to move it to view, and draw the cursor
|
||||
function TextBoxWidget:moveCursorToCharPos(charpos)
|
||||
if not self.editable then
|
||||
-- we shouldn't have been called if not editable
|
||||
logger.warn("TextBoxWidget:moveCursorToCharPos called, but not editable")
|
||||
return
|
||||
end
|
||||
self.charpos = charpos
|
||||
self.prev_virtual_line_num = self.virtual_line_num
|
||||
local x, y = self:_getXYForCharPos() -- we can get y outside current view
|
||||
-- adjust self.virtual_line_num for overflowed y to have y in current view
|
||||
if y < 0 then
|
||||
local scroll_lines = math.ceil( -y / self.line_height_px )
|
||||
self.virtual_line_num = self.virtual_line_num - scroll_lines
|
||||
if self.virtual_line_num < 1 then
|
||||
self.virtual_line_num = 1
|
||||
end
|
||||
y = y + scroll_lines * self.line_height_px
|
||||
end
|
||||
if y >= self.text_height then
|
||||
local scroll_lines = math.floor( (y-self.text_height) / self.line_height_px ) + 1
|
||||
self.virtual_line_num = self.virtual_line_num + scroll_lines
|
||||
-- needs to deal with possible overflow ?
|
||||
y = y - scroll_lines * self.line_height_px
|
||||
end
|
||||
if not self._bb then
|
||||
return -- no bb yet to render the cursor too
|
||||
end
|
||||
if self.virtual_line_num ~= self.prev_virtual_line_num then
|
||||
-- We scrolled the view: full render and refresh needed
|
||||
self:free()
|
||||
self:_renderText(self.virtual_line_num, self.virtual_line_num + self.lines_per_page - 1)
|
||||
-- Store the original image of where we will draw the cursor, for a
|
||||
-- quick restore and two small refreshes when moving only the cursor
|
||||
self.cursor_restore_x = x
|
||||
self.cursor_restore_y = y
|
||||
self.cursor_restore_bb = Blitbuffer.new(self.cursor_line.dimen.w, self.cursor_line.dimen.h, self._bb:getType())
|
||||
self.cursor_restore_bb:blitFrom(self._bb, 0, 0, x, y, self.cursor_line.dimen.w, self.cursor_line.dimen.h)
|
||||
-- Paint the cursor, and refresh the whole widget
|
||||
self.cursor_line:paintTo(self._bb, x, y)
|
||||
UIManager:setDirty(self.dialog or "all", function()
|
||||
return "ui", self.dimen
|
||||
end)
|
||||
elseif self._bb then
|
||||
if CURSOR_USE_REFRESH_FUNCS then
|
||||
-- We didn't scroll the view, only the cursor was moved
|
||||
local restore_x, restore_y
|
||||
if self.cursor_restore_bb then
|
||||
-- Restore the previous cursor position content, and do
|
||||
-- a small ui refresh of the old cursor area
|
||||
self._bb:blitFrom(self.cursor_restore_bb, self.cursor_restore_x, self.cursor_restore_y,
|
||||
0, 0, self.cursor_line.dimen.w, self.cursor_line.dimen.h)
|
||||
-- remember current values for use in the setDirty funcs, as
|
||||
-- we will have overriden them when these are called
|
||||
restore_x = self.cursor_restore_x
|
||||
restore_y = self.cursor_restore_y
|
||||
if not CURSOR_COMBINE_REGIONS then
|
||||
UIManager:setDirty(self.dialog or "all", function()
|
||||
return "ui", Geom:new{
|
||||
x = self.dimen.x + restore_x,
|
||||
y = self.dimen.y + restore_y,
|
||||
w = self.cursor_line.dimen.w,
|
||||
h = self.cursor_line.dimen.h,
|
||||
}
|
||||
end)
|
||||
end
|
||||
self.cursor_restore_bb:free()
|
||||
self.cursor_restore_bb = nil
|
||||
end
|
||||
-- Store the original image of where we will draw the new cursor
|
||||
self.cursor_restore_x = x
|
||||
self.cursor_restore_y = y
|
||||
self.cursor_restore_bb = Blitbuffer.new(self.cursor_line.dimen.w, self.cursor_line.dimen.h, self._bb:getType())
|
||||
self.cursor_restore_bb:blitFrom(self._bb, 0, 0, x, y, self.cursor_line.dimen.w, self.cursor_line.dimen.h)
|
||||
-- Paint the cursor, and do a small ui refresh of the new cursor area
|
||||
self.cursor_line:paintTo(self._bb, x, y)
|
||||
UIManager:setDirty(self.dialog or "all", function()
|
||||
local cursor_region = Geom:new{
|
||||
x = self.dimen.x + x,
|
||||
y = self.dimen.y + y,
|
||||
w = self.cursor_line.dimen.w,
|
||||
h = self.cursor_line.dimen.h,
|
||||
}
|
||||
if CURSOR_COMBINE_REGIONS and restore_x and restore_y then
|
||||
local restore_region = Geom:new{
|
||||
x = self.dimen.x + restore_x,
|
||||
y = self.dimen.y + restore_y,
|
||||
w = self.cursor_line.dimen.w,
|
||||
h = self.cursor_line.dimen.h,
|
||||
}
|
||||
cursor_region = cursor_region:combine(restore_region)
|
||||
end
|
||||
return "ui", cursor_region
|
||||
end)
|
||||
else -- CURSOR_USE_REFRESH_FUNCS = false
|
||||
-- We didn't scroll the view, only the cursor was moved
|
||||
local restore_region
|
||||
if self.cursor_restore_bb then
|
||||
-- Restore the previous cursor position content, and do
|
||||
-- a small ui refresh of the old cursor area
|
||||
self._bb:blitFrom(self.cursor_restore_bb, self.cursor_restore_x, self.cursor_restore_y,
|
||||
0, 0, self.cursor_line.dimen.w, self.cursor_line.dimen.h)
|
||||
if self.dimen then
|
||||
restore_region = Geom:new{
|
||||
x = self.dimen.x + self.cursor_restore_x,
|
||||
y = self.dimen.y + self.cursor_restore_y,
|
||||
w = self.cursor_line.dimen.w,
|
||||
h = self.cursor_line.dimen.h,
|
||||
}
|
||||
if not CURSOR_COMBINE_REGIONS then
|
||||
UIManager:setDirty(self.dialog or "all", "ui", restore_region)
|
||||
end
|
||||
end
|
||||
self.cursor_restore_bb:free()
|
||||
self.cursor_restore_bb = nil
|
||||
end
|
||||
-- Store the original image of where we will draw the new cursor
|
||||
self.cursor_restore_x = x
|
||||
self.cursor_restore_y = y
|
||||
self.cursor_restore_bb = Blitbuffer.new(self.cursor_line.dimen.w, self.cursor_line.dimen.h, self._bb:getType())
|
||||
self.cursor_restore_bb:blitFrom(self._bb, 0, 0, x, y, self.cursor_line.dimen.w, self.cursor_line.dimen.h)
|
||||
-- Paint the cursor, and do a small ui refresh of the new cursor area
|
||||
self.cursor_line:paintTo(self._bb, x, y)
|
||||
if self.dimen then
|
||||
local cursor_region = Geom:new{
|
||||
x = self.dimen.x + x,
|
||||
y = self.dimen.y + y,
|
||||
w = self.cursor_line.dimen.w,
|
||||
h = self.cursor_line.dimen.h,
|
||||
}
|
||||
if CURSOR_COMBINE_REGIONS and restore_region then
|
||||
cursor_region = cursor_region:combine(restore_region)
|
||||
end
|
||||
UIManager:setDirty(self.dialog or "all", "ui", cursor_region)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function TextBoxWidget:moveCursorToXY(x, y, restrict_to_view)
|
||||
if restrict_to_view then
|
||||
-- Wrap y to current view (when getting coordinates from gesture)
|
||||
-- (no real need to check for x, getCharPosAtXY() is ok with any x)
|
||||
if y < 0 then
|
||||
y = 0
|
||||
end
|
||||
if y >= self.text_height then
|
||||
y = self.text_height - 1
|
||||
end
|
||||
end
|
||||
local charpos = self:getCharPosAtXY(x, y)
|
||||
self:moveCursorToCharPos(charpos)
|
||||
end
|
||||
|
||||
-- Update self.virtual_line_num to the page containing charpos
|
||||
function TextBoxWidget:scrollViewToCharPos()
|
||||
if self.top_line_num then
|
||||
-- if previous top_line_num provided, go to that line
|
||||
self.virtual_line_num = self.top_line_num
|
||||
if self.virtual_line_num < 1 then
|
||||
self.virtual_line_num = 1
|
||||
end
|
||||
if self.virtual_line_num > #self.vertical_string_list then
|
||||
self.virtual_line_num = #self.vertical_string_list
|
||||
end
|
||||
-- Ensure we don't show too much blank at end (when deleting last lines)
|
||||
-- local max_empty_lines = math.floor(self.lines_per_page / 2)
|
||||
-- Best to not allow any, for initially non-scrolled widgets
|
||||
local max_empty_lines = 0
|
||||
local max_virtual_line_num = #self.vertical_string_list - self.lines_per_page + 1 + max_empty_lines
|
||||
if self.virtual_line_num > max_virtual_line_num then
|
||||
self.virtual_line_num = max_virtual_line_num
|
||||
if self.virtual_line_num < 1 then
|
||||
self.virtual_line_num = 1
|
||||
end
|
||||
end
|
||||
-- and adjust if cursor is out of view
|
||||
self:moveCursorToCharPos(self.charpos)
|
||||
return
|
||||
end
|
||||
-- Otherwise, find the "hard" page containing charpos
|
||||
local ln = 1
|
||||
while true do
|
||||
local lend = ln + self.lines_per_page - 1
|
||||
if lend >= #self.vertical_string_list then
|
||||
break -- last page
|
||||
end
|
||||
if self.vertical_string_list[lend+1].offset >= self.charpos then
|
||||
break
|
||||
end
|
||||
ln = ln + self.lines_per_page
|
||||
end
|
||||
self.virtual_line_num = ln
|
||||
end
|
||||
|
||||
function TextBoxWidget:moveCursorLeft()
|
||||
if self.charpos > 1 then
|
||||
self:moveCursorToCharPos(self.charpos-1)
|
||||
end
|
||||
end
|
||||
|
||||
function TextBoxWidget:moveCursorRight()
|
||||
if self.charpos < #self.charlist + 1 then -- we can move after last char
|
||||
self:moveCursorToCharPos(self.charpos+1)
|
||||
end
|
||||
end
|
||||
|
||||
function TextBoxWidget:moveCursorUp()
|
||||
if self.vertical_string_list and #self.vertical_string_list < 2 then return end
|
||||
local x, y
|
||||
x, y = self:_findCharPos()
|
||||
local charpos = self:moveCursor(x, y - self.line_height_px +1)
|
||||
if charpos then
|
||||
self:moveCursorToCharpos(charpos)
|
||||
end
|
||||
return charpos
|
||||
local x, y = self:_getXYForCharPos()
|
||||
self:moveCursorToXY(x, y - self.line_height_px)
|
||||
end
|
||||
|
||||
function TextBoxWidget:moveCursorDown()
|
||||
if self.vertical_string_list and #self.vertical_string_list < 2 then return end
|
||||
local x, y
|
||||
x, y = self:_findCharPos()
|
||||
local charpos = self:moveCursor(x, y + self.line_height_px +1)
|
||||
if charpos then
|
||||
self:moveCursorToCharpos(charpos)
|
||||
end
|
||||
return charpos
|
||||
local x, y = self:_getXYForCharPos()
|
||||
self:moveCursorToXY(x, y + self.line_height_px)
|
||||
end
|
||||
|
||||
function TextBoxWidget:paintTo(bb, x, y)
|
||||
self.dimen.x, self.dimen.y = x, y
|
||||
bb:blitFrom(self._bb, x, y, 0, 0, self.width, self._bb:getHeight())
|
||||
end
|
||||
|
||||
function TextBoxWidget:free()
|
||||
logger.dbg("TextBoxWidget:free called")
|
||||
-- :free() is called when our parent widget is closing, and
|
||||
-- here whenever :_renderText() is being called, to display
|
||||
-- a new page: cancel any scheduled image update, as it
|
||||
-- is no more related to current page
|
||||
if self.image_update_action then
|
||||
logger.dbg("TextBoxWidget:free: cancelling self.image_update_action")
|
||||
UIManager:unschedule(self.image_update_action)
|
||||
end
|
||||
if self._bb then
|
||||
self._bb:free()
|
||||
self._bb = nil
|
||||
end
|
||||
end
|
||||
--- Text selection with Hold
|
||||
|
||||
-- Allow selection of a single word at hold position
|
||||
function TextBoxWidget:onHoldWord(callback, ges)
|
||||
@@ -771,7 +1101,6 @@ function TextBoxWidget:onHoldWord(callback, ges)
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
-- Allow selection of one or more words (with no visual feedback)
|
||||
-- Gestures should be declared in widget using us (e.g dictquicklookup.lua)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user