VirtualKeyboard: Revamp visibility handling (#10852)

Move as much of the state tracking as possible inside VirtualKeyboard itself.
InputDialog unfortunately needs an internal tracking of this state because it needs to know about it *before* the VK is shown, so we have to keep a bit of duplication in there, although we do try much harder to keep everything in sync (at least at function call edges), and to keep the damage contained to, essentially, the toggle button's handler.

(Followup to #10803 & #10850)
reviewable/pr10874/r1
NiLuJe 9 months ago committed by GitHub
parent 4d620d9fd2
commit 4cc620b702
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -920,10 +920,10 @@ function ReaderStyleTweak:editBookTweak(touchmenu_instance)
end end
end end
end, end,
-- Set/save view and cursor position callback -- Store/retrieve view and cursor position callback
view_pos_callback = function(top_line_num, charpos) view_pos_callback = function(top_line_num, charpos)
-- This same callback is called with no argument to get initial position, -- This same callback is called with no arguments on init to retrieve the stored initial position,
-- and with arguments to give back final position when closed. -- and with arguments to store the final position on close.
if top_line_num and charpos then if top_line_num and charpos then
self.book_style_tweak_last_edit_pos = {top_line_num, charpos} self.book_style_tweak_last_edit_pos = {top_line_num, charpos}
else else

@ -141,7 +141,7 @@ local InputDialog = FocusManager:extend{
add_scroll_buttons = false, -- add scroll Up/Down buttons to first row of buttons 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 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 -- note that the text widget can be scrolled with Swipe North/South even when no button
keyboard_hidden = false, -- start with keyboard hidden in full fullscreen mode keyboard_visible = true, -- whether we start with the keyboard visible or not (i.e., our caller skipped onShowKeyboard)
-- needs add_nav_bar to have a Show keyboard button to get it back -- needs add_nav_bar to have a Show keyboard button to get it back
scroll_by_pan = false, -- allow scrolling by lines with Pan (= Swipe, but wait a bit at end scroll_by_pan = false, -- allow scrolling by lines with Pan (= Swipe, but wait a bit at end
-- of gesture before releasing) (may conflict with movable) -- of gesture before releasing) (may conflict with movable)
@ -162,8 +162,8 @@ local InputDialog = FocusManager:extend{
edited_callback = nil, -- Called on each text modification edited_callback = nil, -- Called on each text modification
-- For use by TextEditor plugin: -- For use by TextEditor plugin:
view_pos_callback = nil, -- Called with no arg to get initial top_line_num/charpos, view_pos_callback = nil, -- Called with no args on init to retrieve top_line_num/charpos (however the caller chooses to do so, e.g., some will store it in a LuaSettings),
-- called with (top_line_num, charpos) to give back position on close. -- called with (top_line_num, charpos) on close to let the callback do its thing so that the no args branch spits back useful data..
-- Set to false if movable gestures conflicts with subwidgets gestures -- Set to false if movable gestures conflicts with subwidgets gestures
is_movable = true, is_movable = true,
@ -189,6 +189,7 @@ local InputDialog = FocusManager:extend{
alignment_strict = false, alignment_strict = false,
-- for internal use -- for internal use
_keyboard_was_visible = nil, -- previous kb visibility state
_text_modified = false, -- previous known modified status _text_modified = false, -- previous known modified status
_top_line_num = nil, _top_line_num = nil,
_charpos = nil, _charpos = nil,
@ -216,7 +217,7 @@ function InputDialog:init()
self.text_width = self.text_width or math.floor(self.width * 0.9) self.text_width = self.text_width or math.floor(self.width * 0.9)
end end
if self.readonly then -- hide keyboard if we can't edit if self.readonly then -- hide keyboard if we can't edit
self.keyboard_hidden = true self.keyboard_visible = false
end end
if self.fullscreen or self.add_nav_bar then if self.fullscreen or self.add_nav_bar then
self.deny_keyboard_hiding = true self.deny_keyboard_hiding = true
@ -299,10 +300,7 @@ function InputDialog:init()
local text_height = input_widget:getTextHeight() local text_height = input_widget:getTextHeight()
local line_height = input_widget:getLineHeight() local line_height = input_widget:getLineHeight()
local input_pad_height = input_widget:getSize().h - text_height local input_pad_height = input_widget:getSize().h - text_height
local keyboard_height = 0 local keyboard_height = self.keyboard_visible and input_widget:getKeyboardDimen().h or 0
if not self.keyboard_hidden then
keyboard_height = input_widget:getKeyboardDimen().h
end
input_widget:onCloseWidget() -- free() textboxwidget and keyboard input_widget:onCloseWidget() -- free() textboxwidget and keyboard
-- Find out available height -- Find out available height
local available_height = self.screen_height local available_height = self.screen_height
@ -331,9 +329,14 @@ function InputDialog:init()
end end
end end
if self.view_pos_callback then if self.view_pos_callback then
-- Get initial cursor and top line num from callback -- Retrieve cursor position and top line num from our callback.
-- (will work in case of re-init as these are saved by onClose() -- Mainly used for runtime re-inits.
self._top_line_num, self._charpos = self.view_pos_callback() -- c.f., our onClose handler for the other end of this.
-- *May* return nils, in which case, we do *not* want to override our caller's values!
local top_line_num, charpos = self.view_pos_callback()
if top_line_num and charpos then
self._top_line_num, self._charpos = top_line_num, charpos
end
end end
self._input_widget = self.inputtext_class:new{ self._input_widget = self.inputtext_class:new{
text = self.input, text = self.input,
@ -368,7 +371,6 @@ function InputDialog:init()
scroll_by_pan = self.scroll_by_pan, scroll_by_pan = self.scroll_by_pan,
cursor_at_end = self.cursor_at_end, cursor_at_end = self.cursor_at_end,
readonly = self.readonly, readonly = self.readonly,
manage_keyboard_state = not self.add_nav_bar, -- we handle keyboard toggle ourselve if nav_bar
parent = self, parent = self,
is_text_edited = self._text_modified, is_text_edited = self._text_modified,
top_line_num = self._top_line_num, top_line_num = self._top_line_num,
@ -423,8 +425,7 @@ function InputDialog:init()
} }
frame = self.movable frame = self.movable
end end
local keyboard_height = self.keyboard_hidden and 0 local keyboard_height = self.keyboard_visible and self._input_widget:getKeyboardDimen().h or 0
or self._input_widget:getKeyboardDimen().h
self[1] = CenterContainer:new{ self[1] = CenterContainer:new{
dimen = Geom:new{ dimen = Geom:new{
w = self.screen_width, w = self.screen_width,
@ -449,6 +450,11 @@ function InputDialog:init()
self:addWidget(widget, true) self:addWidget(widget, true)
end end
end end
-- If we're fullscreen without a keyboard, make sure only the toggle button can show the keyboard...
if self.fullscreen and not self.keyboard_visible then
self:lockKeyboard(true)
end
end end
function InputDialog:addWidget(widget, re_init) function InputDialog:addWidget(widget, re_init)
@ -474,13 +480,13 @@ function InputDialog:getAddedWidgetAvailableWidth()
return self._input_widget.width return self._input_widget.width
end end
-- Close the keyboard if we tap anywhere outside of the keyboard (that isn't an input field, where it would be caught via InputText:onTapTextBox)
function InputDialog:onTap() function InputDialog:onTap()
-- This is slightly more fine-grained than VK's own visibility lock, hence the duplication...
if self.deny_keyboard_hiding then if self.deny_keyboard_hiding then
return return
end end
if self._input_widget.onCloseKeyboard then self:onCloseKeyboard()
self._input_widget:onCloseKeyboard()
end
end end
function InputDialog:getInputText() function InputDialog:getInputText()
@ -525,24 +531,79 @@ function InputDialog:onCloseWidget()
end end
function InputDialog:onShowKeyboard(ignore_first_hold_release) function InputDialog:onShowKeyboard(ignore_first_hold_release)
if not self.readonly and not self.keyboard_hidden then -- NOTE: There's no VirtualKeyboard widget instantiated at all when readonly,
self._input_widget:onShowKeyboard(ignore_first_hold_release) -- and our input widget handles that itself, so we don't need any guards here.
end -- (In which case, isKeyboardVisible will return `nil`, same as if we had a VK instantiated but *never* shown).
self._input_widget:onShowKeyboard(ignore_first_hold_release)
-- There's a bit of a chicken or egg issue in init where we would like to check the actual keyboard's visibility state,
-- but the widget might not exist or be shown yet, so we'll just have to keep this in sync...
self.keyboard_visible = self._input_widget:isKeyboardVisible()
end
function InputDialog:onCloseKeyboard()
self._input_widget:onCloseKeyboard()
self.keyboard_visible = self._input_widget:isKeyboardVisible()
end
function InputDialog:isKeyboardVisible()
return self._input_widget:isKeyboardVisible()
end end
function InputDialog:toggleKeyboard(force_hide) function InputDialog:lockKeyboard(toggle)
if force_hide and self.keyboard_hidden then return end return self._input_widget:lockKeyboard(toggle)
self.keyboard_hidden = not self.keyboard_hidden end
-- NOTE: Only called by fullscreen and/or add_nav_bar codepaths
-- We do not currently have !fullscreen add_nav_bar callers...
function InputDialog:toggleKeyboard(force_toggle)
-- Remember the *current* visibility, as the following close will reset it
local visible = self:isKeyboardVisible()
-- When we forcibly close the keyboard, remember its current visiblity state, so that we can properly restore it later.
-- (This is used by some buttons in fullscreen mode, where we might want to keep the original keyboard hidden when popping up a new one for another InputDialog).
if force_toggle == false then
-- NOTE: visible will be nil between our own init and a show of the keyboard, which is precisely what happens when we *hide* the keyboard.
self._keyboard_was_visible = visible == true
end
self.input = self:getInputText() -- re-init with up-to-date text self.input = self:getInputText() -- re-init with up-to-date text
self:onClose() -- will close keyboard and save view position self:onClose() -- will close keyboard and save view position
self:free() self:free()
if force_toggle == false and not visible then
-- Already hidden, bye!
return
end
-- Init needs to know the keyboard's visibility state *before* the widget is actually shown...
if force_toggle == true then
self.keyboard_visible = true
elseif force_toggle == false then
self.keyboard_visible = false
elseif self._keyboard_was_visible ~= nil then
self.keyboard_visible = self._keyboard_was_visible
self._keyboard_was_visible = nil
else
self.keyboard_visible = not visible
end
self:init() self:init()
if not self.keyboard_hidden then
-- NOTE: If we ever have non-fullscreen add_nav_bar callers, it might make sense *not* to lock the keyboard there?
if self.keyboard_visible then
self:lockKeyboard(false)
self:onShowKeyboard() self:onShowKeyboard()
else
self:onCloseKeyboard()
-- Prevent InputText:onTapTextBox from opening the keyboard back up on top of our buttons
self:lockKeyboard(true)
end end
-- Make sure we refresh the nav bar, as it will have moved, and it belongs to us, not to VK or our input widget...
self:refreshButtons()
end end
function InputDialog:onKeyboardHeightChanged() function InputDialog:onKeyboardHeightChanged()
local visible = self:isKeyboardVisible()
self.input = self:getInputText() -- re-init with up-to-date text self.input = self:getInputText() -- re-init with up-to-date text
self:onClose() -- will close keyboard and save view position self:onClose() -- will close keyboard and save view position
self._input_widget:onCloseWidget() -- proper cleanup of InputText and its keyboard self._input_widget:onCloseWidget() -- proper cleanup of InputText and its keyboard
@ -555,8 +616,11 @@ function InputDialog:onKeyboardHeightChanged()
self:free() self:free()
-- Restore original text_height (or reset it if none to force recomputing it) -- Restore original text_height (or reset it if none to force recomputing it)
self.text_height = self.orig_text_height or nil self.text_height = self.orig_text_height or nil
-- Same deal as in toggleKeyboard...
self.keyboard_visible = visible
self:init() self:init()
if not self.keyboard_hidden then if self.keyboard_visible then
self:onShowKeyboard() self:onShowKeyboard()
end end
-- Our position on screen has probably changed, so have the full screen refreshed -- Our position on screen has probably changed, so have the full screen refreshed
@ -573,14 +637,16 @@ function InputDialog:onCloseDialog()
end end
function InputDialog:onClose() function InputDialog:onClose()
-- Tell our input widget to poke its text widget so that we'll pickup up to date values
self._input_widget:resyncPos()
-- Remember current view & position in case of re-init -- Remember current view & position in case of re-init
self._top_line_num = self._input_widget.top_line_num self._top_line_num = self._input_widget.top_line_num
self._charpos = self._input_widget.charpos self._charpos = self._input_widget.charpos
if self.view_pos_callback then if self.view_pos_callback then
-- Give back top line num and cursor position -- This lets the caller store/process the current top line num and cursor position via this callback
self.view_pos_callback(self._top_line_num, self._charpos) self.view_pos_callback(self._top_line_num, self._charpos)
end end
self._input_widget:onCloseKeyboard() self:onCloseKeyboard()
end end
function InputDialog:refreshButtons() function InputDialog:refreshButtons()
@ -764,7 +830,7 @@ function InputDialog:_addScrollButtons(nav_bar)
-- Also add Keyboard hide/show button if we can -- Also add Keyboard hide/show button if we can
if self.fullscreen and not self.readonly then if self.fullscreen and not self.readonly then
table.insert(row, { table.insert(row, {
text = self.keyboard_hidden and "↑⌨" or "", text = self.keyboard_visible and "↓⌨" or "",
id = "keyboard", id = "keyboard",
callback = function() callback = function()
self:toggleKeyboard() self:toggleKeyboard()
@ -776,8 +842,7 @@ function InputDialog:_addScrollButtons(nav_bar)
table.insert(row, { table.insert(row, {
text = _("Find"), text = _("Find"),
callback = function() callback = function()
local keyboard_hidden_state = not self.keyboard_hidden self:toggleKeyboard(false) -- hide text editor keyboard
self:toggleKeyboard(true) -- hide text editor keyboard
local input_dialog local input_dialog
input_dialog = InputDialog:new{ input_dialog = InputDialog:new{
title = _("Enter text to search for"), title = _("Enter text to search for"),
@ -790,21 +855,20 @@ function InputDialog:_addScrollButtons(nav_bar)
id = "close", id = "close",
callback = function() callback = function()
UIManager:close(input_dialog) UIManager:close(input_dialog)
self.keyboard_hidden = keyboard_hidden_state
self:toggleKeyboard() self:toggleKeyboard()
end, end,
}, },
{ {
text = _("Find first"), text = _("Find first"),
callback = function() callback = function()
self:findCallback(keyboard_hidden_state, input_dialog, true) self:findCallback(input_dialog, true)
end, end,
}, },
{ {
text = _("Find next"), text = _("Find next"),
is_enter_default = true, is_enter_default = true,
callback = function() callback = function()
self:findCallback(keyboard_hidden_state, input_dialog) self:findCallback(input_dialog)
end, end,
}, },
}, },
@ -829,8 +893,7 @@ function InputDialog:_addScrollButtons(nav_bar)
table.insert(row, { table.insert(row, {
text = _("Go"), text = _("Go"),
callback = function() callback = function()
local keyboard_hidden_state = not self.keyboard_hidden self:toggleKeyboard(false) -- hide text editor keyboard
self:toggleKeyboard(true) -- hide text editor keyboard
local cur_line_num, last_line_num = self._input_widget:getLineNums() local cur_line_num, last_line_num = self._input_widget:getLineNums()
local input_dialog local input_dialog
input_dialog = InputDialog:new{ input_dialog = InputDialog:new{
@ -846,7 +909,6 @@ function InputDialog:_addScrollButtons(nav_bar)
id = "close", id = "close",
callback = function() callback = function()
UIManager:close(input_dialog) UIManager:close(input_dialog)
self.keyboard_hidden = keyboard_hidden_state
self:toggleKeyboard() self:toggleKeyboard()
end, end,
}, },
@ -857,7 +919,6 @@ function InputDialog:_addScrollButtons(nav_bar)
local new_line_num = tonumber(input_dialog:getInputText()) local new_line_num = tonumber(input_dialog:getInputText())
if new_line_num and new_line_num >= 1 and new_line_num <= last_line_num then if new_line_num and new_line_num >= 1 and new_line_num <= last_line_num then
UIManager:close(input_dialog) UIManager:close(input_dialog)
self.keyboard_hidden = keyboard_hidden_state
self:toggleKeyboard() self:toggleKeyboard()
self._input_widget:moveCursorToCharPos(self._input_widget:getLineCharPos(new_line_num)) self._input_widget:moveCursorToCharPos(self._input_widget:getLineCharPos(new_line_num))
end end
@ -939,11 +1000,10 @@ function InputDialog:_addScrollButtons(nav_bar)
end end
end end
function InputDialog:findCallback(keyboard_hidden_state, input_dialog, find_first) function InputDialog:findCallback(input_dialog, find_first)
self.search_value = input_dialog:getInputText() self.search_value = input_dialog:getInputText()
if self.search_value == "" then return end if self.search_value == "" then return end
UIManager:close(input_dialog) UIManager:close(input_dialog)
self.keyboard_hidden = keyboard_hidden_state
self:toggleKeyboard() self:toggleKeyboard()
local start_pos = find_first and 1 or self._charpos + 1 local start_pos = find_first and 1 or self._charpos + 1
local char_pos = util.stringSearch(self.input, self.search_value, self.case_sensitive, start_pos) local char_pos = util.stringSearch(self.input, self.search_value, self.case_sensitive, start_pos)

@ -32,9 +32,8 @@ local InputText = InputContainer:extend{
focused = true, focused = true,
parent = nil, -- parent dialog that will be set dirty parent = nil, -- parent dialog that will be set dirty
edit_callback = nil, -- called with true when text modified, false on init or text re-set edit_callback = nil, -- called with true when text modified, false on init or text re-set
scroll_callback = nil, -- called with (low, high) when view is scrolled (cf ScrollTextWidget) scroll_callback = nil, -- called with (low, high) when view is scrolled (c.f., ScrollTextWidget)
scroll_by_pan = false, -- allow scrolling by lines with Pan (needs scroll=true) scroll_by_pan = false, -- allow scrolling by lines with Pan (needs scroll=true)
manage_keyboard_state = true, -- manage keyboard hidden/shown state
width = nil, width = nil,
height = nil, -- when nil, will be set to original text height (possibly height = nil, -- when nil, will be set to original text height (possibly
@ -54,7 +53,10 @@ local InputText = InputContainer:extend{
auto_para_direction = false, auto_para_direction = false,
alignment_strict = false, alignment_strict = false,
readonly = nil, -- will not support a Keyboard widget if true
-- for internal use -- for internal use
keyboard = nil, -- Keyboard widget (either VirtualKeyboard or PhysicalKeyboard)
text_widget = nil, -- Text Widget for cursor movement, possibly a ScrollTextWidget text_widget = nil, -- Text Widget for cursor movement, possibly a ScrollTextWidget
charlist = nil, -- table of individual chars from input string charlist = nil, -- table of individual chars from input string
charpos = nil, -- position of the cursor, where a new char would be inserted charpos = nil, -- position of the cursor, where a new char would be inserted
@ -65,7 +67,6 @@ local InputText = InputContainer:extend{
for_measurement_only = nil, -- When the widget is a one-off used to compute text height for_measurement_only = nil, -- When the widget is a one-off used to compute text height
do_select = false, -- to start text selection do_select = false, -- to start text selection
selection_start_pos = nil, -- selection start position selection_start_pos = nil, -- selection start position
is_keyboard_hidden = true, -- to be able to show the keyboard again when it was hidden. (On init, it's the caller's responsibility to call onShowKeyboard, as far as we're concerned, it's hidden)
} }
-- These may be (internally) overloaded as needed, depending on Device capabilities. -- These may be (internally) overloaded as needed, depending on Device capabilities.
@ -73,6 +74,11 @@ function InputText:initEventListener() end
function InputText:onFocus() end function InputText:onFocus() end
function InputText:onUnfocus() end function InputText:onUnfocus() end
-- Resync our position state with our text widget's actual state
function InputText:resyncPos()
self.charpos, self.top_line_num = self.text_widget:getCharPos()
end
local function initTouchEvents() local function initTouchEvents()
if Device:isTouchDevice() then if Device:isTouchDevice() then
function InputText:initEventListener() function InputText:initEventListener()
@ -133,8 +139,8 @@ local function initTouchEvents()
if self.parent.onSwitchFocus then if self.parent.onSwitchFocus then
self.parent:onSwitchFocus(self) self.parent:onSwitchFocus(self)
else else
if self.is_keyboard_hidden and self.manage_keyboard_state then if self.keyboard then
self:onShowKeyboard() self.keyboard:showKeyboard()
end end
end end
-- zh keyboard with candidates shown here has _frame_textwidget.dimen = nil. -- zh keyboard with candidates shown here has _frame_textwidget.dimen = nil.
@ -144,7 +150,7 @@ local function initTouchEvents()
local x = ges.pos.x - self._frame_textwidget.dimen.x - textwidget_offset local x = ges.pos.x - self._frame_textwidget.dimen.x - textwidget_offset
local y = ges.pos.y - self._frame_textwidget.dimen.y - 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.text_widget:moveCursorToXY(x, y, true) -- restrict_to_view=true
self.charpos, self.top_line_num = self.text_widget:getCharPos() self:resyncPos()
end end
return true return true
end end
@ -512,7 +518,7 @@ function InputText:initTextBox(text, char_added)
} }
end end
-- Get back possibly modified charpos and virtual_line_num -- Get back possibly modified charpos and virtual_line_num
self.charpos, self.top_line_num = self.text_widget:getCharPos() self:resyncPos()
self._frame_textwidget = FrameContainer:new{ self._frame_textwidget = FrameContainer:new{
bordersize = self.bordersize, bordersize = self.bordersize,
@ -676,22 +682,27 @@ dbg:guard(InputText, "onTextInput",
end) end)
function InputText:onShowKeyboard(ignore_first_hold_release) function InputText:onShowKeyboard(ignore_first_hold_release)
Device:startTextInput() if self.keyboard then
self.keyboard:showKeyboard(ignore_first_hold_release)
if self.is_keyboard_hidden or not self.manage_keyboard_state then
self.keyboard.ignore_first_hold_release = ignore_first_hold_release
UIManager:show(self.keyboard)
self.is_keyboard_hidden = false
end end
return true return true
end end
function InputText:onCloseKeyboard() function InputText:onCloseKeyboard()
Device:stopTextInput() if self.keyboard then
self.keyboard:hideKeyboard()
end
end
function InputText:isKeyboardVisible()
if self.keyboard then
return self.keyboard:isVisible()
end
end
if not self.is_keyboard_hidden or not self.manage_keyboard_state then function InputText:lockKeyboard(toggle)
UIManager:close(self.keyboard) if self.keyboard then
self.is_keyboard_hidden = true return self.keyboard:lockVisibility(toggle)
end end
end end
@ -873,71 +884,71 @@ end
function InputText:leftChar() function InputText:leftChar()
if self.charpos == 1 then return end if self.charpos == 1 then return end
self.text_widget:moveCursorLeft() self.text_widget:moveCursorLeft()
self.charpos, self.top_line_num = self.text_widget:getCharPos() self:resyncPos()
end end
function InputText:rightChar() function InputText:rightChar()
if self.charpos > #self.charlist then return end if self.charpos > #self.charlist then return end
self.text_widget:moveCursorRight() self.text_widget:moveCursorRight()
self.charpos, self.top_line_num = self.text_widget:getCharPos() self:resyncPos()
end end
function InputText:goToStartOfLine() function InputText:goToStartOfLine()
local new_pos = select(1, self:getStringPos({"\n", "\r"}, {"\n", "\r"})) local new_pos = select(1, self:getStringPos({"\n", "\r"}, {"\n", "\r"}))
self.text_widget:moveCursorToCharPos(new_pos) self.text_widget:moveCursorToCharPos(new_pos)
self.charpos, self.top_line_num = self.text_widget:getCharPos() self:resyncPos()
end end
function InputText:goToEndOfLine() function InputText:goToEndOfLine()
local new_pos = select(2, self:getStringPos({"\n", "\r"}, {"\n", "\r"})) + 1 local new_pos = select(2, self:getStringPos({"\n", "\r"}, {"\n", "\r"})) + 1
self.text_widget:moveCursorToCharPos(new_pos) self.text_widget:moveCursorToCharPos(new_pos)
self.charpos, self.top_line_num = self.text_widget:getCharPos() self:resyncPos()
end end
function InputText:goToHome() function InputText:goToHome()
self.text_widget:moveCursorHome() self.text_widget:moveCursorHome()
self.charpos, self.top_line_num = self.text_widget:getCharPos() self:resyncPos()
end end
function InputText:goToEnd() function InputText:goToEnd()
self.text_widget:moveCursorEnd() self.text_widget:moveCursorEnd()
self.charpos, self.top_line_num = self.text_widget:getCharPos() self:resyncPos()
end end
function InputText:moveCursorToCharPos(char_pos) function InputText:moveCursorToCharPos(char_pos)
self.text_widget:moveCursorToCharPos(char_pos) self.text_widget:moveCursorToCharPos(char_pos)
self.charpos, self.top_line_num = self.text_widget:getCharPos() self:resyncPos()
end end
function InputText:upLine() function InputText:upLine()
self.text_widget:moveCursorUp() self.text_widget:moveCursorUp()
self.charpos, self.top_line_num = self.text_widget:getCharPos() self:resyncPos()
end end
function InputText:downLine() function InputText:downLine()
if #self.charlist == 0 then return end -- Avoid cursor moving within a hint. if #self.charlist == 0 then return end -- Avoid cursor moving within a hint.
self.text_widget:moveCursorDown() self.text_widget:moveCursorDown()
self.charpos, self.top_line_num = self.text_widget:getCharPos() self:resyncPos()
end end
function InputText:scrollDown() function InputText:scrollDown()
self.text_widget:scrollDown() self.text_widget:scrollDown()
self.charpos, self.top_line_num = self.text_widget:getCharPos() self:resyncPos()
end end
function InputText:scrollUp() function InputText:scrollUp()
self.text_widget:scrollUp() self.text_widget:scrollUp()
self.charpos, self.top_line_num = self.text_widget:getCharPos() self:resyncPos()
end end
function InputText:scrollToTop() function InputText:scrollToTop()
self.text_widget:scrollToTop() self.text_widget:scrollToTop()
self.charpos, self.top_line_num = self.text_widget:getCharPos() self:resyncPos()
end end
function InputText:scrollToBottom() function InputText:scrollToBottom()
self.text_widget:scrollToBottom() self.text_widget:scrollToBottom()
self.charpos, self.top_line_num = self.text_widget:getCharPos() self:resyncPos()
end end
function InputText:clear() function InputText:clear()

@ -218,7 +218,9 @@ end
function MultiInputDialog:onSwitchFocus(inputbox) function MultiInputDialog:onSwitchFocus(inputbox)
-- unfocus current inputbox -- unfocus current inputbox
self._input_widget:unfocus() self._input_widget:unfocus()
self._input_widget:onCloseKeyboard() -- and close its existing keyboard (via InputDialog's thin wrapper around _input_widget's own method)
self:onCloseKeyboard()
UIManager:setDirty(nil, function() UIManager:setDirty(nil, function()
return "ui", self.dialog_frame.dimen return "ui", self.dialog_frame.dimen
end) end)
@ -226,7 +228,9 @@ function MultiInputDialog:onSwitchFocus(inputbox)
-- focus new inputbox -- focus new inputbox
self._input_widget = inputbox self._input_widget = inputbox
self._input_widget:focus() self._input_widget:focus()
self._input_widget:onShowKeyboard()
-- Make sure we have a (new) visible keyboard
self:onShowKeyboard()
end end
return MultiInputDialog return MultiInputDialog

@ -169,4 +169,11 @@ function PhysicalKeyboard:setupNumericMappingUI()
self.dimen = keyboard_frame:getSize() self.dimen = keyboard_frame:getSize()
end end
-- Match VirtualKeyboard's API to ease caller's life
function PhysicalKeyboard:lockVisibility() end
function PhysicalKeyboard:setVisibility() end
function PhysicalKeyboard:isVisible() return true end
function PhysicalKeyboard:showKeyboard() end
function PhysicalKeyboard:hideKeyboard() end
return PhysicalKeyboard return PhysicalKeyboard

@ -755,6 +755,8 @@ end
local VirtualKeyboard = FocusManager:extend{ local VirtualKeyboard = FocusManager:extend{
name = "VirtualKeyboard", name = "VirtualKeyboard",
visible = nil,
lock_visibility = false,
covers_footer = true, covers_footer = true,
modal = true, modal = true,
disable_double_tap = true, disable_double_tap = true,
@ -929,11 +931,53 @@ end
function VirtualKeyboard:onShow() function VirtualKeyboard:onShow()
self:_refresh(true) self:_refresh(true)
self.visible = true
Device:startTextInput()
return true return true
end end
function VirtualKeyboard:onCloseWidget() function VirtualKeyboard:onCloseWidget()
self:_refresh(true) self:_refresh(true)
self.visible = false
-- NOTE: This effectively stops SDL text input when a keyboard is hidden (... but navigational stuff still works).
-- If you instead wanted it to be enabled as long as an input dialog is displayed, regardless of VK's state,
-- this could be moved to InputDialog's onShow/onCloseWidget handlers (but, it would allow input on unfocused fields).
-- NOTE: But something more complex, possibly based on an in-class ref count would have to be implemented in order to be able to deal
-- with multiple InputDialogs being shown and closed in asymmetric fashion... Ugh.
Device:stopTextInput()
end
function VirtualKeyboard:lockVisibility(toggle)
self.lock_visibility = toggle
end
function VirtualKeyboard:setVisibility(toggle)
if self.lock_visibility then
return
end
if toggle then
UIManager:show(self)
else
self:onClose()
end
end
function VirtualKeyboard:isVisible()
return self.visible
end
function VirtualKeyboard:showKeyboard(ignore_first_hold_release)
if not self:isVisible() then
self.ignore_first_hold_release = ignore_first_hold_release
self:setVisibility(true)
end
end
function VirtualKeyboard:hideKeyboard()
if self:isVisible() then
self:setVisibility(false)
end
end end
function VirtualKeyboard:initLayer(layer) function VirtualKeyboard:initLayer(layer)

@ -545,13 +545,13 @@ function TextEditor:editFile(file_path, readonly)
cursor_at_end = false, cursor_at_end = false,
readonly = readonly, readonly = readonly,
add_nav_bar = true, add_nav_bar = true,
keyboard_hidden = not self.show_keyboard_on_start, keyboard_visible = self.show_keyboard_on_start, -- InputDialog will enforce false if readonly
scroll_by_pan = true, scroll_by_pan = true,
buttons = {buttons_first_row}, buttons = {buttons_first_row},
-- Set/save view and cursor position callback -- Store/retrieve view and cursor position callback
view_pos_callback = function(top_line_num, charpos) view_pos_callback = function(top_line_num, charpos)
-- This same callback is called with no argument to get initial position, -- This same callback is called with no arguments on init to retrieve the stored initial position,
-- and with arguments to give back final position when closed. -- and with arguments to store the final position on close.
if top_line_num and charpos then if top_line_num and charpos then
self.last_view_pos[file_path] = {top_line_num, charpos} self.last_view_pos[file_path] = {top_line_num, charpos}
else else
@ -572,7 +572,7 @@ function TextEditor:editFile(file_path, readonly)
end, end,
-- File saving callback -- File saving callback
save_callback = function(content, closing) -- Will add Save/Close buttons save_callback = function(content, closing) -- Will add Save/Close buttons
if self.readonly then if readonly then
-- We shouldn't be called if read-only, but just in case -- We shouldn't be called if read-only, but just in case
return false, _("File is read only") return false, _("File is read only")
end end
@ -641,8 +641,10 @@ Do you want to keep this file as empty, or do you prefer to delete it?
} }
UIManager:show(input) UIManager:show(input)
input:onShowKeyboard() if self.show_keyboard_on_start and not readonly then
-- Note about self.readonly: input:onShowKeyboard()
end
-- Note about readonly:
-- We might have liked to still show keyboard even if readonly, just -- We might have liked to still show keyboard even if readonly, just
-- to use the arrow keys for line by line scrolling with cursor. -- to use the arrow keys for line by line scrolling with cursor.
-- But it's easier to just let InputDialog and InputText do their -- But it's easier to just let InputDialog and InputText do their

Loading…
Cancel
Save