mirror of
https://github.com/koreader/koreader.git
synced 2025-12-24 12:14:05 +01:00
* FocusManager: Fix `focus_flags` check in `moveFocusTo` (0 is truthy in Lua, can't do AND checks like in C ;).) * FileManager+FileChooser: Pass our custom title bar directly to FileChooser (which also means we can now use FC's FocusManager layout directly). * FileChooser/Menu: Get rid of the weird `outer_title_bar` hack, and simply take a `custom_title_bar` pointer to an actual TitleBar instance instead. * FileManager/Menu/ListMenu/CoverMenu: Fix content height computations in `_recalculateDimen` (all the non-FM cases were including an old and now unused padding value, `self.header_padding`, leading to more blank space at the bottom than necessary, and, worse, leading to different item heights between FM views, possibly leading to unnecessary thumbnail scaling !) * ButtonDialog: Proper focus management when the ButtonTable is wrapped in a ScrollableContainer. * ConfigDialog: Implement a stupid workaround for a weird FocusManager issue when going back from `[⋮]` buttons. * ConfigDialog: Don't move the visual focus in `update` (i.e., we use `NOT_FOCUS` now that it works as intended). * DictQuickLookup: Ensures the `Menu` key bind does the exact same thing as the hamburger icon. * DictQuickLookup: Ensure we refocus after having mangled the FocusManager layout (prevents an old focus highlight from lingering on the wrong button). * FileChooser: Stop flagging it as no_title, because it is *never* without a title. (This behavior was a remnant of the previous FM-specific title bar hacks, which are no longer a thing). * FileChooser: Stop calling `mergeTitleBarIntoLayout` twice in `updateItems`. We already call Menu's, which handles it. (Prevents the title bar from being added twice to the FocusManager layout). * FocusManager: Relax the `Unfocus` checks in `moveFocusTo` to ensure we *always* unfocus something (if unfocusing was requested), even if we have to blast the whole widget tree to do so. This ensures callers that mangle self.layout can expect things to work after calling it regardless of how borked the current focus is. * FocusManager: Allow passing `focus_flags` to `refocusWidget`, so that it can be forwarded to the internal `moveFocusTo` call. * FocusManager: The above also allows us to enforce a default that ensures we do *not* send a Focus event on Touch devices, even if they have the hasDPad devcap. This essentially restores the previous/current behavior of not showing the visual feedback from such focus "events" sent programmatically, given the `focus_flags` check fix at the root of this PR ;). * InputDialog: Fix numerous issues relating to double/ghost instances of both InputText and VirtualKeyboard, ensuring we only ever have a single InputText & VK instance live. * InputDialog: Make sure every way we have of hiding the VK play nice together, especially when the `toggleKeyboard` button (shown w/ `add_nav_bar`) is at play. And doubly so when we're `fullscreen`, as hiding the VK implies resizing the widget. * InputText: Make sure we're flagged as in-focus when tapping inside the text field. * InputText: Make sure we don't attempt to show an already-visible VK in the custom `hasDPad` `onFocus` handler. * Menu: Get rid of an old and no longer used (nor meaningful) hack in `onFocus` about the initial/programmatically-sent Focus event. * Menu: Get rid of the unused `header_padding` field mentioned earlier in the FM/FC fixes. * Menu: Use `FOCUS_ONLY_ON_NT` in the explicit `moveFocusTo` call in `updatePageInfo`, so as to keep the current behavior of not showing the visual feedback of this focus on Touch devices. * Menu: Make sure *all* the `moveFocusTo` calls are gated behind the `hasDPad` devcap (previously, that was only the case for `updatePageInfo`, but not `mergeTitleBarIntoLayout` (which is called by `updateItems`). * MultiInputDialog: Actively get rid of the InputText & VK instances from the base class's constructor that we do not use. * MultiInputDialog: Ensure the FocusManager layout is *slightly* less broken (password fields can still be a bit weird, though). * TextViewer: Get rid of the unfocus -> layout mangling -> refocus hack now that `refocusWidget` handles this case sanely. * VirtualKeyboard: Notify our parent InputDialog when we get closed, so it can act accordingly (e.g., resize itself when `fullscreen`). * ScrollableContainer: Implement the necessary machinery for focus handling inside ButtonDialog (specifically, when scrolling via PgUp/PgDwn). * TextEditor: Given the above fixes, the plugin is no longer disabled on non-touch devices. * ReaderBookMark: Make sure we request a full refresh when closing the "Edit note" dialog, as CRe highlights may extend past its dimensions, and if it's closed separately from VK, the refresh would have been limited to its own dimensions, leaving a neat InputDialog-sized hole in the highlights ;).
249 lines
10 KiB
Lua
249 lines
10 KiB
Lua
local Blitbuffer = require("ffi/blitbuffer")
|
|
local Button = require("ui/widget/button")
|
|
local Device = require("device")
|
|
local FocusManager = require("ui/widget/focusmanager")
|
|
local HorizontalGroup = require("ui/widget/horizontalgroup")
|
|
local LineWidget = require("ui/widget/linewidget")
|
|
local Size = require("ui/size")
|
|
local VerticalGroup = require("ui/widget/verticalgroup")
|
|
local VerticalSpan = require("ui/widget/verticalspan")
|
|
local Geom = require("ui/geometry")
|
|
local Screen = Device.screen
|
|
|
|
local ButtonTable = FocusManager:extend{
|
|
width = nil,
|
|
-- If requested, allow ButtonTable to shrink itself if 'width' can
|
|
-- be reduced without any truncation or font size getting smaller.
|
|
shrink_unneeded_width = false,
|
|
-- But we won't go below this: buttons are tapable, we want some
|
|
-- minimal width so they are easy to tap (this is mostly needed
|
|
-- for CJK languages where button text can be one or two glyphs).
|
|
shrink_min_width = Screen:scaleBySize(100),
|
|
|
|
buttons = {
|
|
{
|
|
{text="OK", enabled=true, callback=nil},
|
|
{text="Cancel", enabled=false, callback=nil},
|
|
},
|
|
},
|
|
sep_width = Size.line.medium,
|
|
zero_sep = false,
|
|
}
|
|
|
|
function ButtonTable:init()
|
|
self.width = self.width or math.floor(math.min(Screen:getWidth(), Screen:getHeight()) * 0.9)
|
|
self.buttons_layout = {}
|
|
self.button_by_id = {}
|
|
self.container = VerticalGroup:new{ width = self.width }
|
|
self[1] = self.container
|
|
if self.zero_sep then
|
|
-- If we're asked to add a first line, don't add a vspan before: caller
|
|
-- must do its own padding before.
|
|
self:addVerticalSeparator()
|
|
end
|
|
local row_cnt = #self.buttons
|
|
local table_min_needed_width = -1
|
|
for i = 1, row_cnt do
|
|
self:addVerticalSpan()
|
|
local buttons_layout_line = {}
|
|
local horizontal_group = HorizontalGroup:new{}
|
|
local row = self.buttons[i]
|
|
local column_cnt = #row
|
|
local available_width = self.width - self.sep_width * (column_cnt - 1)
|
|
local unspecified_width_buttons = 0
|
|
for j = 1, column_cnt do
|
|
local btn_entry = row[j]
|
|
if btn_entry.width then
|
|
available_width = available_width - btn_entry.width
|
|
else
|
|
unspecified_width_buttons = unspecified_width_buttons + 1
|
|
end
|
|
end
|
|
local default_button_width = math.floor(available_width / unspecified_width_buttons)
|
|
local min_needed_button_width = -1
|
|
for j = 1, column_cnt do
|
|
local btn_entry = row[j]
|
|
local button = Button:new{
|
|
text = btn_entry.text,
|
|
text_func = btn_entry.text_func,
|
|
checked_func = btn_entry.checked_func,
|
|
lang = btn_entry.lang,
|
|
icon = btn_entry.icon,
|
|
icon_width = btn_entry.icon_width,
|
|
icon_height = btn_entry.icon_height,
|
|
align = btn_entry.align,
|
|
enabled = btn_entry.enabled,
|
|
enabled_func = btn_entry.enabled_func,
|
|
callback = function()
|
|
if self.show_parent and self.show_parent.movable then
|
|
self.show_parent.movable:resetEventState()
|
|
end
|
|
btn_entry.callback()
|
|
end,
|
|
hold_callback = btn_entry.hold_callback,
|
|
allow_hold_when_disabled = btn_entry.allow_hold_when_disabled,
|
|
vsync = btn_entry.vsync,
|
|
width = btn_entry.width or default_button_width,
|
|
height = btn_entry.height,
|
|
bordersize = 0,
|
|
margin = 0,
|
|
padding = Size.padding.buttontable, -- a bit taller than standalone buttons, for easier tap
|
|
padding_h = btn_entry.align == "left" and Size.padding.large or Size.padding.button,
|
|
-- allow text to take more of the horizontal space if centered
|
|
avoid_text_truncation = btn_entry.avoid_text_truncation,
|
|
text_font_face = btn_entry.font_face,
|
|
text_font_size = btn_entry.font_size,
|
|
text_font_bold = btn_entry.font_bold,
|
|
show_parent = self.show_parent,
|
|
}
|
|
if self.shrink_unneeded_width and not btn_entry.width and min_needed_button_width ~= false then
|
|
-- We gather the largest min width of all buttons without a specified width,
|
|
-- and will see how it does when this largest min width is applied to all
|
|
-- buttons (without a specified width): we still want to keep them the same
|
|
-- size and balanced.
|
|
local min_width = button:getMinNeededWidth()
|
|
if min_width then
|
|
if min_needed_button_width < min_width then
|
|
min_needed_button_width = min_width
|
|
end
|
|
else
|
|
-- If any one button in this row can't be made smaller, give up
|
|
min_needed_button_width = false
|
|
end
|
|
end
|
|
if btn_entry.id then
|
|
self.button_by_id[btn_entry.id] = button
|
|
end
|
|
local button_dim = button:getSize()
|
|
local vertical_sep = LineWidget:new{
|
|
background = btn_entry.no_vertical_sep and Blitbuffer.COLOR_WHITE or Blitbuffer.COLOR_GRAY,
|
|
dimen = Geom:new{
|
|
w = self.sep_width,
|
|
h = button_dim.h,
|
|
}
|
|
}
|
|
buttons_layout_line[j] = button
|
|
table.insert(horizontal_group, button)
|
|
if j < column_cnt then
|
|
table.insert(horizontal_group, vertical_sep)
|
|
end
|
|
end -- end for each button
|
|
table.insert(self.container, horizontal_group)
|
|
self:addVerticalSpan()
|
|
if i < row_cnt then
|
|
self:addVerticalSeparator()
|
|
end
|
|
if column_cnt > 0 then
|
|
-- Only add lines that are not separator to the focusmanager
|
|
table.insert(self.buttons_layout, buttons_layout_line)
|
|
end
|
|
if self.shrink_unneeded_width and table_min_needed_width ~= false then
|
|
if min_needed_button_width then
|
|
if min_needed_button_width >= 0 and min_needed_button_width < default_button_width then
|
|
local row_min_width = self.width - (default_button_width - min_needed_button_width)*unspecified_width_buttons
|
|
if table_min_needed_width < row_min_width then
|
|
table_min_needed_width = row_min_width
|
|
end
|
|
end
|
|
else
|
|
-- If any one row can't be made smaller, give up
|
|
table_min_needed_width = false
|
|
end
|
|
end
|
|
end -- end for each button line
|
|
if Device:hasDPad() then
|
|
self.layout = self.buttons_layout
|
|
self:refocusWidget()
|
|
else
|
|
self.key_events = {} -- deregister all key press event listeners
|
|
end
|
|
if self.shrink_unneeded_width and table_min_needed_width ~= false
|
|
and table_min_needed_width > 0 and table_min_needed_width < self.width then
|
|
self.width = table_min_needed_width > self.shrink_min_width and table_min_needed_width or self.shrink_min_width
|
|
self.shrink_unneeded_width = false
|
|
self:free()
|
|
self:init()
|
|
end
|
|
end
|
|
|
|
function ButtonTable:addVerticalSpan()
|
|
table.insert(self.container, VerticalSpan:new{
|
|
width = Size.span.vertical_default,
|
|
})
|
|
end
|
|
|
|
function ButtonTable:addVerticalSeparator(black_line)
|
|
table.insert(self.container, LineWidget:new{
|
|
background = black_line and Blitbuffer.COLOR_BLACK or Blitbuffer.COLOR_GRAY,
|
|
dimen = Geom:new{
|
|
w = self.width,
|
|
h = self.sep_width,
|
|
},
|
|
})
|
|
end
|
|
|
|
function ButtonTable:setupGridScrollBehaviour()
|
|
-- So that the last row get the same height as all others,
|
|
-- we add an invisible separator below it
|
|
self.container:resetLayout()
|
|
table.insert(self.container, VerticalSpan:new{
|
|
width = self.sep_width,
|
|
})
|
|
self.container:getSize() -- have it recompute its offsets and size
|
|
|
|
-- Generate self.step_scroll_grid (so that what we add next is not part of it)
|
|
self:getStepScrollGrid()
|
|
|
|
-- Insert 2 lines off-dimensions in VerticalGroup (that will show only when overflowing)
|
|
table.insert(self.container, 1, LineWidget:new{
|
|
background = Blitbuffer.COLOR_BLACK,
|
|
dimen = Geom:new{
|
|
w = self.width,
|
|
h = self.sep_width,
|
|
},
|
|
})
|
|
table.insert(self.container._offsets, 1, { x=self.width, y=-self.sep_width })
|
|
table.insert(self.container, LineWidget:new{
|
|
background = Blitbuffer.COLOR_BLACK,
|
|
dimen = Geom:new{
|
|
w = self.width,
|
|
h = self.sep_width,
|
|
},
|
|
})
|
|
table.insert(self.container._offsets, { x=self.width, y=self.container._size.h + self.sep_width })
|
|
end
|
|
|
|
|
|
function ButtonTable:getStepScrollGrid()
|
|
if not self.step_scroll_grid then
|
|
local step_rows = {}
|
|
local offsets = self.container._offsets
|
|
local idx = self.zero_sep and 2 or 1
|
|
local row_num = 1
|
|
while idx <= #self.container do
|
|
local row = {
|
|
row_num = row_num, -- (used by ScrollableContainer, and generally helpful for debugging)
|
|
top = offsets[idx].y, -- top of our vspan above text
|
|
content_top = offsets[idx+1].y, -- top of our text widget
|
|
content_bottom = offsets[idx+2].y - 1, -- bottom of our text widget
|
|
bottom = idx+4 <= #self.container and offsets[idx+4].y -1 or self.container:getSize().h -1
|
|
-- bottom of our vspan + separator below text
|
|
-- To implement when needed:
|
|
-- columns = { array of similar info about each button in that row's HorizontalGroup }
|
|
-- Its absence means free scrolling on the x-axis (if scroll ends up being needed)
|
|
}
|
|
table.insert(step_rows, row)
|
|
idx = idx + 4
|
|
row_num = row_num + 1
|
|
end
|
|
self.step_scroll_grid = step_rows
|
|
end
|
|
return self.step_scroll_grid
|
|
end
|
|
|
|
function ButtonTable:getButtonById(id)
|
|
return self.button_by_id[id] -- nil if not found
|
|
end
|
|
|
|
return ButtonTable
|