diff --git a/frontend/apps/filemanager/filemanager.lua b/frontend/apps/filemanager/filemanager.lua index bb60d04df..0c57ae380 100644 --- a/frontend/apps/filemanager/filemanager.lua +++ b/frontend/apps/filemanager/filemanager.lua @@ -148,6 +148,7 @@ function FileManager:init() is_popout = false, is_borderless = true, has_close_button = true, + perpage = G_reader_settings:readSetting("items_per_page"), file_filter = function(filename) if DocumentRegistry:getProvider(filename) then return true diff --git a/frontend/apps/filemanager/filemanagerfilesearcher.lua b/frontend/apps/filemanager/filemanagerfilesearcher.lua index c2d099966..6be83617f 100644 --- a/frontend/apps/filemanager/filemanagerfilesearcher.lua +++ b/frontend/apps/filemanager/filemanagerfilesearcher.lua @@ -152,6 +152,7 @@ function FileSearcher:showSearchResults() show_parent = menu_container, onMenuHold = self.onMenuHold, cface = Font:getFace("smallinfofont"), + perpage = G_reader_settings:readSetting("items_per_page") or 14, _manager = self, } table.insert(menu_container, self.search_menu) diff --git a/frontend/apps/filemanager/filemanagermenu.lua b/frontend/apps/filemanager/filemanagermenu.lua index b4b90a9f6..baf7e5164 100644 --- a/frontend/apps/filemanager/filemanagermenu.lua +++ b/frontend/apps/filemanager/filemanagermenu.lua @@ -91,6 +91,26 @@ function FileManagerMenu:setUpdateItemTable() checked_func = function() return self.ui.file_chooser.show_hidden end, callback = function() self.ui:toggleHiddenFiles() end } + self.menu_items.items_per_page = { + text = _("Items per page"), + callback = function() + local SpinWidget = require("ui/widget/spinwidget") + local curr_items = G_reader_settings:readSetting("items_per_page") or 14 + local items = SpinWidget:new{ + width = Screen:getWidth() * 0.6, + value = curr_items, + value_min = 6, + value_max = 24, + ok_text = _("Set items"), + title_text = _("Items per page"), + callback = function(spin) + G_reader_settings:saveSetting("items_per_page", spin.value) + self.ui:onRefresh() + end + } + UIManager:show(items) + end + } self.menu_items.sort_by = self.ui:getSortingMenuTable() self.menu_items.reverse_sorting = { text = _("Reverse sorting"), diff --git a/frontend/apps/filemanager/filemanagersetdefaults.lua b/frontend/apps/filemanager/filemanagersetdefaults.lua index 3cce9c966..d668a5424 100644 --- a/frontend/apps/filemanager/filemanagersetdefaults.lua +++ b/frontend/apps/filemanager/filemanagersetdefaults.lua @@ -91,6 +91,7 @@ function SetDefaults:init() width = Screen:getWidth()-15, height = Screen:getHeight()-15, cface = Font:getFace("smallinfofont"), + perpage = G_reader_settings:readSetting("items_per_page") or 14, show_parent = menu_container, _manager = self, } diff --git a/frontend/apps/reader/modules/readerbookmark.lua b/frontend/apps/reader/modules/readerbookmark.lua index b0e03146b..abbe4df56 100644 --- a/frontend/apps/reader/modules/readerbookmark.lua +++ b/frontend/apps/reader/modules/readerbookmark.lua @@ -218,6 +218,8 @@ function ReaderBookmark:onShowBookmark() width = Screen:getWidth(), height = Screen:getHeight(), cface = Font:getFace("x_smallinfofont"), + perpage = G_reader_settings:readSetting("items_per_page") or 14, + line_color = require("ffi/blitbuffer").COLOR_WHITE, on_close_ges = { GestureRange:new{ ges = "two_finger_swipe", diff --git a/frontend/apps/reader/modules/readertoc.lua b/frontend/apps/reader/modules/readertoc.lua index b5bd89d5a..dc6c6baf3 100644 --- a/frontend/apps/reader/modules/readertoc.lua +++ b/frontend/apps/reader/modules/readertoc.lua @@ -317,6 +317,9 @@ function ReaderToc:onShowToc() width = Screen:getWidth(), height = Screen:getHeight(), cface = Font:getFace("x_smallinfofont"), + single_line = true, + perpage = G_reader_settings:readSetting("items_per_page") or 14, + line_color = require("ffi/blitbuffer").COLOR_WHITE, on_close_ges = { GestureRange:new{ ges = "two_finger_swipe", diff --git a/frontend/ui/elements/filemanager_menu_order.lua b/frontend/ui/elements/filemanager_menu_order.lua index 707f1f666..ba1cfe111 100644 --- a/frontend/ui/elements/filemanager_menu_order.lua +++ b/frontend/ui/elements/filemanager_menu_order.lua @@ -8,6 +8,7 @@ local order = { setting = { "filemanager_display_mode", "show_hidden_files", + "items_per_page", "----------------------------", "sort_by", "reverse_sorting", diff --git a/frontend/ui/widget/filechooser.lua b/frontend/ui/widget/filechooser.lua index 479923527..3ac67c6b5 100644 --- a/frontend/ui/widget/filechooser.lua +++ b/frontend/ui/widget/filechooser.lua @@ -34,6 +34,7 @@ local FileChooser = Menu:extend{ collate = "strcoll", -- or collate = "access", reverse_collate = false, path_items = {}, -- store last browsed location(item index) for each path + perpage = G_reader_settings:readSetting("items_per_page"), } function FileChooser:init() diff --git a/frontend/ui/widget/menu.lua b/frontend/ui/widget/menu.lua index 689a95c8f..d65c2a289 100644 --- a/frontend/ui/widget/menu.lua +++ b/frontend/ui/widget/menu.lua @@ -21,6 +21,7 @@ local OverlapGroup = require("ui/widget/overlapgroup") local RenderText = require("ui/rendertext") local RightContainer = require("ui/widget/container/rightcontainer") local Size = require("ui/size") +local TextBoxWidget = require("ui/widget/textboxwidget") local TextWidget = require("ui/widget/textwidget") local UIManager = require("ui/uimanager") local UnderlineContainer = require("ui/widget/container/underlinecontainer") @@ -129,10 +130,15 @@ local MenuItem = InputContainer:new{ detail = nil, face = Font:getFace("cfont", 30), info_face = Font:getFace("infont", 15), + font = "cfont", + font_size = 24, + infont = "infont", + infont_size = 18, dimen = nil, shortcut = nil, shortcut_style = "square", _underline_container = nil, + linesize = Size.line.medium, } function MenuItem:init() @@ -179,38 +185,114 @@ function MenuItem:init() text_ellipsis_mandatory_padding = Size.span.horizontal_small end local mandatory = self.mandatory and ""..self.mandatory or "" - local mandatory_w = RenderText:sizeUtf8Text(0, self.dimen.w, self.info_face, - ""..mandatory, true, self.bold).x local state_button_width = self.state_size.w or 0 - local my_text = self.text and ""..self.text or "" - local w = RenderText:sizeUtf8Text(0, self.dimen.w, self.face, my_text, true, self.bold).x - if w + mandatory_w + state_button_width + text_mandatory_padding >= self.content_width then - if Device:hasKeyboard() then - self.active_key_events.ShowItemDetail = { - {"Right"}, doc = "show item detail" - } - end - local indicator = "\226\128\166 " -- an ellipsis - local indicator_w = RenderText:sizeUtf8Text(0, self.dimen.w, self.face, - indicator, true, self.bold).x - self.text = RenderText:getSubTextByWidth(my_text, self.face, - self.content_width - indicator_w - mandatory_w - state_button_width - text_ellipsis_mandatory_padding, - true, self.bold) .. indicator - end - local state_button = self.state or HorizontalSpan:new{ width = state_button_width, } local state_indent = self.state and self.state.indent or "" + local item_name + local mandatory_widget + + if self.single_line then -- items only in single line + self.info_face = Font:getFace(self.infont, self.infont_size) + self.face = Font:getFace(self.font, self.font_size) + + local mandatory_w = RenderText:sizeUtf8Text(0, self.dimen.w, self.info_face, ""..mandatory, true, self.bold).x + + local my_text = self.text and ""..self.text or "" + local w = RenderText:sizeUtf8Text(0, self.dimen.w, self.face, my_text, true, self.bold).x + if w + mandatory_w + state_button_width + text_mandatory_padding >= self.content_width then + if Device:hasKeyboard() then + self.active_key_events.ShowItemDetail = { + {"Right"}, doc = "show item detail" + } + end + local indicator = "\226\128\166 " -- an ellipsis + local indicator_w = RenderText:sizeUtf8Text(0, self.dimen.w, self.face, + indicator, true, self.bold).x + self.text = RenderText:getSubTextByWidth(my_text, self.face, + self.content_width - indicator_w - mandatory_w - state_button_width - text_ellipsis_mandatory_padding, + true, self.bold) .. indicator + end + + item_name = TextWidget:new{ + text = self.text, + face = self.face, + bold = self.bold, + fgcolor = self.dim and Blitbuffer.COLOR_GREY or nil, + } + mandatory_widget = TextWidget:new{ + text = mandatory, + face = self.info_face, + bold = self.bold, + fgcolor = self.dim and Blitbuffer.COLOR_GREY or nil, + } + else + while true do + -- Free previously made widgets to avoid memory leaks + if mandatory_widget then + mandatory_widget:free() + end + mandatory_widget = TextWidget:new { + text = mandatory, + face = Font:getFace(self.infont, self.infont_size), + bold = self.bold, + fgcolor = self.dim and Blitbuffer.COLOR_GREY or nil, + } + local height = mandatory_widget:getSize().h + + + if height < self.dimen.h - 2 * self.linesize then -- we fit ! + break + end + -- Don't go too low + if self.infont_size < 12 then + break; + else + -- If we don't fit, decrease font size + self.infont_size = self.infont_size - 1 + end + end + self.info_face = Font:getFace(self.infont, self.infont_size) + + local mandatory_w = RenderText:sizeUtf8Text(0, self.dimen.w, self.info_face, "" .. mandatory, true, self.bold).x + while true do + -- Free previously made widgets to avoid memory leaks + if item_name then + item_name:free() + end + item_name = TextBoxWidget:new { + text = self.text, + face = Font:getFace(self.font, self.font_size), + width = self.content_width - mandatory_w - state_button_width - text_mandatory_padding, + alignment = "left", + bold = self.bold, + fgcolor = self.dim and Blitbuffer.COLOR_GREY or nil, + } + local height = item_name:getSize().h + if height < self.dimen.h - 2 * self.linesize then -- we fit ! + break + end + -- Don't go too low, and then truncate text + if self.font_size < 12 then + self.text = self.text:sub(1, -5) .. "…" + else + -- If we don't fit, decrease font size + self.font_size = self.font_size - 2 + end + end + self.face = Font:getFace(self.font, self.font_size) + end + local state_container = LeftContainer:new{ dimen = Geom:new{w = self.content_width/2, h = self.dimen.h}, HorizontalGroup:new{ HorizontalSpan:new{ width = RenderText:sizeUtf8Text(0, self.dimen.w, self.face, - state_indent, true, self.bold).x, + state_indent, true, self.bold).x, }, - state_button + state_button, } } local text_container = LeftContainer:new{ @@ -219,27 +301,20 @@ function MenuItem:init() HorizontalSpan:new{ width = self.state_size.w, }, - TextWidget:new{ - text = self.text, - face = self.face, - bold = self.bold, - fgcolor = self.dim and Blitbuffer.COLOR_GREY or nil, - } + item_name, } } local mandatory_container = RightContainer:new{ dimen = Geom:new{w = self.content_width, h = self.dimen.h}, - TextWidget:new{ - text = mandatory, - face = self.info_face, - bold = self.bold, - fgcolor = self.dim and Blitbuffer.COLOR_GREY or nil, - } + mandatory_widget, } self._underline_container = UnderlineContainer:new{ + color = self.line_color, + linesize = self.linesize, vertical_align = "center", + padding = 0, dimen = Geom:new{ w = self.content_width, h = self.dimen.h @@ -282,7 +357,7 @@ function MenuItem:onFocus() end function MenuItem:onUnfocus() - self._underline_container.color = Blitbuffer.COLOR_WHITE + self._underline_container.color = self.line_color self.key_events = {} return true end @@ -394,22 +469,37 @@ local Menu = FocusManager:new{ has_close_button = true, -- close_callback is a function, which is executed when menu is closed -- it is usually set by the widget which creates the menu - close_callback = nil + close_callback = nil, + linesize = Size.line.medium, + perpage = G_reader_settings:readSetting("items_per_page") or 14, + line_color = Blitbuffer.COLOR_GREY, } function Menu:_recalculateDimen() + self.perpage = G_reader_settings:readSetting("items_per_page") or 14 + self.span_width = 0 self.dimen.w = self.width + self.dimen.h = self.height + if self.dimen.h > Screen:getHeight() or self.dimen.h == nil then + self.dimen.h = Screen:getHeight() + end self.item_dimen = Geom:new{ w = self.dimen.w, - h = Screen:scaleBySize(46), -- hardcoded for now + h = Screen:scaleBySize(46), } - -- if height not given, dynamically calculate it - self.dimen.h = self.height or (#self.item_table + 2) * self.item_dimen.h - if self.dimen.h > Screen:getHeight() then - self.dimen.h = Screen:getHeight() + local height_dim + local bottom_height = 0 + local top_height = 0 + if self.page_return_arrow and self.page_info_text then + bottom_height = math.max(self.page_return_arrow:getSize().h, self.page_info_text:getSize().h) + + 2 * Size.padding.button end - -- header and footer should approximately take up space of 2 items - self.perpage = math.floor(self.dimen.h / self.item_dimen.h) - (self.no_title and 1 or 2) + if self.menu_title and not self.no_title then + top_height = self.menu_title:getSize().h + 2 * Size.padding.small + end + height_dim = self.dimen.h - bottom_height - top_height + self.item_dimen.h = math.floor(height_dim / self.perpage) + self.span_width = math.floor((height_dim - (self.perpage * (self.item_dimen.h ))) / 2 -1 ) self.page_num = math.ceil(#self.item_table / self.perpage) -- fix current page if out of range if self.page_num > 0 and self.page > self.page_num then self.page = self.page_num end @@ -418,7 +508,11 @@ end function Menu:init() self.show_parent = self.show_parent or self self.item_table_stack = {} - self:_recalculateDimen() + self.dimen.w = self.width + self.dimen.h = self.height + if self.dimen.h > Screen:getHeight() or self.dimen.h == nil then + self.dimen.h = Screen:getHeight() + end self.page = 1 ----------------------------------- @@ -534,29 +628,30 @@ function Menu:init() } } - local content + self:_recalculateDimen() + self.vertical_span = HorizontalGroup:new{ + VerticalSpan:new{ width = self.span_width } + } if self.no_title then - content = OverlapGroup:new{ - dimen = self.dimen:copy(), - VerticalGroup:new{ - align = "left", - body, - }, - page_return, - footer, + self.content_group = VerticalGroup:new{ + align = "left", + self.vertical_span, + body, } else - content = OverlapGroup:new{ - dimen = self.dimen:copy(), - VerticalGroup:new{ - align = "left", - header, - body, - }, - page_return, - footer, + self.content_group = VerticalGroup:new{ + align = "left", + header, + self.vertical_span, + body, } end + local content = OverlapGroup:new{ + dimen = self.dimen:copy(), + self.content_group, + page_return, + footer, + } self[1] = FrameContainer:new{ background = Blitbuffer.COLOR_WHITE, @@ -566,7 +661,6 @@ function Menu:init() radius = self.is_popout and math.floor(self.dimen.w/20) or 0, content } - ------------------------------------------ -- start to set up input event callback -- ------------------------------------------ @@ -655,12 +749,18 @@ function Menu:updateItems(select_number) self.item_group:clear() self.page_info:resetLayout() self.return_button:resetLayout() + self.vertical_span:clear() + self.content_group:resetLayout() self:_recalculateDimen() -- default to select the first item if not select_number then select_number = 1 end + --font size between 12 and 18 for better matching + local infont_size = math.floor(18 - (self.perpage - 6) / 3) + --font size between 14 and 24 for better matching + local font_size = math.floor(24 - ((self.perpage - 6)/ 18) * 10 ) for c = 1, math.min(self.perpage, #self.item_table) do -- calculate index in item_table @@ -688,12 +788,18 @@ function Menu:updateItems(select_number) mandatory = self.item_table[i].mandatory, bold = self.item_table.current == i or self.item_table[i].bold == true, dim = self.item_table[i].dim, - face = self.cface, + font = "smallinfofont", + font_size = font_size, + infont = "infont", + infont_size = infont_size, dimen = self.item_dimen:new(), shortcut = item_shortcut, shortcut_style = shortcut_style, table = self.item_table[i], menu = self, + linesize = self.linesize, + single_line = self.single_line, + line_color = self.line_color, } table.insert(self.item_group, item_tmp) -- this is for focus manager diff --git a/frontend/ui/widget/spinwidget.lua b/frontend/ui/widget/spinwidget.lua new file mode 100644 index 000000000..428132c8e --- /dev/null +++ b/frontend/ui/widget/spinwidget.lua @@ -0,0 +1,201 @@ +local Blitbuffer = require("ffi/blitbuffer") +local ButtonTable = require("ui/widget/buttontable") +local CenterContainer = require("ui/widget/container/centercontainer") +local CloseButton = require("ui/widget/closebutton") +local Device = require("device") +local FrameContainer = require("ui/widget/container/framecontainer") +local Geom = require("ui/geometry") +local GestureRange = require("ui/gesturerange") +local Font = require("ui/font") +local HorizontalGroup = require("ui/widget/horizontalgroup") +local InputContainer = require("ui/widget/container/inputcontainer") +local LineWidget = require("ui/widget/linewidget") +local OverlapGroup = require("ui/widget/overlapgroup") +local NumberPickerWidget = require("ui/widget/numberpickerwidget") +local Size = require("ui/size") +local TextWidget = require("ui/widget/textwidget") +local UIManager = require("ui/uimanager") +local VerticalGroup = require("ui/widget/verticalgroup") +local WidgetContainer = require("ui/widget/container/widgetcontainer") +local _ = require("gettext") +local Screen = Device.screen + +local SpinWidget = InputContainer:new{ + title_face = Font:getFace("x_smalltfont"), + width = Screen:getWidth() * 0.95, + height = Screen:getHeight(), + value = 1, + value_max = 20, + value_min = 0, + ok_text = _("OK"), + cancel_text = _("Cancel"), +} + +function SpinWidget:init() + self.medium_font_face = Font:getFace("ffont") + self.light_bar = {} + self.screen_width = Screen:getWidth() + self.screen_height = Screen:getHeight() + if Device:hasKeys() then + self.key_events = { + Close = { {"Back"}, doc = "close spin widget" } + } + end + if Device:isTouchDevice() then + self.ges_events = { + TapClose = { + GestureRange:new{ + ges = "tap", + range = Geom:new{ + w = self.screen_width, + h = self.screen_height, + } + }, + }, + } + end + self:update() +end + +function SpinWidget:update() + local value_widget = NumberPickerWidget:new{ + show_parent = self, + width = self.screen_width * 0.2, + value = self.value, + value_min = self.value_min, + value_max = self.value_max, + value_step = 1, + value_hold_step = 4, + } + local value_group = HorizontalGroup:new{ + align = "center", + value_widget, + } + + local value_title = FrameContainer:new{ + padding = Size.padding.default, + margin = Size.margin.title, + bordersize = 0, + TextWidget:new{ + text = self.title_text, + face = self.title_face, + bold = true, + width = self.width, + }, + } + local value_line = LineWidget:new{ + dimen = Geom:new{ + w = self.width, + h = Size.line.thick, + } + } + local value_bar = OverlapGroup:new{ + dimen = { + w = self.width, + h = value_title:getSize().h + }, + value_title, + CloseButton:new{ window = self, padding_top = Size.margin.title, }, + } + local buttons = { + { + { + text = self.cancel_text, + callback = function() + self:onClose() + end, + }, + { + text = self.ok_text, + callback = function() + if self.callback then + self.value = value_widget:getValue() + self:callback(self) + end + self:onClose() + end, + }, + } + } + + local ok_cancel_buttons = ButtonTable:new{ + width = self.width - 2*Size.padding.default, + buttons = buttons, + zero_sep = true, + show_parent = self, + } + + self.spin_frame = FrameContainer:new{ + radius = Size.radius.window, + padding = 0, + margin = 0, + background = Blitbuffer.COLOR_WHITE, + VerticalGroup:new{ + align = "left", + value_bar, + value_line, + CenterContainer:new{ + dimen = Geom:new{ + w = self.width, + h = value_group:getSize().h + self.screen_height * 0.1, + }, + value_group + }, + CenterContainer:new{ + dimen = Geom:new{ + w = self.width, + h = ok_cancel_buttons:getSize().h, + }, + ok_cancel_buttons + } + } + } + self[1] = WidgetContainer:new{ + align = "center", + dimen =Geom:new{ + x = 0, y = 0, + w = self.screen_width, + h = self.screen_height, + }, + FrameContainer:new{ + bordersize = 0, + self.spin_frame, + } + } + UIManager:setDirty(self, function() + return "ui", self.spin_frame.dimen + end) +end + +function SpinWidget:onCloseWidget() + UIManager:setDirty(nil, function() + return "partial", self.spin_frame.dimen + end) + return true +end + +function SpinWidget:onShow() + UIManager:setDirty(self, function() + return "ui", self.spin_frame.dimen + end) + return true +end + +function SpinWidget:onAnyKeyPressed() + UIManager:close(self) + return true +end + +function SpinWidget:onTapClose(arg, ges_ev) + if ges_ev.pos:notIntersectWith(self.spin_frame.dimen) then + self:onClose() + end + return true +end + +function SpinWidget:onClose() + UIManager:close(self) + return true +end + +return SpinWidget