diff --git a/frontend/ui/widget/buttondialog.lua b/frontend/ui/widget/buttondialog.lua index d8925136f..bb92e93e2 100644 --- a/frontend/ui/widget/buttondialog.lua +++ b/frontend/ui/widget/buttondialog.lua @@ -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 diff --git a/frontend/ui/widget/buttontable.lua b/frontend/ui/widget/buttontable.lua index ef54de90a..96d59696a 100644 --- a/frontend/ui/widget/buttontable.lua +++ b/frontend/ui/widget/buttontable.lua @@ -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)