Non-touch: highlight support (#8877)

readerhighlight: non-touch support
focusmanager: fix same type container share same selected field
radiobuttonwidget: non touch support
sortwidget: non touch support
openwithdialog: fix layout contains textinput, checkboxes added to layout twice
reviewable/pr8892/r1
Philip Chan 2 years ago committed by GitHub
parent a6d6ba3606
commit 4f849c23ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -13,6 +13,7 @@ local UIManager = require("ui/uimanager")
local dbg = require("dbg")
local logger = require("logger")
local util = require("util")
local Size = require("ui/size")
local ffiUtil = require("ffi/util")
local _ = require("gettext")
local C_ = _.pgettext
@ -47,6 +48,28 @@ end
function ReaderHighlight:init()
self.select_mode = false -- extended highlighting
self._start_indicator_highlight = false
self._current_indicator_pos = nil
self._previous_indicator_pos = nil
if Device:hasDPad() then
-- Used for text selection with dpad/keys
local QUICK_INDICTOR_MOVE = true
self.key_events.StopHighlightIndicator = { {Device.input.group.Back}, doc = "Stop non-touch highlight", args = true } -- true: clear highlight selection
self.key_events.UpHighlightIndicator = { {"Up"}, doc = "move indicator up", event = "MoveHighlightIndicator", args = {0, -1} }
self.key_events.DownHighlightIndicator = { {"Down"}, doc = "move indicator down", event = "MoveHighlightIndicator", args = {0, 1} }
-- let FewKeys device can move indicator left
self.key_events.LeftHighlightIndicator = { {"Left"}, doc = "move indicator left", event = "MoveHighlightIndicator", args = {-1, 0} }
self.key_events.RightHighlightIndicator = { {"Right"}, doc = "move indicator right", event = "MoveHighlightIndicator", args = {1, 0} }
self.key_events.HighlightPress = { {"Press"}, doc = "highlight start or end" }
if Device:hasKeys() then
self.key_events.QuicklyUpHighlightIndicator = { {"Shift", "Up"}, doc = "quick move indicator up", event = "MoveHighlightIndicator", args = {0, -1, QUICK_INDICTOR_MOVE} }
self.key_events.QuicklyDownHighlightIndicator = { {"Shift", "Down"}, doc = "quick move indicator down", event = "MoveHighlightIndicator", args = {0, 1, QUICK_INDICTOR_MOVE} }
self.key_events.QuicklyLeftHighlightIndicator = { {"Shift", "Left"}, doc = "quick move indicator left", event = "MoveHighlightIndicator", args = {-1, 0, QUICK_INDICTOR_MOVE} }
self.key_events.QuicklyRightHighlightIndicator = { {"Shift", "Right"}, doc = "quick move indicator right", event = "MoveHighlightIndicator", args = {1, 0, QUICK_INDICTOR_MOVE} }
self.key_events.StartHighlightIndicator = { {"H"}, doc = "start non-touch highlight" }
end
end
self._highlight_buttons = {
-- highlight and add_note are for the document itself,
@ -295,6 +318,14 @@ local long_press_action = {
function ReaderHighlight:addToMainMenu(menu_items)
-- insert table to main reader menu
if Device:hasDPad() then
menu_items.start_content_selection = {
text = _("Start content selection"),
callback = function()
self:onStartHighlightIndicator()
end,
}
end
menu_items.highlight_options = {
text = _("Highlight style"),
sub_item_table = {},
@ -361,14 +392,35 @@ function ReaderHighlight:addToMainMenu(menu_items)
end
menu_items.translation_settings = Translator:genSettingsMenu()
if not Device:isTouchDevice() then
-- Menu items below aren't needed.
return
end
menu_items.long_press = {
text = _("Long-press on text"),
sub_item_table = {
{
text = _("Highlight long-press interval"),
keep_menu_open = true,
callback = function()
local SpinWidget = require("ui/widget/spinwidget")
local items = SpinWidget:new{
title_text = _("Highlight long-press interval"),
info_text = _([[
If a touch is not released in this interval, it is considered a long-press. On document text, single word selection will not be triggered.
The interval value is in seconds and can range from 3 to 20 seconds.]]),
width = math.floor(Screen:getWidth() * 0.75),
value = G_reader_settings:readSetting("highlight_long_hold_threshold", 3),
value_min = 3,
value_max = 20,
value_step = 1,
value_hold_step = 5,
ok_text = _("Set interval"),
default_value = 3,
callback = function(spin)
G_reader_settings:saveSetting("highlight_long_hold_threshold", spin.value)
end
}
UIManager:show(items)
end,
},
{
text = _("Dictionary on single word selection"),
checked_func = function()
@ -397,6 +449,12 @@ function ReaderHighlight:addToMainMenu(menu_items)
end,
})
end
-- long_press menu is under taps_and_gestures menu which is not available for non touch device
-- Clone long_press menu and change label making much meaning for non touch devices
if Device:hasDPad() then
menu_items.selection_text = util.tableDeepCopy(menu_items.long_press)
menu_items.selection_text.text = _("Select on text")
end
end
function ReaderHighlight:genPanelZoomMenu()
@ -907,6 +965,7 @@ function ReaderHighlight:onHold(arg, ges)
fullscreen = true,
}
UIManager:show(imgviewer)
self:onStopHighlightIndicator()
return true
end
@ -1365,7 +1424,8 @@ function ReaderHighlight:onHoldRelease()
local long_final_hold = false
if self.hold_last_tv then
local hold_duration = TimeVal:now() - self.hold_last_tv
if hold_duration > TimeVal:new{ sec = 3, usec = 0 } then
local long_hold_threshold = G_reader_settings:readSetting("highlight_long_hold_threshold", 3)
if hold_duration > TimeVal:new{ sec = long_hold_threshold, usec = 0 } then
-- We stayed 3 seconds before release without updating selection
long_final_hold = true
end
@ -1806,4 +1866,125 @@ function ReaderHighlight:onClose()
self:clear()
end
function ReaderHighlight:onHighlightPress()
if self._current_indicator_pos then
if not self._start_indicator_highlight then
-- try a tap at current indicator position to open any existing highlight
if not self:onTap(nil, self:_createHighlightGesture("tap")) then
-- no existing highlight at current indicator position: start hold
self._start_indicator_highlight = true
self:onHold(nil, self:_createHighlightGesture("hold"))
if self.selected_text and self.selected_text.sboxes and #self.selected_text.sboxes then
local pos = self.selected_text.sboxes[1]
-- set hold_pos to center of selected_test to make center selection more stable, not jitted at edge
self.hold_pos = self.view:screenToPageTransform({
x = pos.x + pos.w / 2,
y = pos.y + pos.h / 2
})
-- move indicator to center selected text making succeed same row selection much accurate.
UIManager:setDirty(self.dialog, "ui", self._current_indicator_pos)
self._current_indicator_pos.x = pos.x + pos.w / 2 - self._current_indicator_pos.w / 2
self._current_indicator_pos.y = pos.y + pos.h / 2 - self._current_indicator_pos.h / 2
UIManager:setDirty(self.dialog, "ui", self._current_indicator_pos)
end
else
self:onStopHighlightIndicator(true) -- need_clear_selection=true
end
else
self:onHoldRelease(nil, self:_createHighlightGesture("hold_release"))
self:onStopHighlightIndicator()
end
return true
end
return false
end
function ReaderHighlight:onStartHighlightIndicator()
if self.view.visible_area and not self._current_indicator_pos then
-- set start position to centor of page
local rect = self._previous_indicator_pos
if not rect then
rect = Geom:new()
rect.x = self.view.visible_area.w / 2
rect.y = self.view.visible_area.h / 2
rect.w = Size.item.height_default
rect.h = rect.w
end
self._current_indicator_pos = rect
self.view.highlight.indicator = rect
UIManager:setDirty(self.dialog, "ui", rect)
return true
end
return false
end
function ReaderHighlight:onStopHighlightIndicator(need_clear_selection)
if self._current_indicator_pos then
local rect = self._current_indicator_pos
self._previous_indicator_pos = rect
self._start_indicator_highlight = false
self._current_indicator_pos = nil
self.view.highlight.indicator = nil
UIManager:setDirty(self.dialog, "ui", rect)
if need_clear_selection then
self:clear()
end
return true
end
return false
end
function ReaderHighlight:onMoveHighlightIndicator(args)
if self.view.visible_area and self._current_indicator_pos then
local dx, dy, quick_move = unpack(args)
local step_distance = self.view.visible_area.w / 5 -- quick move distance: fifth of visible_area
local y_step_distance = self.view.visible_area.h / 5
if step_distance > y_step_distance then
-- take the smaller, make all direction move distance much predictable
step_distance = y_step_distance
end
if not quick_move then
step_distance = step_distance / 4 -- twentieth of visible_area
end
local rect = self._current_indicator_pos:copy()
rect.x = rect.x + step_distance * dx
rect.y = rect.y + step_distance * dy
if rect.x < 0 then
rect.x = 0
end
if rect.x + rect.w > self.view.visible_area.w then
rect.x = self.view.visible_area.w - rect.w
end
if rect.y < 0 then
rect.y = 0
end
if rect.y + rect.h > self.view.visible_area.h then
rect.y = self.view.visible_area.h - rect.h
end
UIManager:setDirty(self.dialog, "ui", self._current_indicator_pos)
self._current_indicator_pos = rect
self.view.highlight.indicator = rect
UIManager:setDirty(self.dialog, "ui", rect)
if self._start_indicator_highlight then
self:onHoldPan(nil, self:_createHighlightGesture("hold_pan"))
end
return true
end
return false
end
function ReaderHighlight:_createHighlightGesture(gesture)
local point = self._current_indicator_pos:copy()
point.x = point.x + point.w / 2
point.y = point.y + point.h / 2
point.w = 0
point.h = 0
return {
ges = gesture,
pos = point,
time = TimeVal:realtime(),
}
end
return ReaderHighlight

@ -19,6 +19,7 @@ local UIManager = require("ui/uimanager")
local dbg = require("dbg")
local logger = require("logger")
local optionsutil = require("ui/data/optionsutil")
local Size = require("ui/size")
local _ = require("gettext")
local Screen = Device.screen
local T = require("ffi/util").template
@ -44,6 +45,7 @@ local ReaderView = OverlapGroup:extend{
temp = {},
saved_drawer = "lighten",
saved = {},
indicator = nil, -- geom: non-touch highlight position indicator: {x = 50, y=50}
},
highlight_visible = true,
-- PDF/DjVu continuous paging
@ -199,6 +201,10 @@ function ReaderView:paintTo(bb, x, y)
if self.highlight.temp then
self:drawTempHighlight(bb, x, y)
end
-- draw highlight position indicator for non-touch
if self.highlight.indicator then
self:drawHighlightIndicator(bb, x, y)
end
-- paint dogear
if self.dogear_visible then
self.dogear:paintTo(bb, x, y)
@ -454,6 +460,23 @@ function ReaderView:drawScrollView(bb, x, y)
self.state.pos)
end
function ReaderView:drawHighlightIndicator(bb, x, y)
local rect = self.highlight.indicator
-- paint big cross line +
bb:paintRect(
rect.x,
rect.y + rect.h / 2 - Size.border.thick / 2,
rect.w,
Size.border.thick
)
bb:paintRect(
rect.x + rect.w / 2 - Size.border.thick / 2,
rect.y,
Size.border.thick,
rect.h
)
end
function ReaderView:drawTempHighlight(bb, x, y)
for page, boxes in pairs(self.highlight.temp) do
for i = 1, #boxes do

@ -55,8 +55,10 @@ local order = {
"speed_reading_module_perception_expander",
"----------------------------",
"highlight_options",
"selection_text", -- if Device:hasDPad()
"panel_zoom_options",
"djvu_render_mode",
"start_content_selection", -- if Device:hasDPad(), put this as last one so it is easy to select with "press" and "up" keys
},
setting = {
-- common settings

@ -33,11 +33,6 @@ local FocusManager = InputContainer:new{
}
function FocusManager:init()
if not self.selected then
self.selected = { x = 1, y = 1 }
else
self.selected = self.selected -- make sure current FocusManager has its own selected field
end
if Device:hasDPad() then
local event_keys = {}
-- these will all generate the same event, just with different arguments
@ -97,6 +92,23 @@ function FocusManager:init()
end
end
function FocusManager:_init()
InputContainer._init(self)
-- Make sure each FocusManager instance has its own selection field.
-- Take ButtonTable = FocusManager:new{} for example.
-- FocusManager:init method called once and all ButtonTable instances share same selected field.
-- It has problem when
-- 1. ButtonTable A (layout 1 row, 4 columns) shown, and move focus, make selected to (4, 1)
-- 2. ButtonTable A closed and ButtonTable B (layout 2 rows, 2 columns) shown
-- 3. selected (4, 1) is invalid(overflow) for ButtonTable B, and FocusManager ignore all focus move events.
if not self.selected then
self.selected = { x = 1, y = 1 }
else
self.selected = {x = self.selected.x, y = self.selected.y }
end
end
function FocusManager:isAlternativeKey(key)
for _, seq in pairs(self.extra_key_events) do
for _, oneseq in ipairs(seq) do
@ -219,7 +231,7 @@ function FocusManager:onFocusMove(args)
self.selected.y = self.selected.y + dy
self.selected.x = self.selected.x + dx
end
logger.dbg("Cursor position : ".. self.selected.y .." : "..self.selected.x)
logger.dbg("FocusManager cursor position is:", self.selected.x, ",", self.selected.y)
if self.layout[self.selected.y][self.selected.x] ~= current_item
or not self.layout[self.selected.y][self.selected.x].is_inactive then
@ -257,7 +269,7 @@ function FocusManager:moveFocusTo(x, y, focus_flags)
target_item = self.layout[y][x]
end
if target_item then
logger.dbg("Move focus position to: " .. y .. ", " .. x)
logger.dbg("FocusManager: Move focus position to:", y, ",", x)
self.selected.x = x
self.selected.y = y
-- widget create new layout on update, previous may be removed from new layout.
@ -351,7 +363,7 @@ function FocusManager:_sendGestureEventToFocusedWidget(gesture)
point.y = point.y + point.h / 2
point.w = 0
point.h = 0
logger.dbg("FocusManager: Send " .. gesture .. " to " .. point.x .. ", " .. point.y)
logger.dbg("FocusManager: Send", gesture, "to", point.x , ",", point.y)
UIManager:sendEvent(Event:new("Gesture", {
ges = gesture,
pos = point,

@ -23,7 +23,6 @@ local OpenWithDialog = InputDialog:extend{}
function OpenWithDialog:init()
-- init title and buttons in base class
InputDialog.init(self)
self.element_width = math.floor(self.width * 0.9)
self.radio_button_table = RadioButtonTable:new{
@ -42,6 +41,7 @@ function OpenWithDialog:init()
end
end
}
self.layout = {self.layout[#self.layout]} -- keep bottom buttons
self:mergeLayoutInVertical(self.radio_button_table, #self.layout) -- before bottom buttons
self._input_widget = self.radio_button_table
@ -86,13 +86,11 @@ function OpenWithDialog:init()
text = _("Always use this engine for this file"),
parent = self,
}
table.insert(self.layout, #self.layout, {self._check_file_button}) -- before bottom buttons
self:addWidget(self._check_file_button)
self._check_global_button = self._check_global_button or CheckButton:new{
text = _("Always use this engine for file type"),
parent = self,
}
table.insert(self.layout, #self.layout, {self._check_global_button}) -- before bottom buttons
self:addWidget(self._check_global_button)
self.dialog_frame = FrameContainer:new{

@ -3,10 +3,10 @@ local ButtonTable = require("ui/widget/buttontable")
local CenterContainer = require("ui/widget/container/centercontainer")
local Device = require("device")
local FrameContainer = require("ui/widget/container/framecontainer")
local FocusManager = require("ui/widget/focusmanager")
local Geom = require("ui/geometry")
local GestureRange = require("ui/gesturerange")
local HorizontalGroup = require("ui/widget/horizontalgroup")
local InputContainer = require("ui/widget/container/inputcontainer")
local MovableContainer = require("ui/widget/container/movablecontainer")
local RadioButtonTable = require("ui/widget/radiobuttontable")
local Size = require("ui/size")
@ -17,7 +17,7 @@ local WidgetContainer = require("ui/widget/container/widgetcontainer")
local _ = require("gettext")
local Screen = Device.screen
local RadioButtonWidget = InputContainer:new{
local RadioButtonWidget = FocusManager:new{
title_text = "",
info_text = nil,
width = nil,
@ -49,27 +49,24 @@ function RadioButtonWidget:init()
self.width = math.floor(math.min(self.screen_width, self.screen_height) * self.width_factor)
end
if Device:hasKeys() then
self.key_events = {
Close = { {Device.input.group.Back}, doc = "close widget" }
}
self.key_events.Close = { {Device.input.group.Back}, doc = "close widget" }
end
if Device:isTouchDevice() then
self.ges_events = {
TapClose = {
GestureRange:new{
ges = "tap",
range = Geom:new{
w = self.screen_width,
h = self.screen_height,
}
},
self.ges_events = {
TapClose = {
GestureRange:new{
ges = "tap",
range = Geom:new{
w = self.screen_width,
h = self.screen_height,
}
},
}
end
},
}
self:update()
end
function RadioButtonWidget:update()
self.layout = {}
if self.default_provider then
local row, col = self:getButtonIndex(self.default_provider)
self.radio_buttons[row][col].text = self.radio_buttons[row][col].text .. "\u{A0}\u{A0}"
@ -83,6 +80,7 @@ function RadioButtonWidget:update()
parent = self,
face = self.face,
}
self:mergeLayoutInVertical(value_widget)
local value_group = HorizontalGroup:new{
align = "center",
value_widget,
@ -149,7 +147,7 @@ function RadioButtonWidget:update()
zero_sep = true,
show_parent = self,
}
self:mergeLayoutInVertical(ok_cancel_buttons)
local vgroup = VerticalGroup:new{
align = "left",
title_bar,

@ -6,6 +6,7 @@ local CenterContainer = require("ui/widget/container/centercontainer")
local CheckMark = require("ui/widget/checkmark")
local Device = require("device")
local Font = require("ui/font")
local FocusManager = require("ui/widget/focusmanager")
local FrameContainer = require("ui/widget/container/framecontainer")
local Geom = require("ui/geometry")
local GestureRange = require("ui/gesturerange")
@ -34,20 +35,18 @@ local SortItemWidget = InputContainer:new{
function SortItemWidget:init()
self.dimen = Geom:new{w = self.width, h = self.height}
if Device:isTouchDevice() then
self.ges_events.Tap = {
GestureRange:new{
ges = "tap",
range = self.dimen,
}
self.ges_events.Tap = {
GestureRange:new{
ges = "tap",
range = self.dimen,
}
self.ges_events.Hold = {
GestureRange:new{
ges = "hold",
range = self.dimen,
}
}
self.ges_events.Hold = {
GestureRange:new{
ges = "hold",
range = self.dimen,
}
end
}
local item_checkable = false
local item_checked = self.item.checked
@ -69,6 +68,8 @@ function SortItemWidget:init()
self[1] = FrameContainer:new{
padding = 0,
bordersize = 0,
focusable = true,
focus_border_size = Size.border.thin,
LeftContainer:new{ -- needed only for auto UI mirroring
dimen = Geom:new{
w = self.width,
@ -113,7 +114,7 @@ function SortItemWidget:onHold()
return true
end
local SortWidget = InputContainer:new{
local SortWidget = FocusManager:new{
title = "",
width = nil,
height = nil,
@ -125,6 +126,7 @@ local SortWidget = InputContainer:new{
}
function SortWidget:init()
self.layout = {}
-- no item is selected on start
self.marked = 0
self.orig_item_table = nil
@ -134,11 +136,9 @@ function SortWidget:init()
h = self.height or Screen:getHeight(),
}
if Device:hasKeys() then
self.key_events = {
--don't get locked in on non touch devices
AnyKeyPressed = { { Device.input.group.Any },
seqtext = "any key", doc = "close dialog" }
}
self.key_events.Close = { { Device.input.group.Back }, doc = "close dialog" }
self.key_events.NextPage = { { Device.input.group.PgFwd}, doc = "next page"}
self.key_events.PrevPage = { { Device.input.group.PgBack}, doc = "prev page"}
end
if Device:isTouchDevice() then
self.ges_events.Swipe = {
@ -257,6 +257,10 @@ function SortWidget:init()
self.footer_last_down,
self.footer_ok,
}
table.insert(self.layout, {
self.footer_cancel,
self.footer_ok,
})
local bottom_line = LineWidget:new{
dimen = Geom:new{ w = self.item_width, h = Size.line.thick },
background = Blitbuffer.COLOR_DARK_GRAY,
@ -364,6 +368,7 @@ end
-- make sure self.item_margin and self.item_height are set before calling this
function SortWidget:_populateItems()
self.main_content:clear()
self.layout = { self.layout[#self.layout] } -- keep footer
local idx_offset = (self.show_page - 1) * self.items_per_page
local page_last
if idx_offset + self.items_per_page <= #self.item_table then
@ -377,16 +382,18 @@ function SortWidget:_populateItems()
if idx == self.marked then
invert_status = true
end
local item = SortItemWidget:new{
height = self.item_height,
width = self.item_width,
item = self.item_table[idx],
invert = invert_status,
index = idx,
show_parent = self,
}
table.insert(self.layout, #self.layout, {item})
table.insert(
self.main_content,
SortItemWidget:new{
height = self.item_height,
width = self.item_width,
item = self.item_table[idx],
invert = invert_status,
index = idx,
show_parent = self,
}
item
)
end
self.footer_page:setText(T(_("Page %1 of %2"), self.show_page, self.pages), self.footer_center_width)
@ -420,10 +427,6 @@ function SortWidget:_populateItems()
end)
end
function SortWidget:onAnyKeyPressed()
return self:onClose()
end
function SortWidget:onNextPage()
self:nextPage()
return true

Loading…
Cancel
Save