mirror of
https://github.com/koreader/koreader.git
synced 2025-08-10 00:52:38 +00:00
ButtonDialog: allow for step/page scrolling
With tall ButtonDialog with many rows, allows for a more natural and readable scrolling without any truncated row (like in Excel, we previously behave as web browsers). ButtonTable: - make the span and separator layout more explicite - add some small horizontal padding when button text is centered
This commit is contained in:
@@ -48,8 +48,11 @@ local Geom = require("ui/geometry")
|
||||
local GestureRange = require("ui/gesturerange")
|
||||
local InputContainer = require("ui/widget/container/inputcontainer")
|
||||
local MovableContainer = require("ui/widget/container/movablecontainer")
|
||||
local ScrollableContainer = require("ui/widget/container/scrollablecontainer")
|
||||
local Size = require("ui/size")
|
||||
local UIManager = require("ui/uimanager")
|
||||
local VerticalGroup = require("ui/widget/verticalgroup")
|
||||
local VerticalSpan = require("ui/widget/verticalspan")
|
||||
local _ = require("gettext")
|
||||
local Screen = require("device").screen
|
||||
|
||||
@@ -61,6 +64,9 @@ local ButtonDialog = InputContainer:extend{
|
||||
shrink_min_width = nil, -- default to ButtonTable's default
|
||||
tap_close_callback = nil,
|
||||
alpha = nil, -- passed to MovableContainer
|
||||
-- If scrolling, prefers using this/these numbers of buttons rows per page
|
||||
-- (depending on what the screen height allows) to compute the height.
|
||||
rows_per_page = nil, -- number or array of numbers
|
||||
}
|
||||
|
||||
function ButtonDialog:init()
|
||||
@@ -86,17 +92,65 @@ function ButtonDialog:init()
|
||||
}
|
||||
}
|
||||
end
|
||||
self.buttontable = ButtonTable:new{
|
||||
buttons = self.buttons,
|
||||
width = self.width - 2*Size.border.window - 2*Size.padding.button,
|
||||
shrink_unneeded_width = self.shrink_unneeded_width,
|
||||
shrink_min_width = self.shrink_min_width,
|
||||
show_parent = self,
|
||||
}
|
||||
-- If the ButtonTable ends up being taller than the screen, wrap it inside a ScrollableContainer.
|
||||
-- Ensure some small top and bottom padding, so the scrollbar stand out, and some outer margin
|
||||
-- so the this dialog does not take the full height and stand as a popup.
|
||||
local max_height = Screen:getHeight() - 2*Size.padding.buttontable - 2*Size.margin.default
|
||||
local height = self.buttontable:getSize().h
|
||||
local scontainer
|
||||
if height > max_height then
|
||||
-- Adjust the ScrollableContainer to an integer multiple of the row height
|
||||
-- (assuming all rows get the same height), so when scrolling per page,
|
||||
-- we always end up seeing full rows.
|
||||
self.buttontable:setupGridScrollBehaviour()
|
||||
local step_scroll_grid = self.buttontable:getStepScrollGrid()
|
||||
local row_height = step_scroll_grid[1].bottom + 1 - step_scroll_grid[1].top
|
||||
local fit_rows = math.floor(max_height / row_height)
|
||||
if self.rows_per_page then
|
||||
if type(self.rows_per_page) == "number" then
|
||||
if fit_rows > self.rows_per_page then
|
||||
fit_rows = self.rows_per_page
|
||||
end
|
||||
else
|
||||
for _, nb in ipairs(self.rows_per_page) do
|
||||
if fit_rows >= nb then
|
||||
fit_rows = nb
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
-- (Comment the next line to test ScrollableContainer behaviour when things do not fit)
|
||||
max_height = row_height * fit_rows
|
||||
self.cropping_widget = ScrollableContainer:new{
|
||||
dimen = Geom:new{
|
||||
-- We'll be exceeding the provided width in this case (let's not bother
|
||||
-- ensuring it, we'd need to re-setup the ButtonTable...)
|
||||
w = self.buttontable:getSize().w + ScrollableContainer:getScrollbarWidth(),
|
||||
h = max_height,
|
||||
},
|
||||
show_parent = self,
|
||||
step_scroll_grid = step_scroll_grid,
|
||||
self.buttontable,
|
||||
}
|
||||
scontainer = VerticalGroup:new{
|
||||
VerticalSpan:new{ width=Size.padding.buttontable },
|
||||
self.cropping_widget,
|
||||
VerticalSpan:new{ width=Size.padding.buttontable },
|
||||
}
|
||||
end
|
||||
self.movable = MovableContainer:new{
|
||||
alpha = self.alpha,
|
||||
anchor = self.anchor,
|
||||
FrameContainer:new{
|
||||
ButtonTable:new{
|
||||
buttons = self.buttons,
|
||||
width = self.width - 2*Size.border.window - 2*Size.padding.button,
|
||||
shrink_unneeded_width = self.shrink_unneeded_width,
|
||||
shrink_min_width = self.shrink_min_width,
|
||||
show_parent = self,
|
||||
},
|
||||
scontainer or self.buttontable,
|
||||
background = Blitbuffer.COLOR_WHITE,
|
||||
bordersize = Size.border.window,
|
||||
radius = Size.radius.window,
|
||||
@@ -114,6 +168,22 @@ function ButtonDialog:init()
|
||||
}
|
||||
end
|
||||
|
||||
function ButtonDialog:getButtonById(id)
|
||||
return self.buttontable:getButtonById(id)
|
||||
end
|
||||
|
||||
function ButtonDialog:getScrolledOffset()
|
||||
if self.cropping_widget then
|
||||
return self.cropping_widget:getScrolledOffset()
|
||||
end
|
||||
end
|
||||
|
||||
function ButtonDialog:setScrolledOffset(offset_point)
|
||||
if offset_point and self.cropping_widget then
|
||||
return self.cropping_widget:setScrolledOffset(offset_point)
|
||||
end
|
||||
end
|
||||
|
||||
function ButtonDialog:onShow()
|
||||
UIManager:setDirty(self, function()
|
||||
return "ui", self.movable.dimen
|
||||
|
||||
@@ -39,14 +39,12 @@ function ButtonTable:init()
|
||||
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.
|
||||
-- Things look better when the first line is gray like the others.
|
||||
self:addHorizontalSep(false, true, true)
|
||||
else
|
||||
self:addHorizontalSep(false, false, true)
|
||||
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]
|
||||
@@ -68,6 +66,7 @@ function ButtonTable:init()
|
||||
local button = Button:new{
|
||||
text = btn_entry.text,
|
||||
text_func = btn_entry.text_func,
|
||||
lang = btn_entry.lang,
|
||||
icon = btn_entry.icon,
|
||||
icon_width = btn_entry.icon_width,
|
||||
icon_height = btn_entry.icon_height,
|
||||
@@ -87,7 +86,7 @@ function ButtonTable:init()
|
||||
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 0,
|
||||
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,
|
||||
@@ -128,8 +127,9 @@ function ButtonTable:init()
|
||||
end
|
||||
end -- end for each button
|
||||
table.insert(self.container, horizontal_group)
|
||||
self:addVerticalSpan()
|
||||
if i < row_cnt then
|
||||
self:addHorizontalSep(true, true, true)
|
||||
self:addVerticalSeparator()
|
||||
end
|
||||
if column_cnt > 0 then
|
||||
-- Only add lines that are not separator to the focusmanager
|
||||
@@ -149,7 +149,6 @@ function ButtonTable:init()
|
||||
end
|
||||
end
|
||||
end -- end for each button line
|
||||
self:addHorizontalSep(true, false, false)
|
||||
if Device:hasDPad() then
|
||||
self.layout = self.buttons_layout
|
||||
self:refocusWidget()
|
||||
@@ -165,24 +164,79 @@ function ButtonTable:init()
|
||||
end
|
||||
end
|
||||
|
||||
function ButtonTable:addHorizontalSep(vspan_before, add_line, vspan_after, black_line)
|
||||
if vspan_before then
|
||||
table.insert(self.container,
|
||||
VerticalSpan:new{ width = Size.span.vertical_default })
|
||||
end
|
||||
if add_line then
|
||||
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,
|
||||
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, -- (not used, but may help with 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)
|
||||
}
|
||||
})
|
||||
end
|
||||
if vspan_after then
|
||||
table.insert(self.container,
|
||||
VerticalSpan:new{ width = Size.span.vertical_default })
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user