Skip to content

Commit 33b7143

Browse files
[ReaderKeySelection] NT: add indicator overlay (koreader#15547)
1 parent 0557ada commit 33b7143

3 files changed

Lines changed: 180 additions & 27 deletions

File tree

frontend/apps/reader/modules/readerhighlight.lua

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1895,6 +1895,10 @@ function ReaderHighlight:onHoldPan(_, ges)
18951895
if self.ui.paging and self.selected_text then
18961896
self.view.highlight.temp[self.hold_pos.page] = self.selected_text.sboxes
18971897
end
1898+
-- Ensure indicator overlay does not restore stale background over updated highlights.
1899+
if self.ui.keyselection:isActive() then
1900+
self.ui.keyselection:clearOverlay()
1901+
end
18981902
UIManager:setDirty(self.dialog, "ui")
18991903
end
19001904

frontend/apps/reader/modules/readerkeyselection.lua

Lines changed: 176 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
local BD = require("ui/bidi")
2+
local Blitbuffer = require("ffi/blitbuffer")
23
local Device = require("device")
34
local DoubleSpinWidget = require("ui/widget/doublespinwidget")
45
local Geom = require("ui/geometry")
@@ -22,6 +23,128 @@ local math_max = math.max
2223
local math_min = math.min
2324
local math_floor = math.floor
2425

26+
-- Minimal overlay that draws the crosshairs without touching the document
27+
-- render layer. Captures the page as its background on first paint, then
28+
-- restores only the previous indicator region before drawing the new one.
29+
local IndicatorOverlay = InputContainer:extend{
30+
parent_ui = nil,
31+
handleEvent = true,
32+
indicator_rect = nil,
33+
_prev_rect = nil,
34+
_saved_bb = nil,
35+
covers_fullscreen = false,
36+
}
37+
38+
local function getIndicatorSaveRect(rect, max_w, max_h)
39+
if not rect then return nil end
40+
local save_r = rect:copy()
41+
local bt2 = math_floor(Size.border.thick / 2)
42+
save_r.x = math_floor(save_r.x - bt2)
43+
save_r.y = math_floor(save_r.y - bt2)
44+
save_r.w = save_r.w + Size.border.thick * 2
45+
save_r.h = save_r.h + Size.border.thick * 2
46+
save_r = save_r:intersect(Geom:new{ x = 0, y = 0, w = max_w, h = max_h })
47+
if not save_r or save_r.w <= 0 or save_r.h <= 0 then
48+
return nil
49+
end
50+
return save_r
51+
end
52+
53+
local function getIndicatorDirtyRect(old_rect, new_rect, max_w, max_h)
54+
local old_save = getIndicatorSaveRect(old_rect, max_w, max_h)
55+
local new_save = getIndicatorSaveRect(new_rect, max_w, max_h)
56+
if old_save and new_save then
57+
return Geom.boundingBox({ old_save, new_save })
58+
end
59+
return old_save or new_save
60+
end
61+
62+
function IndicatorOverlay:freeSavedBB()
63+
if self._saved_bb then
64+
self._saved_bb:free()
65+
self._saved_bb = nil
66+
end
67+
self._prev_rect = nil
68+
end
69+
70+
function IndicatorOverlay:handleEvent(event)
71+
if not event or not event.handler then return false end
72+
-- Only forward input events that would otherwise be swallowed by us, being
73+
-- the topmost layer. Broadcast events reach parent_ui directly via UIManager.
74+
local input_events = {
75+
onKeyPress = true,
76+
onKeyRepeat = true,
77+
onKeyRelease = true,
78+
onGesture = true,
79+
onPan = true, -- mouse wheel pan via sendEvent
80+
}
81+
if input_events[event.handler] and self.parent_ui then
82+
return self.parent_ui:handleEvent(event)
83+
end
84+
return false
85+
end
86+
87+
function IndicatorOverlay:drawCrosshairs(bb, rect)
88+
if not rect then return end
89+
bb:invertRect(
90+
rect.x,
91+
math.floor(rect.y + rect.h / 2 - Size.border.thick / 2),
92+
rect.w,
93+
Size.border.thick
94+
)
95+
bb:invertRect(
96+
math.floor(rect.x + rect.w / 2 - Size.border.thick / 2),
97+
rect.y,
98+
Size.border.thick,
99+
rect.h
100+
)
101+
end
102+
103+
function IndicatorOverlay:getSize()
104+
return self.dimen or Screen:getSize()
105+
end
106+
107+
function IndicatorOverlay:paintTo(bb, x, y, is_dirty)
108+
-- If is_dirty is nil, the parent ReaderUI painted over us with new content.
109+
-- The background we saved is now invalid, so we clear it.
110+
if is_dirty == nil then
111+
self:freeSavedBB()
112+
end
113+
114+
-- Restore previous unblemished page background
115+
if self._prev_rect and self._saved_bb then
116+
local r = self._prev_rect
117+
bb:blitFrom(self._saved_bb, r.x, r.y, 0, 0, r.w, r.h)
118+
end
119+
120+
if self.indicator_rect then
121+
local r = self.indicator_rect
122+
local save_r = getIndicatorSaveRect(r, bb:getWidth(), bb:getHeight())
123+
if save_r and self.dimen then
124+
save_r = save_r:intersect(self.dimen)
125+
end
126+
if not save_r or save_r.w <= 0 or save_r.h <= 0 then
127+
self._prev_rect = nil
128+
return
129+
end
130+
131+
-- Resize the saved Blitbuffer if necessary
132+
if not self._saved_bb or self._saved_bb:getWidth() < save_r.w or self._saved_bb:getHeight() < save_r.h then
133+
self:freeSavedBB()
134+
self._saved_bb = Blitbuffer.new(save_r.w, save_r.h, bb:getType())
135+
end
136+
137+
-- Copy clean background from screen
138+
self._saved_bb:blitFrom(bb, 0, 0, save_r.x, save_r.y, save_r.w, save_r.h)
139+
140+
-- Draw the crosshair natively overriding the background
141+
self:drawCrosshairs(bb, self.indicator_rect)
142+
self._prev_rect = save_r
143+
else
144+
self._prev_rect = nil
145+
end
146+
end
147+
25148
local ReaderKeySelection = InputContainer:extend{}
26149

27150
function ReaderKeySelection:init()
@@ -48,6 +171,11 @@ end
48171

49172
function ReaderKeySelection:onSetDimensions(dimen)
50173
self.screen_w, self.screen_h = dimen.w, dimen.h
174+
if self._indicator_overlay then
175+
local overlay_rect = getIndicatorSaveRect(self._current_indicator_pos, dimen.w, dimen.h)
176+
self._indicator_overlay.dimen = overlay_rect or Geom:new{ x = 0, y = 0, w = dimen.w, h = dimen.h }
177+
self._indicator_overlay:freeSavedBB()
178+
end
51179
end
52180

53181
function ReaderKeySelection:registerKeyEvents()
@@ -229,6 +357,11 @@ function ReaderKeySelection:isActive()
229357
return self._current_indicator_pos ~= nil
230358
end
231359

360+
function ReaderKeySelection:clearOverlay()
361+
if not self._indicator_overlay then return end
362+
self._indicator_overlay:freeSavedBB()
363+
end
364+
232365
function ReaderKeySelection:startHighlightIndicator()
233366
-- disable long-press icon (poke-ball), as it is triggered constantly due to NT devices needing a workaround for text selection to work.
234367
self.ui.highlight.long_hold_reached_action = function() end
@@ -243,10 +376,24 @@ function ReaderKeySelection:startHighlightIndicator()
243376
rect.h = rect.w
244377
end
245378
self._current_indicator_pos = rect
379+
380+
-- Compute padded saved region (match paintTo padding)
381+
local max_w = self.screen_w or Screen:getWidth()
382+
local max_h = self.screen_h or Screen:getHeight()
383+
local save_r = getIndicatorSaveRect(rect, max_w, max_h)
384+
-- Fallback to minimal rect if intersection collapsed
385+
if not save_r then
386+
save_r = Geom:new{ x = math.floor(rect.x), y = math.floor(rect.y), w = rect.w, h = rect.h }
387+
end
388+
self._indicator_overlay = IndicatorOverlay:new{
389+
dimen = Geom:new{ x = save_r.x, y = save_r.y, w = save_r.w, h = save_r.h },
390+
parent_ui = self.ui,
391+
}
392+
UIManager:show(self._indicator_overlay)
246393
if self.ui.paging then
247394
self._last_indicator_move_args = {dx = 0, dy = 0, distance = 0, time = time:now()}
248-
self.view.highlight.indicator = rect
249-
UIManager:setDirty(self.dialog, "ui", rect)
395+
self._indicator_overlay.indicator_rect = rect
396+
UIManager:setDirty(self._indicator_overlay, "ui", rect)
250397
return true
251398
end
252399
local center_x = rect.x + rect.w * 0.5
@@ -285,6 +432,11 @@ function ReaderKeySelection:stopHighlightIndicator(need_clear_selection)
285432
self._edge_dx, self._edge_dy = nil, nil
286433
self._last_move_was_quick_move = nil
287434
self._previous_indicator_word = nil
435+
if self._indicator_overlay then
436+
self._indicator_overlay:freeSavedBB()
437+
UIManager:close(self._indicator_overlay)
438+
self._indicator_overlay = nil
439+
end
288440
self._last_indicator_move_args = nil
289441
UIManager:setDirty(self.dialog, "ui", rect)
290442
if need_clear_selection then
@@ -421,6 +573,14 @@ function ReaderKeySelection:pageTurnDuringSelection()
421573
self._edge_dx, self._edge_dy = nil, nil
422574
self._previous_indicator_word = nil
423575
local last_pos = self._current_indicator_pos
576+
if self._indicator_overlay then
577+
local old_dirty = getIndicatorSaveRect(last_pos, self.screen_w, self.screen_h)
578+
self._indicator_overlay:freeSavedBB()
579+
self._indicator_overlay.indicator_rect = nil
580+
if old_dirty then
581+
UIManager:setDirty(self.dialog, "ui", old_dirty)
582+
end
583+
end
424584
local target_x = last_pos.x + last_pos.w * 0.5
425585
local target_y = last_pos.y + last_pos.h * 0.5
426586

@@ -577,10 +737,21 @@ function ReaderKeySelection:_setIndicatorToWord(word)
577737
end
578738

579739
function ReaderKeySelection:_setIndicatorRect(rect)
580-
UIManager:setDirty(self.dialog, "fast", self._current_indicator_pos)
740+
local old_rect = self._current_indicator_pos
581741
self._current_indicator_pos = rect
582-
self.view.highlight.indicator = self._current_indicator_pos
583-
UIManager:setDirty(self.dialog, "fast", self._current_indicator_pos)
742+
if not self._indicator_overlay then
743+
logger.warn("ReaderKeySelection: _setIndicatorRect: no overlay")
744+
return
745+
end
746+
logger.dbg("ReaderKeySelection: _setIndicatorRect: dirtying overlay, rect=", rect)
747+
self._indicator_overlay.indicator_rect = rect
748+
local dirty = getIndicatorDirtyRect(old_rect, rect, self.screen_w, self.screen_h)
749+
if dirty then
750+
self._indicator_overlay.dimen = dirty
751+
UIManager:setDirty(self._indicator_overlay, "fast", dirty)
752+
else
753+
UIManager:setDirty(self._indicator_overlay, "fast", rect)
754+
end
584755
end
585756

586757
function ReaderKeySelection:_getWordAnchorCoordinates(word)

frontend/apps/reader/modules/readerview.lua

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,6 @@ function ReaderView:init()
100100
saved_drawer = "lighten",
101101
-- NOTE: Unfortunately, yellow tends to look like absolute ass on Kaleido panels...
102102
saved_color = Screen:isColorEnabled() and "yellow" or "gray",
103-
indicator = nil, -- geom: non-touch highlight position indicator: {x = 50, y=50}
104103
}
105104
self.page_states = {}
106105
self.page_gap = {
@@ -244,10 +243,6 @@ function ReaderView:paintTo(bb, x, y)
244243
if self.highlight.temp and next(self.highlight.temp) then
245244
self:drawTempHighlight(bb, x, y)
246245
end
247-
-- draw highlight position indicator for non-touch
248-
if self.highlight.indicator then
249-
self:drawHighlightIndicator(bb, x, y)
250-
end
251246
-- paint dogear
252247
if self.dogear_visible then
253248
self.dogear:paintTo(bb, x, y)
@@ -507,23 +502,6 @@ function ReaderView:drawScrollView(bb, x, y)
507502
self.state.pos)
508503
end
509504

510-
function ReaderView:drawHighlightIndicator(bb, x, y)
511-
local rect = self.highlight.indicator
512-
-- paint big cross line +
513-
bb:paintRect(
514-
rect.x,
515-
rect.y + rect.h / 2 - Size.border.thick / 2,
516-
rect.w,
517-
Size.border.thick
518-
)
519-
bb:paintRect(
520-
rect.x + rect.w / 2 - Size.border.thick / 2,
521-
rect.y,
522-
Size.border.thick,
523-
rect.h
524-
)
525-
end
526-
527505
function ReaderView:drawTempHighlight(bb, x, y)
528506
local color = self.highlight.saved_drawer ~= "invert"
529507
and G_reader_settings:isTrue("highlight_selection_use_highlight_color")

0 commit comments

Comments
 (0)