mirror of
https://github.com/koreader/koreader
synced 2024-11-13 19:11:25 +00:00
e502bf04d3
* [VirtualKeyboard] Add support for keynaviguation Also rename the variable "layout" to "keyboard_layout" because conflict with the layout from the focusmanager * Make the goto dialog compatible with key naviguation My solution is to change the order of the widget. The last one will the virtualkeybard so it catch all the keybinding, and below it, make the dialog "is_always_active = true" so it can receive touch event. * Correctly show the virtual keyboard on dpad devices * change the order to call the virtualKeyboard so it end up on top * Handle the multi input dialog * Support reopening the virtualKeyboard by the Press key * add check focusmanager * Fix https://github.com/koreader/koreader/issues/3797 * MultiInputDialog : Now work on non touch-device * Set the virtualkeyboard to be a modal widget * Fix the layout in multiinputwidget * Fix for the various combination of hasKeys,hasDpad,isTouchDevice * [Focusmanager] Better handling of malformed layout
248 lines
7.6 KiB
Lua
248 lines
7.6 KiB
Lua
--[[--
|
|
An InputContainer is a WidgetContainer that handles user input events including multi touches
|
|
and key presses.
|
|
|
|
See @{InputContainer:registerTouchZones} for examples of how to listen for multi touch input.
|
|
|
|
This example illustrates how to listen for a key press input event:
|
|
|
|
PanBy20 = {
|
|
{ "Shift", Input.group.Cursor },
|
|
seqtext = "Shift+Cursor",
|
|
doc = "pan by 20px",
|
|
event = "Pan", args = 20, is_inactive = true,
|
|
},
|
|
PanNormal = {
|
|
{ Input.group.Cursor },
|
|
seqtext = "Cursor",
|
|
doc = "pan by 10 px", event = "Pan", args = 10,
|
|
},
|
|
Quit = { {"Home"} },
|
|
|
|
It is recommended to reference configurable sequences from another table
|
|
and to store that table as a configuration setting.
|
|
|
|
]]
|
|
|
|
local DepGraph = require("depgraph")
|
|
local Event = require("ui/event")
|
|
local Geom = require("ui/geometry")
|
|
local GestureRange = require("ui/gesturerange")
|
|
local UIManager = require("ui/uimanager")
|
|
local WidgetContainer = require("ui/widget/container/widgetcontainer")
|
|
local _ = require("gettext")
|
|
local Screen = require("device").screen
|
|
|
|
if require("device"):isAndroid() then
|
|
require("jit").off(true, true)
|
|
end
|
|
|
|
local InputContainer = WidgetContainer:new{
|
|
vertical_align = "top",
|
|
}
|
|
|
|
function InputContainer:_init()
|
|
-- we need to do deep copy here
|
|
local new_key_events = {}
|
|
if self.key_events then
|
|
for k,v in pairs(self.key_events) do
|
|
new_key_events[k] = v
|
|
end
|
|
end
|
|
self.key_events = new_key_events
|
|
|
|
local new_ges_events = {}
|
|
if self.ges_events then
|
|
for k,v in pairs(self.ges_events) do
|
|
new_ges_events[k] = v
|
|
end
|
|
end
|
|
self.ges_events = new_ges_events
|
|
self.touch_zone_dg = nil
|
|
self._zones = {}
|
|
self._ordered_touch_zones = {}
|
|
end
|
|
|
|
function InputContainer:paintTo(bb, x, y)
|
|
if self[1] == nil then
|
|
return
|
|
end
|
|
|
|
if not self.dimen then
|
|
local content_size = self[1]:getSize()
|
|
self.dimen = Geom:new{w = content_size.w, h = content_size.h}
|
|
end
|
|
self.dimen.x = x
|
|
self.dimen.y = y
|
|
if self.vertical_align == "center" then
|
|
local content_size = self[1]:getSize()
|
|
self[1]:paintTo(bb, x, y + math.floor((self.dimen.h - content_size.h)/2))
|
|
else
|
|
self[1]:paintTo(bb, x, y)
|
|
end
|
|
end
|
|
|
|
--[[--
|
|
|
|
Register touch zones into this InputContainer.
|
|
|
|
See gesturedetector for a list of supported gestures.
|
|
|
|
NOTE: You are responsible for calling self:@{updateTouchZonesOnScreenResize} with the new
|
|
screen dimensions whenever the screen is rotated or resized.
|
|
|
|
@tparam table zones list of touch zones to register
|
|
|
|
@usage
|
|
local InputContainer = require("ui/widget/container/inputcontainer")
|
|
local test_widget = InputContainer:new{}
|
|
test_widget:registerTouchZones({
|
|
{
|
|
id = "foo_tap",
|
|
ges = "tap",
|
|
-- This binds the handler to the full screen
|
|
screen_zone = {
|
|
ratio_x = 0, ratio_y = 0, ratio_w = 1, ratio_h = 1,
|
|
},
|
|
handler = function(ges)
|
|
print('User tapped on screen!')
|
|
return true
|
|
end
|
|
},
|
|
{
|
|
id = "foo_swipe",
|
|
ges = "swipe",
|
|
-- This binds the handler to bottom half of the screen
|
|
screen_zone = {
|
|
ratio_x = 0, ratio_y = 0.5, ratio_w = 1, ratio_h = 0.5,
|
|
},
|
|
handler = function(ges)
|
|
print("User swiped at the bottom with direction:", ges.direction)
|
|
return true
|
|
end
|
|
},
|
|
})
|
|
require("ui/uimanager"):show(test_widget)
|
|
|
|
]]
|
|
function InputContainer:registerTouchZones(zones)
|
|
local screen_width, screen_height = Screen:getWidth(), Screen:getHeight()
|
|
if not self.touch_zone_dg then self.touch_zone_dg = DepGraph:new{} end
|
|
for _, zone in ipairs(zones) do
|
|
-- override touch zone with the same id to support reregistration
|
|
if self._zones[zone.id] then
|
|
self.touch_zone_dg:removeNode(zone.id)
|
|
end
|
|
self._zones[zone.id]= {
|
|
def = zone,
|
|
handler = zone.handler,
|
|
gs_range = GestureRange:new{
|
|
ges = zone.ges,
|
|
rate = zone.rate,
|
|
range = Geom:new{
|
|
x = screen_width * zone.screen_zone.ratio_x,
|
|
y = screen_height * zone.screen_zone.ratio_y,
|
|
w = screen_width * zone.screen_zone.ratio_w,
|
|
h = screen_height * zone.screen_zone.ratio_h,
|
|
},
|
|
},
|
|
}
|
|
self.touch_zone_dg:addNode(zone.id)
|
|
if zone.overrides then
|
|
for _, override_zone_id in ipairs(zone.overrides) do
|
|
self.touch_zone_dg:addNodeDep(override_zone_id, zone.id)
|
|
end
|
|
end
|
|
end
|
|
self._ordered_touch_zones = {}
|
|
for _, zone_id in ipairs(self.touch_zone_dg:serialize()) do
|
|
table.insert(self._ordered_touch_zones, self._zones[zone_id])
|
|
end
|
|
end
|
|
|
|
--[[--
|
|
Updates touch zones based on new screen dimensions.
|
|
|
|
@tparam ui.geometry.Geom new_screen_dimen new screen dimensions
|
|
]]
|
|
function InputContainer:updateTouchZonesOnScreenResize(new_screen_dimen)
|
|
for _, tzone in ipairs(self._ordered_touch_zones) do
|
|
local range = tzone.gs_range.range
|
|
range.x = new_screen_dimen.w * tzone.def.screen_zone.ratio_x
|
|
range.y = new_screen_dimen.h * tzone.def.screen_zone.ratio_y
|
|
range.w = new_screen_dimen.w * tzone.def.screen_zone.ratio_w
|
|
range.h = new_screen_dimen.h * tzone.def.screen_zone.ratio_h
|
|
end
|
|
end
|
|
|
|
--[[
|
|
Handles keypresses and checks if they lead to a command.
|
|
If this is the case, we retransmit another event within ourselves.
|
|
--]]
|
|
function InputContainer:onKeyPress(key)
|
|
for name, seq in pairs(self.key_events) do
|
|
if not seq.is_inactive then
|
|
for _, oneseq in ipairs(seq) do
|
|
if key:match(oneseq) then
|
|
local eventname = seq.event or name
|
|
return self:handleEvent(Event:new(eventname, seq.args, key))
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function InputContainer:onGesture(ev)
|
|
for _, tzone in ipairs(self._ordered_touch_zones) do
|
|
if tzone.gs_range:match(ev) and tzone.handler(ev) then
|
|
return true
|
|
end
|
|
end
|
|
for name, gsseq in pairs(self.ges_events) do
|
|
for _, gs_range in ipairs(gsseq) do
|
|
if gs_range:match(ev) then
|
|
local eventname = gsseq.event or name
|
|
if self:handleEvent(Event:new(eventname, gsseq.args, ev)) then
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function InputContainer:onInput(input)
|
|
local InputDialog = require("ui/widget/inputdialog")
|
|
self.input_dialog = InputDialog:new{
|
|
title = input.title or "",
|
|
input = input.input,
|
|
input_hint = input.hint_func and input.hint_func() or input.hint or "",
|
|
input_type = input.type or "number",
|
|
buttons = input.buttons or {
|
|
{
|
|
{
|
|
text = input.cancel_text or _("Cancel"),
|
|
callback = function()
|
|
self:closeInputDialog()
|
|
end,
|
|
},
|
|
{
|
|
text = input.ok_text or _("OK"),
|
|
is_enter_default = true,
|
|
callback = function()
|
|
input.callback(self.input_dialog:getInputText())
|
|
self:closeInputDialog()
|
|
end,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
UIManager:show(self.input_dialog)
|
|
self.input_dialog:onShowKeyboard()
|
|
end
|
|
|
|
function InputContainer:closeInputDialog()
|
|
UIManager:close(self.input_dialog)
|
|
end
|
|
|
|
return InputContainer
|