2016-12-04 06:57:57 +00:00
|
|
|
--[[--
|
2016-12-13 16:06:02 +00:00
|
|
|
An InputContainer is a WidgetContainer that handles user input events including multi touches
|
2016-12-04 06:57:57 +00:00
|
|
|
and key presses.
|
2016-04-07 15:28:52 +00:00
|
|
|
|
2016-12-13 16:06:02 +00:00
|
|
|
See @{InputContainer:registerTouchZones} for examples of how to listen for multi touch input.
|
2013-10-18 20:38:07 +00:00
|
|
|
|
2016-12-13 16:06:02 +00:00
|
|
|
This example illustrates how to listen for a key press input event:
|
2013-10-18 20:38:07 +00:00
|
|
|
|
2014-03-13 13:52:43 +00:00
|
|
|
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"} },
|
2013-10-18 20:38:07 +00:00
|
|
|
|
2016-12-13 16:06:02 +00:00
|
|
|
It is recommended to reference configurable sequences from another table
|
|
|
|
and to store that table as a configuration setting.
|
2016-12-04 06:57:57 +00:00
|
|
|
|
|
|
|
]]
|
|
|
|
|
2017-01-23 14:54:14 +00:00
|
|
|
local DepGraph = require("depgraph")
|
2016-12-04 06:57:57 +00:00
|
|
|
local Event = require("ui/event")
|
2017-04-07 13:20:57 +00:00
|
|
|
local Geom = require("ui/geometry")
|
|
|
|
local GestureRange = require("ui/gesturerange")
|
|
|
|
local UIManager = require("ui/uimanager")
|
|
|
|
local WidgetContainer = require("ui/widget/container/widgetcontainer")
|
2019-02-18 16:07:27 +00:00
|
|
|
local Device = require("device")
|
|
|
|
local Screen = Device.screen
|
2016-12-04 06:57:57 +00:00
|
|
|
local _ = require("gettext")
|
|
|
|
|
2019-02-18 16:07:27 +00:00
|
|
|
if Device.should_restrict_JIT then
|
2020-12-26 19:23:51 +00:00
|
|
|
jit.off(true, true)
|
2016-12-04 06:57:57 +00:00
|
|
|
end
|
|
|
|
|
2013-10-18 20:38:07 +00:00
|
|
|
local InputContainer = WidgetContainer:new{
|
2014-03-13 13:52:43 +00:00
|
|
|
vertical_align = "top",
|
2013-10-18 20:38:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function InputContainer:_init()
|
2014-03-13 13:52:43 +00:00
|
|
|
-- 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
|
2013-10-18 20:38:07 +00:00
|
|
|
|
2014-03-13 13:52:43 +00:00
|
|
|
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
|
2017-01-23 14:54:14 +00:00
|
|
|
self.touch_zone_dg = nil
|
|
|
|
self._zones = {}
|
|
|
|
self._ordered_touch_zones = {}
|
2013-10-18 20:38:07 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
function InputContainer:paintTo(bb, x, y)
|
2016-01-31 22:25:21 +00:00
|
|
|
if self[1] == nil then
|
|
|
|
return
|
|
|
|
end
|
2019-05-05 08:15:13 +00:00
|
|
|
if self.skip_paint then
|
|
|
|
return
|
|
|
|
end
|
2016-01-31 22:25:21 +00:00
|
|
|
|
2015-09-13 08:06:22 +00:00
|
|
|
if not self.dimen then
|
2015-10-26 15:53:07 +00:00
|
|
|
local content_size = self[1]:getSize()
|
|
|
|
self.dimen = Geom:new{w = content_size.w, h = content_size.h}
|
2015-09-13 08:06:22 +00:00
|
|
|
end
|
2014-03-13 13:52:43 +00:00
|
|
|
self.dimen.x = x
|
|
|
|
self.dimen.y = y
|
2016-01-31 22:25:21 +00:00
|
|
|
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)
|
2014-03-13 13:52:43 +00:00
|
|
|
end
|
2013-10-18 20:38:07 +00:00
|
|
|
end
|
|
|
|
|
2016-12-04 06:57:57 +00:00
|
|
|
--[[--
|
|
|
|
|
|
|
|
Register touch zones into this InputContainer.
|
|
|
|
|
2016-12-13 16:06:02 +00:00
|
|
|
See gesturedetector for a list of supported gestures.
|
2016-12-04 06:57:57 +00:00
|
|
|
|
|
|
|
NOTE: You are responsible for calling self:@{updateTouchZonesOnScreenResize} with the new
|
2016-12-13 16:06:02 +00:00
|
|
|
screen dimensions whenever the screen is rotated or resized.
|
2016-12-04 06:57:57 +00:00
|
|
|
|
|
|
|
@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",
|
2016-12-13 16:06:02 +00:00
|
|
|
-- This binds the handler to the full screen
|
2016-12-04 06:57:57 +00:00
|
|
|
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",
|
2016-12-13 16:06:02 +00:00
|
|
|
-- This binds the handler to bottom half of the screen
|
2016-12-04 06:57:57 +00:00
|
|
|
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()
|
2017-01-23 14:54:14 +00:00
|
|
|
if not self.touch_zone_dg then self.touch_zone_dg = DepGraph:new{} end
|
2016-12-04 06:57:57 +00:00
|
|
|
for _, zone in ipairs(zones) do
|
2017-01-23 14:54:14 +00:00
|
|
|
-- override touch zone with the same id to support reregistration
|
|
|
|
if self._zones[zone.id] then
|
|
|
|
self.touch_zone_dg:removeNode(zone.id)
|
2016-12-04 06:57:57 +00:00
|
|
|
end
|
2017-01-23 14:54:14 +00:00
|
|
|
self._zones[zone.id]= {
|
2016-12-04 06:57:57 +00:00
|
|
|
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,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
2017-01-23 14:54:14 +00:00
|
|
|
self.touch_zone_dg:addNode(zone.id)
|
2020-01-24 19:05:21 +00:00
|
|
|
-- print("added "..zone.id)
|
2017-01-23 14:54:14 +00:00
|
|
|
if zone.overrides then
|
|
|
|
for _, override_zone_id in ipairs(zone.overrides) do
|
2020-01-24 19:05:21 +00:00
|
|
|
-- print(" override "..override_zone_id)
|
2017-01-23 14:54:14 +00:00
|
|
|
self.touch_zone_dg:addNodeDep(override_zone_id, zone.id)
|
2016-12-04 06:57:57 +00:00
|
|
|
end
|
|
|
|
end
|
2017-01-23 14:54:14 +00:00
|
|
|
end
|
2020-01-24 19:05:21 +00:00
|
|
|
-- print("ordering:")
|
2017-01-23 14:54:14 +00:00
|
|
|
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])
|
2020-01-24 19:05:21 +00:00
|
|
|
-- print(" "..zone_id)
|
2016-12-04 06:57:57 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-09-29 21:15:57 +00:00
|
|
|
function InputContainer:unRegisterTouchZones(zones)
|
|
|
|
if self.touch_zone_dg then
|
|
|
|
for i, zone in ipairs(zones) do
|
|
|
|
if self._zones[zone.id] then
|
|
|
|
self.touch_zone_dg:removeNode(zone.id)
|
|
|
|
if zone.overrides then
|
|
|
|
for _, override_zone_id in ipairs(zone.overrides) do
|
|
|
|
--self.touch_zone_dg:removeNodeDep(override_zone_id, zone.id)
|
|
|
|
self.touch_zone_dg:removeNodeDep(override_zone_id, zone.id)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
for _, id in ipairs(self._ordered_touch_zones) do
|
|
|
|
if id.def.id == zone.id then
|
|
|
|
table.remove(self._ordered_touch_zones, i)
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
self._ordered_touch_zones = {}
|
|
|
|
if self.touch_zone_dg then
|
|
|
|
for _, zone_id in ipairs(self.touch_zone_dg:serialize()) do
|
|
|
|
table.insert(self._ordered_touch_zones, self._zones[zone_id])
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function InputContainer:checkRegisterTouchZone(id)
|
|
|
|
if self.touch_zone_dg then
|
|
|
|
return self.touch_zone_dg:checkNode(id)
|
|
|
|
else
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-12-04 06:57:57 +00:00
|
|
|
--[[--
|
2016-12-13 16:06:02 +00:00
|
|
|
Updates touch zones based on new screen dimensions.
|
2016-12-04 06:57:57 +00:00
|
|
|
|
2016-12-13 16:06:02 +00:00
|
|
|
@tparam ui.geometry.Geom new_screen_dimen new screen dimensions
|
2016-12-04 06:57:57 +00:00
|
|
|
]]
|
|
|
|
function InputContainer:updateTouchZonesOnScreenResize(new_screen_dimen)
|
2017-01-23 14:54:14 +00:00
|
|
|
for _, tzone in ipairs(self._ordered_touch_zones) do
|
2016-12-19 04:26:10 +00:00
|
|
|
local range = tzone.gs_range.range
|
2016-12-04 06:57:57 +00:00
|
|
|
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
|
|
|
|
|
2013-10-18 20:38:07 +00:00
|
|
|
--[[
|
2016-12-13 16:06:02 +00:00
|
|
|
Handles keypresses and checks if they lead to a command.
|
|
|
|
If this is the case, we retransmit another event within ourselves.
|
2013-10-18 20:38:07 +00:00
|
|
|
--]]
|
|
|
|
function InputContainer:onKeyPress(key)
|
2014-03-13 13:52:43 +00:00
|
|
|
for name, seq in pairs(self.key_events) do
|
2019-01-11 15:39:00 +00:00
|
|
|
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
|
|
|
|
|
|
|
|
-- NOTE: Currently a verbatim copy of onKeyPress ;).
|
|
|
|
function InputContainer:onKeyRepeat(key)
|
|
|
|
for name, seq in pairs(self.key_events) do
|
2014-03-13 13:52:43 +00:00
|
|
|
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
|
2013-10-18 20:38:07 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
function InputContainer:onGesture(ev)
|
2017-01-23 14:54:14 +00:00
|
|
|
for _, tzone in ipairs(self._ordered_touch_zones) do
|
|
|
|
if tzone.gs_range:match(ev) and tzone.handler(ev) then
|
|
|
|
return true
|
2016-12-04 06:57:57 +00:00
|
|
|
end
|
|
|
|
end
|
2014-03-13 13:52:43 +00:00
|
|
|
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
|
2017-01-23 14:54:14 +00:00
|
|
|
if self:handleEvent(Event:new(eventname, gsseq.args, ev)) then
|
|
|
|
return true
|
|
|
|
end
|
2014-03-13 13:52:43 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2021-05-16 10:45:36 +00:00
|
|
|
if self.stop_events_propagation then
|
|
|
|
return true
|
|
|
|
end
|
2013-10-18 20:38:07 +00:00
|
|
|
end
|
|
|
|
|
2019-05-08 08:13:44 +00:00
|
|
|
function InputContainer:onInput(input, ignore_first_hold_release)
|
2015-04-13 06:45:02 +00:00
|
|
|
local InputDialog = require("ui/widget/inputdialog")
|
|
|
|
self.input_dialog = InputDialog:new{
|
|
|
|
title = input.title or "",
|
2020-02-12 22:05:18 +00:00
|
|
|
input = input.input_func and input.input_func() or input.input,
|
2015-04-13 06:45:02 +00:00
|
|
|
input_hint = input.hint_func and input.hint_func() or input.hint or "",
|
|
|
|
input_type = input.type or "number",
|
[RFC] Pagination UI shenanigans (#7335)
* Menu/KeyValuePage/ReaderGoTo: Unify the dialogs. (Generally, "Enter page number" as title, and "Go to page" as OK button).
* Allow *tapping* on pagination buttons, too. Added spacers around the text to accommodate for that.
* Disable input handlers when <= 1 pages, while still printing the label in black.
* Always display both the label and the chevrons, even on single page content. (Menu being an exception, because it can handle showing no content at all, in which case we hide the chevrons).
* KVP: Tweak the pagination buttons layout in order to have consistent centering, regardless of whether the return arrow is enabled or not. (Also, match Menu's layout, more or less).
* Menu: Minor layout tweaks to follow the KVP tweaks above. Fixes, among possibly other things, buttons in (non-FM) "List" menus overlapping the final entry (e.g., OPDS), and popout menus with a border being misaligned (e.g., Calibre, Find a file).
* CalendarView: Minor layout tweaks to follow the KVP tweaks. Ensures the pagination buttons are laid out in the same way as everywhere else (they used to be a wee bit higher).
2021-02-25 04:15:23 +00:00
|
|
|
buttons = input.buttons or {
|
2015-04-13 06:45:02 +00:00
|
|
|
{
|
|
|
|
{
|
2017-04-07 13:20:57 +00:00
|
|
|
text = input.cancel_text or _("Cancel"),
|
2015-04-13 06:45:02 +00:00
|
|
|
callback = function()
|
|
|
|
self:closeInputDialog()
|
|
|
|
end,
|
|
|
|
},
|
|
|
|
{
|
2017-04-07 13:20:57 +00:00
|
|
|
text = input.ok_text or _("OK"),
|
2016-05-26 06:09:49 +00:00
|
|
|
is_enter_default = true,
|
2015-04-13 06:45:02 +00:00
|
|
|
callback = function()
|
2021-04-10 22:08:29 +00:00
|
|
|
if input.deny_blank_input and self.input_dialog:getInputText() == "" then return end
|
2015-04-13 06:45:02 +00:00
|
|
|
input.callback(self.input_dialog:getInputText())
|
|
|
|
self:closeInputDialog()
|
|
|
|
end,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
UIManager:show(self.input_dialog)
|
2019-05-08 08:13:44 +00:00
|
|
|
self.input_dialog:onShowKeyboard(ignore_first_hold_release)
|
2015-04-13 06:45:02 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
function InputContainer:closeInputDialog()
|
|
|
|
UIManager:close(self.input_dialog)
|
|
|
|
end
|
|
|
|
|
2013-10-18 20:38:07 +00:00
|
|
|
return InputContainer
|