|
|
|
local Blitbuffer = require("ffi/blitbuffer")
|
|
|
|
local BottomContainer = require("ui/widget/container/bottomcontainer")
|
|
|
|
local CenterContainer = require("ui/widget/container/centercontainer")
|
|
|
|
local Device = require("device")
|
|
|
|
local Event = require("ui/event")
|
|
|
|
local FFIUtil = require("ffi/util")
|
|
|
|
local FocusManager = require("ui/widget/focusmanager")
|
|
|
|
local Font = require("ui/font")
|
|
|
|
local FrameContainer = require("ui/widget/container/framecontainer")
|
|
|
|
local Geom = require("ui/geometry")
|
|
|
|
local GestureRange = require("ui/gesturerange")
|
|
|
|
local HorizontalGroup = require("ui/widget/horizontalgroup")
|
|
|
|
local HorizontalSpan = require("ui/widget/horizontalspan")
|
|
|
|
local ImageWidget = require("ui/widget/imagewidget")
|
|
|
|
local InputContainer = require("ui/widget/container/inputcontainer")
|
|
|
|
local KeyboardLayoutDialog = require("ui/widget/keyboardlayoutdialog")
|
|
|
|
local Size = require("ui/size")
|
|
|
|
local TextWidget = require("ui/widget/textwidget")
|
The great Input/GestureDetector/TimeVal spring cleanup (a.k.a., a saner main loop) (#7415)
* ReaderDictionary: Port delay computations to TimeVal
* ReaderHighlight: Port delay computations to TimeVal
* ReaderView: Port delay computations to TimeVal
* Android: Reset gesture detection state on APP_CMD_TERM_WINDOW.
This prevents potentially being stuck in bogus gesture states when switching apps.
* GestureDetector:
* Port delay computations to TimeVal
* Fixed delay computations to handle time warps (large and negative deltas).
* Simplified timed callback handling to invalidate timers much earlier, preventing accumulating useless timers that no longer have any chance of ever detecting a gesture.
* Fixed state clearing to handle the actual effective slots, instead of hard-coding slot 0 & slot 1.
* Simplified timed callback handling in general, and added support for a timerfd backend for better performance and accuracy.
* The improved timed callback handling allows us to detect and honor (as much as possible) the three possible clock sources usable by Linux evdev events.
The only case where synthetic timestamps are used (and that only to handle timed callbacks) is limited to non-timerfd platforms where input events use
a clock source that is *NOT* MONOTONIC.
AFAICT, that's pretty much... PocketBook, and that's it?
* Input:
* Use the <linux/input.h> FFI module instead of re-declaring every constant
* Fixed (verbose) debug logging of input events to actually translate said constants properly.
* Completely reset gesture detection state on suspend. This should prevent bogus gesture detection on resume.
* Refactored the waitEvent loop to make it easier to comprehend (hopefully) and much more efficient.
Of specific note, it no longer does a crazy select spam every 100µs, instead computing and relying on sane timeouts,
as afforded by switching the UI event/input loop to the MONOTONIC time base, and the refactored timed callbacks in GestureDetector.
* reMarkable: Stopped enforcing synthetic timestamps on input events, as it should no longer be necessary.
* TimeVal:
* Refactored and simplified, especially as far as metamethods are concerned (based on <bsd/sys/time.h>).
* Added a host of new methods to query the various POSIX clock sources, and made :now default to MONOTONIC.
* Removed the debug guard in __sub, as time going backwards can be a perfectly normal occurrence.
* New methods:
* Clock sources: :realtime, :monotonic, :monotonic_coarse, :realtime_coarse, :boottime
* Utility: :tonumber, :tousecs, :tomsecs, :fromnumber, :isPositive, :isZero
* UIManager:
* Ported event loop & scheduling to TimeVal, and switched to the MONOTONIC time base.
This ensures reliable and consistent scheduling, as time is ensured never to go backwards.
* Added a :getTime() method, that returns a cached TimeVal:now(), updated at the top of every UI frame.
It's used throughout the codebase to cadge a syscall in circumstances where we are guaranteed that a syscall would return a mostly identical value,
because very few time has passed.
The only code left that does live syscalls does it because it's actually necessary for accuracy,
and the only code left that does that in a REALTIME time base is code that *actually* deals with calendar time (e.g., Statistics).
* DictQuickLookup: Port delay computations to TimeVal
* FootNoteWidget: Port delay computations to TimeVal
* HTMLBoxWidget: Port delay computations to TimeVal
* Notification: Port delay computations to TimeVal
* TextBoxWidget: Port delay computations to TimeVal
* AutoSuspend: Port to TimeVal
* AutoTurn:
* Fix it so that settings are actually honored.
* Port to TimeVal
* BackgroundRunner: Port to TimeVal
* Calibre: Port benchmarking code to TimeVal
* BookInfoManager: Removed unnecessary yield in the metadata extraction subprocess now that subprocesses get scheduled properly.
* All in all, these changes reduced the CPU cost of a single tap by a factor of ten (!), and got rid of an insane amount of weird poll/wakeup cycles that must have been hell on CPU schedulers and batteries..
4 years ago
|
|
|
local TimeVal = require("ui/timeval")
|
|
|
|
local UIManager = require("ui/uimanager")
|
|
|
|
local VerticalGroup = require("ui/widget/verticalgroup")
|
|
|
|
local VerticalSpan = require("ui/widget/verticalspan")
|
|
|
|
local WidgetContainer = require("ui/widget/container/widgetcontainer")
|
|
|
|
local logger = require("logger")
|
|
|
|
local util = require("util")
|
|
|
|
local Screen = Device.screen
|
|
|
|
|
|
|
|
local VirtualKeyPopup
|
|
|
|
|
|
|
|
local VirtualKey = InputContainer:new{
|
|
|
|
key = nil,
|
|
|
|
icon = nil,
|
|
|
|
label = nil,
|
|
|
|
bold = nil,
|
|
|
|
|
|
|
|
keyboard = nil,
|
|
|
|
callback = nil,
|
|
|
|
-- This is to inhibit the key's own refresh (useful to avoid conflicts on Layer changing keys)
|
|
|
|
skiptap = nil,
|
|
|
|
skiphold = nil,
|
|
|
|
|
|
|
|
width = nil,
|
|
|
|
height = math.max(Screen:getWidth(), Screen:getHeight())*0.33,
|
|
|
|
bordersize = Size.border.thin,
|
|
|
|
focused_bordersize = Size.border.default * 5,
|
|
|
|
radius = 0,
|
|
|
|
face = Font:getFace("infont"),
|
|
|
|
}
|
|
|
|
|
|
|
|
function VirtualKey:init()
|
|
|
|
if self.keyboard.symbolmode_keys[self.label] ~= nil then
|
|
|
|
self.callback = function () self.keyboard:setLayer("Sym") end
|
|
|
|
self.skiptap = true
|
|
|
|
elseif self.keyboard.shiftmode_keys[self.label] ~= nil then
|
|
|
|
self.callback = function () self.keyboard:setLayer("Shift") end
|
|
|
|
self.skiptap = true
|
|
|
|
elseif self.keyboard.utf8mode_keys[self.label] ~= nil then
|
|
|
|
self.key_chars = self:genkeyboardLayoutKeyChars()
|
|
|
|
self.callback = function ()
|
|
|
|
local current = G_reader_settings:readSetting("keyboard_layout")
|
|
|
|
local keyboard_layouts = G_reader_settings:readSetting("keyboard_layouts") or {}
|
|
|
|
local enabled = false
|
|
|
|
local next_layout = nil
|
|
|
|
for k, v in FFIUtil.orderedPairs(keyboard_layouts) do
|
|
|
|
if enabled and v == true then
|
|
|
|
next_layout = k
|
|
|
|
break
|
|
|
|
end
|
|
|
|
if k == current then
|
|
|
|
enabled = true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
if not next_layout then
|
|
|
|
for k, v in FFIUtil.orderedPairs(keyboard_layouts) do
|
|
|
|
if enabled and v == true then
|
|
|
|
next_layout = k
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
if next_layout then
|
|
|
|
self.keyboard:setKeyboardLayout(next_layout)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
self.hold_callback = function()
|
|
|
|
if util.tableSize(self.key_chars) > 3 then
|
|
|
|
self.popup = VirtualKeyPopup:new{
|
|
|
|
parent_key = self,
|
|
|
|
}
|
|
|
|
else
|
|
|
|
self.keyboard_layout_dialog = KeyboardLayoutDialog:new{
|
|
|
|
parent = self,
|
|
|
|
}
|
|
|
|
UIManager:show(self.keyboard_layout_dialog)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
self.swipe_callback = function(ges)
|
|
|
|
local key_function = self.key_chars[ges.direction.."_func"]
|
|
|
|
if key_function then
|
|
|
|
key_function()
|
|
|
|
end
|
|
|
|
end
|
|
|
|
self.skiptap = true
|
|
|
|
elseif self.keyboard.umlautmode_keys[self.label] ~= nil then
|
|
|
|
self.callback = function () self.keyboard:setLayer("Äéß") end
|
|
|
|
self.skiptap = true
|
|
|
|
elseif self.label == "" then
|
|
|
|
self.callback = function () self.keyboard:delChar() end
|
|
|
|
self.hold_callback = function ()
|
|
|
|
self.ignore_key_release = true -- don't have delChar called on release
|
|
|
|
self.keyboard:delToStartOfLine()
|
|
|
|
end
|
|
|
|
--self.skiphold = true
|
|
|
|
elseif self.label =="←" then
|
|
|
|
self.callback = function() self.keyboard:leftChar() end
|
|
|
|
elseif self.label == "→" then
|
|
|
|
self.callback = function() self.keyboard:rightChar() end
|
|
|
|
elseif self.label == "↑" then
|
|
|
|
self.callback = function() self.keyboard:upLine() end
|
|
|
|
elseif self.label == "↓" then
|
|
|
|
self.callback = function() self.keyboard:downLine() end
|
|
|
|
else
|
|
|
|
self.callback = function () self.keyboard:addChar(self.key) end
|
|
|
|
self.hold_callback = function()
|
|
|
|
if not self.key_chars then return end
|
|
|
|
|
|
|
|
VirtualKeyPopup:new{
|
|
|
|
parent_key = self,
|
|
|
|
}
|
|
|
|
end
|
|
|
|
self.swipe_callback = function(ges)
|
|
|
|
local key_string = self.key_chars[ges.direction]
|
|
|
|
local key_function = self.key_chars[ges.direction.."_func"]
|
|
|
|
|
|
|
|
if not key_function and key_string then
|
|
|
|
if type(key_string) == "table" and key_string.key then
|
|
|
|
key_string = key_string.key
|
|
|
|
end
|
|
|
|
self.keyboard:addChar(key_string)
|
|
|
|
elseif key_function then
|
|
|
|
key_function()
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local label_widget
|
|
|
|
if self.icon then
|
|
|
|
-- Scale icon to fit other characters height
|
|
|
|
-- (use *1.5 as our icons have a bit of white padding)
|
|
|
|
local icon_height = math.ceil(self.face.size * 1.5)
|
|
|
|
label_widget = ImageWidget:new{
|
|
|
|
file = self.icon,
|
|
|
|
scale_factor = 0, -- keep icon aspect ratio
|
|
|
|
height = icon_height,
|
|
|
|
width = self.width - 2*self.bordersize,
|
|
|
|
}
|
|
|
|
else
|
|
|
|
label_widget = TextWidget:new{
|
|
|
|
text = self.label,
|
|
|
|
face = self.face,
|
|
|
|
bold = self.bold or false,
|
|
|
|
}
|
|
|
|
end
|
|
|
|
self[1] = FrameContainer:new{
|
|
|
|
margin = 0,
|
|
|
|
bordersize = self.bordersize,
|
|
|
|
background = Blitbuffer.COLOR_WHITE,
|
|
|
|
radius = 0,
|
|
|
|
padding = 0,
|
|
|
|
allow_mirroring = false,
|
|
|
|
CenterContainer:new{
|
|
|
|
dimen = Geom:new{
|
|
|
|
w = self.width - 2*self.bordersize,
|
|
|
|
h = self.height - 2*self.bordersize,
|
|
|
|
},
|
|
|
|
label_widget,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
self.dimen = Geom:new{
|
|
|
|
w = self.width,
|
|
|
|
h = self.height,
|
|
|
|
}
|
|
|
|
--self.dimen = self[1]:getSize()
|
|
|
|
if Device:isTouchDevice() then
|
|
|
|
self.ges_events = {
|
|
|
|
TapSelect = {
|
|
|
|
GestureRange:new{
|
|
|
|
ges = "tap",
|
|
|
|
range = self.dimen,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
HoldSelect = {
|
|
|
|
GestureRange:new{
|
|
|
|
ges = "hold",
|
|
|
|
range = self.dimen,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
HoldReleaseKey = {
|
|
|
|
GestureRange:new{
|
|
|
|
ges = "hold_release",
|
|
|
|
range = self.dimen,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
PanReleaseKey = {
|
|
|
|
GestureRange:new{
|
|
|
|
ges = "pan_release",
|
|
|
|
range = self.dimen,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
SwipeKey = {
|
|
|
|
GestureRange:new{
|
|
|
|
ges = "swipe",
|
|
|
|
range = self.dimen,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
end
|
|
|
|
if (self.keyboard.shiftmode_keys[self.label] ~= nil and self.keyboard.shiftmode) or
|
|
|
|
(self.keyboard.umlautmode_keys[self.label] ~= nil and self.keyboard.umlautmode) then
|
|
|
|
self[1].background = Blitbuffer.COLOR_LIGHT_GRAY
|
|
|
|
end
|
|
|
|
self.flash_keyboard = G_reader_settings:nilOrTrue("flash_keyboard")
|
|
|
|
end
|
|
|
|
|
|
|
|
function VirtualKey:genkeyboardLayoutKeyChars()
|
|
|
|
local positions = {
|
|
|
|
"northeast",
|
|
|
|
"north",
|
|
|
|
"northwest",
|
|
|
|
"west",
|
|
|
|
}
|
|
|
|
local keyboard_layouts = G_reader_settings:readSetting("keyboard_layouts") or {}
|
|
|
|
local key_chars = {
|
|
|
|
{ label = "🌐",
|
|
|
|
},
|
|
|
|
east = { label = "🌐", },
|
|
|
|
east_func = function ()
|
|
|
|
self.keyboard_layout_dialog = KeyboardLayoutDialog:new{
|
|
|
|
parent = self,
|
|
|
|
}
|
|
|
|
UIManager:show(self.keyboard_layout_dialog)
|
|
|
|
end,
|
|
|
|
}
|
|
|
|
local index = 1
|
|
|
|
for k, v in FFIUtil.orderedPairs(keyboard_layouts) do
|
|
|
|
if v == true then
|
|
|
|
key_chars[positions[index]] = string.sub(k, 1, 2)
|
|
|
|
key_chars[positions[index] .. "_func"] = function()
|
|
|
|
UIManager:tickAfterNext(function() UIManager:close(self.popup) end)
|
|
|
|
self.keyboard:setKeyboardLayout(k)
|
|
|
|
end
|
|
|
|
if index >= 4 then break end
|
|
|
|
index = index + 1
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return key_chars
|
|
|
|
end
|
|
|
|
|
|
|
|
function VirtualKey:update_keyboard(want_flash, want_fast)
|
|
|
|
-- NOTE: We mainly use "fast" when inverted & "ui" when not, with a cherry on top:
|
|
|
|
-- we flash the *full* keyboard instead when we release a hold.
|
|
|
|
if want_flash then
|
|
|
|
UIManager:setDirty(self.keyboard, function()
|
|
|
|
return "flashui", self.keyboard[1][1].dimen
|
|
|
|
end)
|
|
|
|
else
|
|
|
|
local refresh_type = "ui"
|
|
|
|
if want_fast then
|
|
|
|
refresh_type = "fast"
|
|
|
|
end
|
Enable HW dithering in a few key places (#4541)
* Enable HW dithering on supported devices (Clara HD, Forma; Oasis 2, PW4)
* FileManager and co. (where appropriate, i.e., when covers are shown)
* Book Status
* Reader, where appropriate:
* CRe: on pages whith image content (for over 7.5% of the screen area, should hopefully leave stuff like bullet points or small scene breaks alone).
* Other engines: on user-request (in the gear tab of the bottom menu), via the new "Dithering" knob (will only appear on supported devices).
* ScreenSaver
* ImageViewer
* Minimize repaints when flash_ui is enabled (by, almost everywhere, only repainting the flashing element, and not the toplevel window which hosts it).
(The first pass of this involved fixing a few Button instances whose show_parent was wrong, in particular, chevrons in the FM & TopMenu).
* Hunted down a few redundant repaints (unneeded setDirty("all") calls),
either by switching the widget to nil when only a refresh was needed, and not a repaint,
or by passing the appropritate widget to setDirty.
(Note to self: Enable *verbose* debugging to catch broken setDirty calls via its post guard).
There were also a few instances of 'em right behind a widget close.
* Don't repaint the underlying widget when initially showing TopMenu & ConfigDialog.
We unfortunately do need to do it when switching tabs, because of their variable heights.
* On Kobo, disabled the extra and completely useless full refresh before suspend/reboot/poweroff, as well as on resume. No more double refreshes!
* Fix another debug guard in Kobo sysfs_light
* Switch ImageWidget & ImageViewer mostly to "ui" updates, which will be better suited to image content pretty much everywhere, REAGL or not.
PS: (Almost :100: commits! :D)
6 years ago
|
|
|
-- Only repaint the key itself, not the full board...
|
|
|
|
UIManager:widgetRepaint(self[1], self[1].dimen.x, self[1].dimen.y)
|
|
|
|
UIManager:setDirty(nil, function()
|
|
|
|
logger.dbg("update key region", self[1].dimen)
|
|
|
|
return refresh_type, self[1].dimen
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function VirtualKey:onFocus()
|
|
|
|
self[1].inner_bordersize = self.focused_bordersize
|
|
|
|
end
|
|
|
|
|
|
|
|
function VirtualKey:onUnfocus()
|
|
|
|
self[1].inner_bordersize = 0
|
|
|
|
end
|
|
|
|
|
|
|
|
function VirtualKey:onTapSelect(skip_flash)
|
|
|
|
Device:performHapticFeedback("KEYBOARD_TAP")
|
|
|
|
-- just in case it's not flipped to false on hold release where it's supposed to
|
|
|
|
self.keyboard.ignore_first_hold_release = false
|
|
|
|
if self.flash_keyboard and not skip_flash and not self.skiptap then
|
|
|
|
self[1].inner_bordersize = self.focused_bordersize
|
|
|
|
self:update_keyboard(false, true)
|
|
|
|
if self.callback then
|
|
|
|
self.callback()
|
|
|
|
end
|
|
|
|
UIManager:tickAfterNext(function() self:invert(false) end)
|
|
|
|
else
|
|
|
|
if self.callback then
|
|
|
|
self.callback()
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
function VirtualKey:onHoldSelect()
|
|
|
|
Device:performHapticFeedback("LONG_PRESS")
|
|
|
|
if self.flash_keyboard and not self.skiphold then
|
|
|
|
self[1].inner_bordersize = self.focused_bordersize
|
|
|
|
self:update_keyboard(false, true)
|
|
|
|
-- Don't refresh the key region if we're going to show a popup on top of it ;).
|
|
|
|
if self.hold_callback then
|
|
|
|
self[1].inner_bordersize = 0
|
|
|
|
self.hold_callback()
|
|
|
|
else
|
|
|
|
UIManager:tickAfterNext(function() self:invert(false, true) end)
|
|
|
|
end
|
|
|
|
else
|
|
|
|
if self.hold_callback then
|
|
|
|
self.hold_callback()
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
function VirtualKey:onSwipeKey(arg, ges)
|
|
|
|
Device:performHapticFeedback("KEYBOARD_TAP")
|
|
|
|
if self.flash_keyboard and not self.skipswipe then
|
|
|
|
self[1].inner_bordersize = self.focused_bordersize
|
|
|
|
self:update_keyboard(false, true)
|
|
|
|
if self.swipe_callback then
|
|
|
|
self.swipe_callback(ges)
|
|
|
|
end
|
|
|
|
UIManager:tickAfterNext(function() self:invert(false, false) end)
|
|
|
|
else
|
|
|
|
if self.swipe_callback then
|
|
|
|
self.swipe_callback(ges)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
function VirtualKey:onHoldReleaseKey()
|
|
|
|
if self.ignore_key_release then
|
|
|
|
self.ignore_key_release = nil
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
Device:performHapticFeedback("LONG_PRESS")
|
|
|
|
if self.keyboard.ignore_first_hold_release then
|
|
|
|
self.keyboard.ignore_first_hold_release = false
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
self:onTapSelect()
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
function VirtualKey:onPanReleaseKey()
|
|
|
|
if self.ignore_key_release then
|
|
|
|
self.ignore_key_release = nil
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
Device:performHapticFeedback("LONG_PRESS")
|
|
|
|
if self.keyboard.ignore_first_hold_release then
|
|
|
|
self.keyboard.ignore_first_hold_release = false
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
self:onTapSelect()
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
function VirtualKey:invert(invert, hold)
|
|
|
|
if invert then
|
|
|
|
self[1].inner_bordersize = self.focused_bordersize
|
|
|
|
else
|
|
|
|
self[1].inner_bordersize = 0
|
|
|
|
end
|
|
|
|
self:update_keyboard(hold, false)
|
|
|
|
end
|
|
|
|
|
|
|
|
VirtualKeyPopup = FocusManager:new{
|
|
|
|
modal = true,
|
|
|
|
disable_double_tap = true,
|
|
|
|
inputbox = nil,
|
|
|
|
layout = {},
|
|
|
|
}
|
|
|
|
|
|
|
|
function VirtualKeyPopup:onTapClose(arg, ges)
|
|
|
|
if ges.pos:notIntersectWith(self[1][1].dimen) then
|
|
|
|
UIManager:close(self)
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
|
|
|
function VirtualKeyPopup:onClose()
|
|
|
|
UIManager:close(self)
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
function VirtualKeyPopup:onCloseWidget()
|
|
|
|
self:free()
|
|
|
|
UIManager:setDirty(nil, function()
|
|
|
|
return "ui", self[1][1].dimen
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
|
|
|
|
function VirtualKeyPopup:onPressKey()
|
|
|
|
self:getFocusItem():handleEvent(Event:new("TapSelect"))
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
function VirtualKeyPopup:init()
|
|
|
|
local parent_key = self.parent_key
|
|
|
|
local key_chars = parent_key.key_chars
|
|
|
|
local key_char_orig = key_chars[1]
|
|
|
|
local key_char_orig_func = parent_key.callback
|
|
|
|
|
|
|
|
local rows = {
|
|
|
|
extra_key_chars = {
|
|
|
|
key_chars[2],
|
|
|
|
key_chars[3],
|
|
|
|
key_chars[4],
|
|
|
|
-- _func equivalent for unnamed extra keys
|
|
|
|
key_chars[5],
|
|
|
|
key_chars[6],
|
|
|
|
key_chars[7],
|
|
|
|
},
|
|
|
|
top_key_chars = {
|
|
|
|
key_chars.northwest,
|
|
|
|
key_chars.north,
|
|
|
|
key_chars.northeast,
|
|
|
|
key_chars.northwest_func,
|
|
|
|
key_chars.north_func,
|
|
|
|
key_chars.northeast_func,
|
|
|
|
},
|
|
|
|
middle_key_chars = {
|
|
|
|
key_chars.west,
|
|
|
|
key_char_orig,
|
|
|
|
key_chars.east,
|
|
|
|
key_chars.west_func,
|
|
|
|
key_char_orig_func,
|
|
|
|
key_chars.east_func,
|
|
|
|
},
|
|
|
|
bottom_key_chars = {
|
|
|
|
key_chars.southwest,
|
|
|
|
key_chars.south,
|
|
|
|
key_chars.southeast,
|
|
|
|
key_chars.southwest_func,
|
|
|
|
key_chars.south_func,
|
|
|
|
key_chars.southeast_func,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
if util.tableSize(rows.extra_key_chars) == 0 then rows.extra_key_chars = nil end
|
|
|
|
if util.tableSize(rows.top_key_chars) == 0 then rows.top_key_chars = nil end
|
|
|
|
-- we should always have a middle
|
|
|
|
if util.tableSize(rows.bottom_key_chars) == 0 then rows.bottom_key_chars = nil end
|
|
|
|
|
|
|
|
-- to store if a column exists
|
|
|
|
local columns = {}
|
|
|
|
local blank = {
|
|
|
|
HorizontalSpan:new{width = 0},
|
|
|
|
HorizontalSpan:new{width = parent_key.width},
|
|
|
|
HorizontalSpan:new{width = 0},
|
|
|
|
}
|
|
|
|
local h_key_padding = {
|
|
|
|
HorizontalSpan:new{width = 0},
|
|
|
|
HorizontalSpan:new{width = parent_key.keyboard.key_padding},
|
|
|
|
HorizontalSpan:new{width = 0},
|
|
|
|
}
|
|
|
|
local v_key_padding = VerticalSpan:new{width = parent_key.keyboard.key_padding}
|
|
|
|
|
|
|
|
local vertical_group = VerticalGroup:new{ allow_mirroring = false }
|
|
|
|
local horizontal_group_extra = HorizontalGroup:new{ allow_mirroring = false }
|
|
|
|
local horizontal_group_top = HorizontalGroup:new{ allow_mirroring = false }
|
|
|
|
local horizontal_group_middle = HorizontalGroup:new{ allow_mirroring = false }
|
|
|
|
local horizontal_group_bottom = HorizontalGroup:new{ allow_mirroring = false }
|
|
|
|
|
|
|
|
local function horizontalRow(chars, group)
|
|
|
|
local layout_horizontal = {}
|
|
|
|
|
|
|
|
for i = 1,3 do
|
|
|
|
local v = chars[i]
|
|
|
|
local v_func = chars[i+3]
|
|
|
|
|
|
|
|
if v then
|
|
|
|
columns[i] = true
|
|
|
|
blank[i].width = blank[2].width
|
|
|
|
if i == 1 then
|
|
|
|
h_key_padding[i].width = h_key_padding[2].width
|
|
|
|
end
|
|
|
|
|
|
|
|
local key = type(v) == "table" and v.key or v
|
|
|
|
local label = type(v) == "table" and v.label or key
|
|
|
|
local icon = type(v) == "table" and v.icon
|
|
|
|
local bold = type(v) == "table" and v.bold
|
|
|
|
local virtual_key = VirtualKey:new{
|
|
|
|
key = key,
|
|
|
|
label = label,
|
|
|
|
icon = icon,
|
|
|
|
bold = bold,
|
|
|
|
keyboard = parent_key.keyboard,
|
|
|
|
key_chars = key_chars,
|
|
|
|
width = parent_key.width,
|
|
|
|
height = parent_key.height,
|
|
|
|
}
|
|
|
|
-- Support any function as a callback.
|
|
|
|
if v_func then
|
|
|
|
virtual_key.callback = v_func
|
|
|
|
end
|
|
|
|
-- don't open another popup on hold
|
|
|
|
virtual_key.hold_callback = nil
|
|
|
|
-- close popup on hold release
|
|
|
|
virtual_key.onHoldReleaseKey = function()
|
|
|
|
virtual_key:onTapSelect(true)
|
|
|
|
UIManager:close(self)
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
virtual_key.onPanReleaseKey = virtual_key.onHoldReleaseKey
|
|
|
|
|
|
|
|
if v == key_char_orig then
|
|
|
|
virtual_key[1].background = Blitbuffer.COLOR_LIGHT_GRAY
|
|
|
|
|
|
|
|
-- restore ability to hold/pan release on central key after opening popup
|
|
|
|
virtual_key._keyOrigHoldPanHandler = function()
|
|
|
|
virtual_key.onHoldReleaseKey = virtual_key._onHoldReleaseKey
|
|
|
|
virtual_key.onPanReleaseKey = virtual_key._onPanReleaseKey
|
|
|
|
end
|
|
|
|
virtual_key._onHoldReleaseKey = virtual_key.onHoldReleaseKey
|
|
|
|
virtual_key.onHoldReleaseKey = virtual_key._keyOrigHoldPanHandler
|
|
|
|
virtual_key._onPanReleaseKey = virtual_key.onPanReleaseKey
|
|
|
|
virtual_key.onPanReleaseKey = virtual_key._keyOrigHoldPanHandler
|
|
|
|
end
|
|
|
|
|
|
|
|
table.insert(group, virtual_key)
|
|
|
|
table.insert(layout_horizontal, virtual_key)
|
|
|
|
else
|
|
|
|
table.insert(group, blank[i])
|
|
|
|
end
|
|
|
|
table.insert(group, h_key_padding[i])
|
|
|
|
end
|
|
|
|
table.insert(vertical_group, group)
|
|
|
|
table.insert(self.layout, layout_horizontal)
|
|
|
|
end
|
|
|
|
if rows.extra_key_chars then
|
|
|
|
horizontalRow(rows.extra_key_chars, horizontal_group_extra)
|
|
|
|
table.insert(vertical_group, v_key_padding)
|
|
|
|
end
|
|
|
|
if rows.top_key_chars then
|
|
|
|
horizontalRow(rows.top_key_chars, horizontal_group_top)
|
|
|
|
table.insert(vertical_group, v_key_padding)
|
|
|
|
end
|
|
|
|
-- always middle row
|
|
|
|
horizontalRow(rows.middle_key_chars, horizontal_group_middle)
|
|
|
|
if rows.bottom_key_chars then
|
|
|
|
table.insert(vertical_group, v_key_padding)
|
|
|
|
horizontalRow(rows.bottom_key_chars, horizontal_group_bottom)
|
|
|
|
end
|
|
|
|
|
|
|
|
if not columns[3] then
|
|
|
|
h_key_padding[2].width = 0
|
|
|
|
end
|
|
|
|
|
|
|
|
local num_rows = util.tableSize(rows)
|
|
|
|
local num_columns = util.tableSize(columns)
|
|
|
|
|
|
|
|
local keyboard_frame = FrameContainer:new{
|
|
|
|
margin = 0,
|
|
|
|
bordersize = Size.border.default,
|
|
|
|
background = Blitbuffer.COLOR_WHITE,
|
|
|
|
radius = 0,
|
|
|
|
padding = parent_key.keyboard.padding,
|
|
|
|
allow_mirroring = false,
|
|
|
|
CenterContainer:new{
|
|
|
|
dimen = Geom:new{
|
|
|
|
w = parent_key.width*num_columns + 2*Size.border.default + (num_columns)*parent_key.keyboard.key_padding,
|
|
|
|
h = parent_key.height*num_rows + 2*Size.border.default + num_rows*parent_key.keyboard.key_padding,
|
|
|
|
},
|
|
|
|
vertical_group,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
keyboard_frame.dimen = keyboard_frame:getSize()
|
|
|
|
|
|
|
|
self.ges_events = {
|
|
|
|
TapClose = {
|
|
|
|
GestureRange:new{
|
|
|
|
ges = "tap",
|
|
|
|
}
|
|
|
|
},
|
|
|
|
}
|
|
|
|
self.tap_interval_override = G_reader_settings:readSetting("ges_tap_interval_on_keyboard") or 0
|
The great Input/GestureDetector/TimeVal spring cleanup (a.k.a., a saner main loop) (#7415)
* ReaderDictionary: Port delay computations to TimeVal
* ReaderHighlight: Port delay computations to TimeVal
* ReaderView: Port delay computations to TimeVal
* Android: Reset gesture detection state on APP_CMD_TERM_WINDOW.
This prevents potentially being stuck in bogus gesture states when switching apps.
* GestureDetector:
* Port delay computations to TimeVal
* Fixed delay computations to handle time warps (large and negative deltas).
* Simplified timed callback handling to invalidate timers much earlier, preventing accumulating useless timers that no longer have any chance of ever detecting a gesture.
* Fixed state clearing to handle the actual effective slots, instead of hard-coding slot 0 & slot 1.
* Simplified timed callback handling in general, and added support for a timerfd backend for better performance and accuracy.
* The improved timed callback handling allows us to detect and honor (as much as possible) the three possible clock sources usable by Linux evdev events.
The only case where synthetic timestamps are used (and that only to handle timed callbacks) is limited to non-timerfd platforms where input events use
a clock source that is *NOT* MONOTONIC.
AFAICT, that's pretty much... PocketBook, and that's it?
* Input:
* Use the <linux/input.h> FFI module instead of re-declaring every constant
* Fixed (verbose) debug logging of input events to actually translate said constants properly.
* Completely reset gesture detection state on suspend. This should prevent bogus gesture detection on resume.
* Refactored the waitEvent loop to make it easier to comprehend (hopefully) and much more efficient.
Of specific note, it no longer does a crazy select spam every 100µs, instead computing and relying on sane timeouts,
as afforded by switching the UI event/input loop to the MONOTONIC time base, and the refactored timed callbacks in GestureDetector.
* reMarkable: Stopped enforcing synthetic timestamps on input events, as it should no longer be necessary.
* TimeVal:
* Refactored and simplified, especially as far as metamethods are concerned (based on <bsd/sys/time.h>).
* Added a host of new methods to query the various POSIX clock sources, and made :now default to MONOTONIC.
* Removed the debug guard in __sub, as time going backwards can be a perfectly normal occurrence.
* New methods:
* Clock sources: :realtime, :monotonic, :monotonic_coarse, :realtime_coarse, :boottime
* Utility: :tonumber, :tousecs, :tomsecs, :fromnumber, :isPositive, :isZero
* UIManager:
* Ported event loop & scheduling to TimeVal, and switched to the MONOTONIC time base.
This ensures reliable and consistent scheduling, as time is ensured never to go backwards.
* Added a :getTime() method, that returns a cached TimeVal:now(), updated at the top of every UI frame.
It's used throughout the codebase to cadge a syscall in circumstances where we are guaranteed that a syscall would return a mostly identical value,
because very few time has passed.
The only code left that does live syscalls does it because it's actually necessary for accuracy,
and the only code left that does that in a REALTIME time base is code that *actually* deals with calendar time (e.g., Statistics).
* DictQuickLookup: Port delay computations to TimeVal
* FootNoteWidget: Port delay computations to TimeVal
* HTMLBoxWidget: Port delay computations to TimeVal
* Notification: Port delay computations to TimeVal
* TextBoxWidget: Port delay computations to TimeVal
* AutoSuspend: Port to TimeVal
* AutoTurn:
* Fix it so that settings are actually honored.
* Port to TimeVal
* BackgroundRunner: Port to TimeVal
* Calibre: Port benchmarking code to TimeVal
* BookInfoManager: Removed unnecessary yield in the metadata extraction subprocess now that subprocesses get scheduled properly.
* All in all, these changes reduced the CPU cost of a single tap by a factor of ten (!), and got rid of an insane amount of weird poll/wakeup cycles that must have been hell on CPU schedulers and batteries..
4 years ago
|
|
|
self.tap_interval_override = TimeVal:new{ usec = self.tap_interval_override }
|
|
|
|
|
|
|
|
if Device:hasDPad() then
|
|
|
|
self.key_events.PressKey = { {"Press"}, doc = "select key" }
|
|
|
|
end
|
|
|
|
if Device:hasKeys() then
|
|
|
|
self.key_events.Close = { {"Back"}, doc = "close keyboard" }
|
|
|
|
end
|
|
|
|
|
|
|
|
local offset_x = 2*parent_key.keyboard.padding + 2*parent_key.keyboard.bordersize
|
|
|
|
if columns[1] then
|
|
|
|
offset_x = offset_x + parent_key.width + parent_key.keyboard.padding + 2*parent_key.keyboard.bordersize
|
|
|
|
end
|
|
|
|
|
|
|
|
local offset_y = parent_key.keyboard.padding + parent_key.keyboard.padding + 2*parent_key.keyboard.bordersize
|
|
|
|
if rows.extra_key_chars then
|
|
|
|
offset_y = offset_y + parent_key.height + parent_key.keyboard.padding + 2*parent_key.keyboard.bordersize
|
|
|
|
end
|
|
|
|
if rows.top_key_chars then
|
|
|
|
offset_y = offset_y + parent_key.height + parent_key.keyboard.padding + 2*parent_key.keyboard.bordersize
|
|
|
|
end
|
|
|
|
|
|
|
|
local position_container = WidgetContainer:new{
|
|
|
|
dimen = {
|
|
|
|
x = parent_key.dimen.x - offset_x,
|
|
|
|
y = parent_key.dimen.y - offset_y,
|
|
|
|
h = Screen:getSize().h,
|
|
|
|
w = Screen:getSize().w,
|
|
|
|
},
|
|
|
|
keyboard_frame,
|
|
|
|
}
|
|
|
|
if position_container.dimen.x < 0 then
|
|
|
|
position_container.dimen.x = 0
|
|
|
|
elseif position_container.dimen.x + keyboard_frame.dimen.w > Screen:getWidth() then
|
|
|
|
position_container.dimen.x = Screen:getWidth() - keyboard_frame.dimen.w
|
|
|
|
end
|
|
|
|
if position_container.dimen.y < 0 then
|
|
|
|
position_container.dimen.y = 0
|
|
|
|
elseif position_container.dimen.y + keyboard_frame.dimen.h > Screen:getHeight() then
|
|
|
|
position_container.dimen.y = Screen:getHeight() - keyboard_frame.dimen.h
|
|
|
|
end
|
|
|
|
|
|
|
|
self[1] = position_container
|
|
|
|
|
|
|
|
UIManager:show(self)
|
|
|
|
|
|
|
|
UIManager:setDirty(self, function()
|
|
|
|
return "ui", keyboard_frame.dimen
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
|
|
|
|
local VirtualKeyboard = FocusManager:new{
|
Assorted fixes after #7118 (#7161)
* I'd failed to notice that ButtonTable *also* instantiates seven billion Buttons on each update. Unfortunately, that one is way trickier to fix properly, so, work around its behavior in Button. (This fixes multiple issues with stuff using ButtonTable, which is basically anything with a persistent set of buttons. A good and easy test-case is the dictionary popup, e.g., the Highlight button changes text, and the next/prev dic buttons change state. All that, and more, was broken ;p).
* Handle corner-cases related to VirtualKeyboard (e.g., Terminal & Text Editor), which screwed with both TouchMenu & Button heuristics because it's weird.
* Flag a the dictionary switch buttons as vsync
(They trigger a partial repaint of the dictionary content).
* Flag the ReaderSearch buttons as vsync
They very obviously trigger a partial repaint, much like SkimTo ;p.
4 years ago
|
|
|
name = "VirtualKeyboard",
|
|
|
|
modal = true,
|
|
|
|
disable_double_tap = true,
|
|
|
|
inputbox = nil,
|
|
|
|
KEYS = {}, -- table to store layouts
|
|
|
|
shiftmode_keys = {},
|
|
|
|
symbolmode_keys = {},
|
|
|
|
utf8mode_keys = {},
|
|
|
|
umlautmode_keys = {},
|
|
|
|
keyboard_layer = 2,
|
|
|
|
shiftmode = false,
|
|
|
|
symbolmode = false,
|
|
|
|
umlautmode = false,
|
|
|
|
layout = {},
|
|
|
|
|
|
|
|
width = Screen:scaleBySize(600),
|
|
|
|
height = nil,
|
|
|
|
bordersize = Size.border.default,
|
|
|
|
padding = Size.padding.small,
|
|
|
|
key_padding = Size.padding.default,
|
|
|
|
|
|
|
|
lang_to_keyboard_layout = {
|
|
|
|
ar_AA = "ar_AA_keyboard",
|
|
|
|
bg_BG = "bg_keyboard",
|
|
|
|
de = "de_keyboard",
|
|
|
|
el = "el_keyboard",
|
|
|
|
en = "en_keyboard",
|
|
|
|
es = "es_keyboard",
|
|
|
|
fa = "fa_keyboard",
|
|
|
|
fr = "fr_keyboard",
|
|
|
|
he = "he_keyboard",
|
|
|
|
ja = "ja_keyboard",
|
|
|
|
ka = "ka_keyboard",
|
|
|
|
pl = "pl_keyboard",
|
|
|
|
pt_BR = "pt_keyboard",
|
|
|
|
ro = "ro_keyboard",
|
|
|
|
ko_KR = "ko_KR_keyboard",
|
|
|
|
ru = "ru_keyboard",
|
|
|
|
tr = "tr_keyboard",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
function VirtualKeyboard:init()
|
|
|
|
if self.uwrap_func then
|
|
|
|
self.uwrap_func()
|
|
|
|
self.uwrap_func = nil
|
|
|
|
end
|
|
|
|
local lang = self:getKeyboardLayout()
|
|
|
|
local keyboard_layout = self.lang_to_keyboard_layout[lang] or self.lang_to_keyboard_layout["en"]
|
|
|
|
local keyboard = require("ui/data/keyboardlayouts/" .. keyboard_layout)
|
|
|
|
self.KEYS = keyboard.keys
|
|
|
|
self.shiftmode_keys = keyboard.shiftmode_keys
|
|
|
|
self.symbolmode_keys = keyboard.symbolmode_keys
|
|
|
|
self.utf8mode_keys = keyboard.utf8mode_keys
|
|
|
|
self.umlautmode_keys = keyboard.umlautmode_keys
|
|
|
|
self.height = Screen:scaleBySize(64 * #self.KEYS)
|
|
|
|
self.min_layer = keyboard.min_layer
|
|
|
|
self.max_layer = keyboard.max_layer
|
|
|
|
self:initLayer(self.keyboard_layer)
|
|
|
|
self.tap_interval_override = G_reader_settings:readSetting("ges_tap_interval_on_keyboard") or 0
|
The great Input/GestureDetector/TimeVal spring cleanup (a.k.a., a saner main loop) (#7415)
* ReaderDictionary: Port delay computations to TimeVal
* ReaderHighlight: Port delay computations to TimeVal
* ReaderView: Port delay computations to TimeVal
* Android: Reset gesture detection state on APP_CMD_TERM_WINDOW.
This prevents potentially being stuck in bogus gesture states when switching apps.
* GestureDetector:
* Port delay computations to TimeVal
* Fixed delay computations to handle time warps (large and negative deltas).
* Simplified timed callback handling to invalidate timers much earlier, preventing accumulating useless timers that no longer have any chance of ever detecting a gesture.
* Fixed state clearing to handle the actual effective slots, instead of hard-coding slot 0 & slot 1.
* Simplified timed callback handling in general, and added support for a timerfd backend for better performance and accuracy.
* The improved timed callback handling allows us to detect and honor (as much as possible) the three possible clock sources usable by Linux evdev events.
The only case where synthetic timestamps are used (and that only to handle timed callbacks) is limited to non-timerfd platforms where input events use
a clock source that is *NOT* MONOTONIC.
AFAICT, that's pretty much... PocketBook, and that's it?
* Input:
* Use the <linux/input.h> FFI module instead of re-declaring every constant
* Fixed (verbose) debug logging of input events to actually translate said constants properly.
* Completely reset gesture detection state on suspend. This should prevent bogus gesture detection on resume.
* Refactored the waitEvent loop to make it easier to comprehend (hopefully) and much more efficient.
Of specific note, it no longer does a crazy select spam every 100µs, instead computing and relying on sane timeouts,
as afforded by switching the UI event/input loop to the MONOTONIC time base, and the refactored timed callbacks in GestureDetector.
* reMarkable: Stopped enforcing synthetic timestamps on input events, as it should no longer be necessary.
* TimeVal:
* Refactored and simplified, especially as far as metamethods are concerned (based on <bsd/sys/time.h>).
* Added a host of new methods to query the various POSIX clock sources, and made :now default to MONOTONIC.
* Removed the debug guard in __sub, as time going backwards can be a perfectly normal occurrence.
* New methods:
* Clock sources: :realtime, :monotonic, :monotonic_coarse, :realtime_coarse, :boottime
* Utility: :tonumber, :tousecs, :tomsecs, :fromnumber, :isPositive, :isZero
* UIManager:
* Ported event loop & scheduling to TimeVal, and switched to the MONOTONIC time base.
This ensures reliable and consistent scheduling, as time is ensured never to go backwards.
* Added a :getTime() method, that returns a cached TimeVal:now(), updated at the top of every UI frame.
It's used throughout the codebase to cadge a syscall in circumstances where we are guaranteed that a syscall would return a mostly identical value,
because very few time has passed.
The only code left that does live syscalls does it because it's actually necessary for accuracy,
and the only code left that does that in a REALTIME time base is code that *actually* deals with calendar time (e.g., Statistics).
* DictQuickLookup: Port delay computations to TimeVal
* FootNoteWidget: Port delay computations to TimeVal
* HTMLBoxWidget: Port delay computations to TimeVal
* Notification: Port delay computations to TimeVal
* TextBoxWidget: Port delay computations to TimeVal
* AutoSuspend: Port to TimeVal
* AutoTurn:
* Fix it so that settings are actually honored.
* Port to TimeVal
* BackgroundRunner: Port to TimeVal
* Calibre: Port benchmarking code to TimeVal
* BookInfoManager: Removed unnecessary yield in the metadata extraction subprocess now that subprocesses get scheduled properly.
* All in all, these changes reduced the CPU cost of a single tap by a factor of ten (!), and got rid of an insane amount of weird poll/wakeup cycles that must have been hell on CPU schedulers and batteries..
4 years ago
|
|
|
self.tap_interval_override = TimeVal:new{ usec = self.tap_interval_override }
|
A few graphics fixes after #4541 (#4554)
* Various FocusManager related tweaks to limit its usage to devices with a DPad, and prevent initial button highlights in Dialogs on devices where it makes no sense (i.e., those without a DPad. And even on DPad devices, I'm not even sure how we'd go about making one of those pop up anyway, because no Touch ;)!).
* One mysterious fix to text-only Buttons so that the flash_ui highlight always works, and always honors `FrameContainer`'s pill shape. (Before that, an unhighlight on a text button with a callback that didn't repaint anything [say, the find first/find last buttons in the Reader's search bar when you're already on the first/last match] would do a square black highlight, and a white pill-shaped unhighlight (leaving the black corners visible)).
The workaround makes *absolutely* no sense to me (as `self[1] -> self.frame`, AFAICT), but it works, and ensures all highlights/unhighlights are pill-shaped, so at least we're not doing maths for rounded corners for nothing ;).
6 years ago
|
|
|
if Device:hasDPad() then
|
|
|
|
self.key_events.PressKey = { {"Press"}, doc = "select key" }
|
A few graphics fixes after #4541 (#4554)
* Various FocusManager related tweaks to limit its usage to devices with a DPad, and prevent initial button highlights in Dialogs on devices where it makes no sense (i.e., those without a DPad. And even on DPad devices, I'm not even sure how we'd go about making one of those pop up anyway, because no Touch ;)!).
* One mysterious fix to text-only Buttons so that the flash_ui highlight always works, and always honors `FrameContainer`'s pill shape. (Before that, an unhighlight on a text button with a callback that didn't repaint anything [say, the find first/find last buttons in the Reader's search bar when you're already on the first/last match] would do a square black highlight, and a white pill-shaped unhighlight (leaving the black corners visible)).
The workaround makes *absolutely* no sense to me (as `self[1] -> self.frame`, AFAICT), but it works, and ensures all highlights/unhighlights are pill-shaped, so at least we're not doing maths for rounded corners for nothing ;).
6 years ago
|
|
|
end
|
|
|
|
if Device:hasKeys() then
|
|
|
|
self.key_events.Close = { {"Back"}, doc = "close keyboard" }
|
|
|
|
end
|
|
|
|
if keyboard.wrapInputBox then
|
|
|
|
self.uwrap_func = keyboard.wrapInputBox(self.inputbox) or self.uwrap_func
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function VirtualKeyboard:getKeyboardLayout()
|
|
|
|
return G_reader_settings:readSetting("keyboard_layout") or G_reader_settings:readSetting("language")
|
|
|
|
end
|
|
|
|
|
|
|
|
function VirtualKeyboard:setKeyboardLayout(layout)
|
|
|
|
local prev_keyboard_height = self.dimen and self.dimen.h
|
|
|
|
G_reader_settings:saveSetting("keyboard_layout", layout)
|
|
|
|
self:init()
|
|
|
|
if prev_keyboard_height and self.dimen.h ~= prev_keyboard_height then
|
|
|
|
self:_refresh(true, true)
|
|
|
|
-- Keyboard height change: notify parent (InputDialog)
|
|
|
|
if self.inputbox and self.inputbox.parent and self.inputbox.parent.onKeyboardHeightChanged then
|
|
|
|
self.inputbox.parent:onKeyboardHeightChanged()
|
|
|
|
end
|
|
|
|
else
|
|
|
|
self:_refresh(true)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function VirtualKeyboard:onClose()
|
|
|
|
UIManager:close(self)
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
function VirtualKeyboard:onPressKey()
|
|
|
|
self:getFocusItem():handleEvent(Event:new("TapSelect"))
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
function VirtualKeyboard:_refresh(want_flash, fullscreen)
|
|
|
|
local refresh_type = "ui"
|
|
|
|
if want_flash then
|
|
|
|
refresh_type = "flashui"
|
|
|
|
end
|
|
|
|
if fullscreen then
|
|
|
|
UIManager:setDirty("all", refresh_type)
|
|
|
|
return
|
|
|
|
end
|
|
|
|
UIManager:setDirty(self, function()
|
|
|
|
return refresh_type, self[1][1].dimen
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
|
|
|
|
function VirtualKeyboard:onShow()
|
|
|
|
self:_refresh(true)
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
function VirtualKeyboard:onCloseWidget()
|
|
|
|
self:_refresh(false)
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
function VirtualKeyboard:initLayer(layer)
|
|
|
|
local function VKLayer(b1, b2, b3)
|
|
|
|
local function boolnum(bool)
|
|
|
|
return bool and 1 or 0
|
|
|
|
end
|
|
|
|
return 2 - boolnum(b1) + 2 * boolnum(b2) + 4 * boolnum(b3)
|
|
|
|
end
|
|
|
|
|
|
|
|
if layer then
|
|
|
|
-- to be sure layer is selected properly
|
|
|
|
layer = math.max(layer, self.min_layer)
|
|
|
|
layer = math.min(layer, self.max_layer)
|
|
|
|
self.keyboard_layer = layer
|
|
|
|
-- fill the layer modes
|
|
|
|
self.shiftmode = (layer == 1 or layer == 3 or layer == 5 or layer == 7 or layer == 9 or layer == 11)
|
|
|
|
self.symbolmode = (layer == 3 or layer == 4 or layer == 7 or layer == 8 or layer == 11 or layer == 12)
|
|
|
|
self.umlautmode = (layer == 5 or layer == 6 or layer == 7 or layer == 8)
|
|
|
|
else -- or, without input parameter, restore layer from current layer modes
|
|
|
|
self.keyboard_layer = VKLayer(self.shiftmode, self.symbolmode, self.umlautmode)
|
|
|
|
end
|
|
|
|
self:addKeys()
|
|
|
|
end
|
|
|
|
|
|
|
|
function VirtualKeyboard:addKeys()
|
|
|
|
self:free() -- free previous keys' TextWidgets
|
|
|
|
self.layout = {}
|
|
|
|
local base_key_width = math.floor((self.width - (#self.KEYS[1] + 1)*self.key_padding - 2*self.padding)/#self.KEYS[1])
|
|
|
|
local base_key_height = math.floor((self.height - (#self.KEYS + 1)*self.key_padding - 2*self.padding)/#self.KEYS)
|
|
|
|
local h_key_padding = HorizontalSpan:new{width = self.key_padding}
|
|
|
|
local v_key_padding = VerticalSpan:new{width = self.key_padding}
|
|
|
|
local vertical_group = VerticalGroup:new{ allow_mirroring = false }
|
|
|
|
for i = 1, #self.KEYS do
|
|
|
|
local horizontal_group = HorizontalGroup:new{ allow_mirroring = false }
|
|
|
|
local layout_horizontal = {}
|
|
|
|
for j = 1, #self.KEYS[i] do
|
|
|
|
local key
|
|
|
|
local key_chars = self.KEYS[i][j][self.keyboard_layer]
|
|
|
|
local label
|
|
|
|
if type(key_chars) == "table" then
|
|
|
|
key = key_chars[1]
|
|
|
|
label = key_chars.label
|
|
|
|
else
|
|
|
|
key = key_chars
|
|
|
|
key_chars = nil
|
|
|
|
end
|
|
|
|
local width_factor = self.KEYS[i][j].width or 1.0
|
|
|
|
local key_width = math.floor((base_key_width + self.key_padding) * width_factor)
|
|
|
|
- self.key_padding
|
|
|
|
local key_height = base_key_height
|
|
|
|
label = label or self.KEYS[i][j].label or key
|
|
|
|
local virtual_key = VirtualKey:new{
|
|
|
|
key = key,
|
|
|
|
key_chars = key_chars,
|
|
|
|
icon = self.KEYS[i][j].icon,
|
|
|
|
label = label,
|
|
|
|
bold = self.KEYS[i][j].bold,
|
|
|
|
keyboard = self,
|
|
|
|
width = key_width,
|
|
|
|
height = key_height,
|
|
|
|
}
|
|
|
|
if not virtual_key.key_chars then
|
|
|
|
virtual_key.swipe_callback = nil
|
|
|
|
end
|
|
|
|
table.insert(horizontal_group, virtual_key)
|
|
|
|
table.insert(layout_horizontal, virtual_key)
|
|
|
|
if j ~= #self.KEYS[i] then
|
|
|
|
table.insert(horizontal_group, h_key_padding)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
table.insert(vertical_group, horizontal_group)
|
|
|
|
table.insert(self.layout, layout_horizontal)
|
|
|
|
if i ~= #self.KEYS then
|
|
|
|
table.insert(vertical_group, v_key_padding)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local keyboard_frame = FrameContainer:new{
|
|
|
|
margin = 0,
|
|
|
|
bordersize = Size.border.default,
|
|
|
|
background = Blitbuffer.COLOR_WHITE,
|
|
|
|
radius = 0,
|
|
|
|
padding = self.padding,
|
|
|
|
allow_mirroring = false,
|
|
|
|
CenterContainer:new{
|
|
|
|
dimen = Geom:new{
|
|
|
|
w = self.width - 2*Size.border.default - 2*self.padding,
|
|
|
|
h = self.height - 2*Size.border.default - 2*self.padding,
|
|
|
|
},
|
|
|
|
vertical_group,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
self[1] = BottomContainer:new{
|
|
|
|
dimen = Screen:getSize(),
|
|
|
|
keyboard_frame,
|
|
|
|
}
|
|
|
|
self.dimen = keyboard_frame:getSize()
|
|
|
|
end
|
|
|
|
|
|
|
|
function VirtualKeyboard:setLayer(key)
|
|
|
|
if key == "Shift" then
|
|
|
|
self.shiftmode = not self.shiftmode
|
|
|
|
elseif key == "Sym" or key == "ABC" then
|
|
|
|
self.symbolmode = not self.symbolmode
|
|
|
|
elseif key == "Äéß" then
|
|
|
|
self.umlautmode = not self.umlautmode
|
|
|
|
end
|
|
|
|
self:initLayer()
|
|
|
|
self:_refresh(true)
|
|
|
|
end
|
|
|
|
|
|
|
|
function VirtualKeyboard:addChar(key)
|
|
|
|
logger.dbg("add char", key)
|
|
|
|
self.inputbox:addChars(key)
|
|
|
|
end
|
|
|
|
|
|
|
|
function VirtualKeyboard:delChar()
|
|
|
|
logger.dbg("delete char")
|
|
|
|
self.inputbox:delChar()
|
|
|
|
end
|
|
|
|
|
|
|
|
function VirtualKeyboard:delToStartOfLine()
|
|
|
|
logger.dbg("delete to start of line")
|
|
|
|
self.inputbox:delToStartOfLine()
|
|
|
|
end
|
|
|
|
|
|
|
|
function VirtualKeyboard:leftChar()
|
|
|
|
self.inputbox:leftChar()
|
|
|
|
end
|
|
|
|
|
|
|
|
function VirtualKeyboard:rightChar()
|
|
|
|
self.inputbox:rightChar()
|
|
|
|
end
|
|
|
|
|
|
|
|
function VirtualKeyboard:upLine()
|
|
|
|
self.inputbox:upLine()
|
|
|
|
end
|
|
|
|
|
|
|
|
function VirtualKeyboard:downLine()
|
|
|
|
self.inputbox:downLine()
|
|
|
|
end
|
|
|
|
|
|
|
|
function VirtualKeyboard:clear()
|
|
|
|
logger.dbg("clear input")
|
|
|
|
self.inputbox:clear()
|
|
|
|
end
|
|
|
|
|
|
|
|
return VirtualKeyboard
|