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
pull/10440/head
poire-z 1 year ago
parent 7a95d11f07
commit 8c8a032269

@ -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)

Loading…
Cancel
Save