--[[-- Widget that presents a multi-page to show key value pairs. Example: local Foo = KeyValuePage:new{ title = "Statistics", kv_pairs = { {"Current period", "00:00:00"}, -- single or more "-" will generate a solid line "----------------------------", {"Page to read", "5"}, {"Time to read", "00:01:00"}, {"Press me", "will invoke the callback", callback = function() print("hello") end }, }, } UIManager:show(Foo) ]] local InputContainer = require("ui/widget/container/inputcontainer") local FrameContainer = require("ui/widget/container/framecontainer") local VerticalGroup = require("ui/widget/verticalgroup") local VerticalSpan = require("ui/widget/verticalspan") local OverlapGroup = require("ui/widget/overlapgroup") local LeftContainer = require("ui/widget/container/leftcontainer") local RightContainer = require("ui/widget/container/rightcontainer") local LineWidget = require("ui/widget/linewidget") local Blitbuffer = require("ffi/blitbuffer") local CloseButton = require("ui/widget/closebutton") local UIManager = require("ui/uimanager") local TextWidget = require("ui/widget/textwidget") local GestureRange = require("ui/gesturerange") local RenderText = require("ui/rendertext") local Geom = require("ui/geometry") local Font = require("ui/font") local Device = require("device") local Screen = Device.screen local ellipsis, space = "…", " " local ellipsis_width, space_width local function truncateTextByWidth(text, face, max_width, prepend_space) if not ellipsis_width then ellipsis_width = RenderText:sizeUtf8Text(0, max_width, face, ellipsis).x end if not space_width then space_width = RenderText:sizeUtf8Text(0, max_width, face, space).x end local new_txt_width = max_width - ellipsis_width - space_width local sub_txt = RenderText:getSubTextByWidth(text, face, new_txt_width) if prepend_space then return space.. sub_txt .. ellipsis else return sub_txt .. ellipsis .. space end end local KeyValueTitle = VerticalGroup:new{ kv_page = nil, title = "", tface = Font:getFace("tfont"), align = "left", } function KeyValueTitle:init() self.close_button = CloseButton:new{ window = self } local btn_width = self.close_button:getSize().w local title_txt_width = RenderText:sizeUtf8Text( 0, self.width, self.tface, self.title).x local show_title_txt if self.width < (title_txt_width + btn_width) then show_title_txt = truncateTextByWidth( self.title, self.tface, self.width-btn_width) else show_title_txt = self.title end -- title and close button table.insert(self, OverlapGroup:new{ dimen = { w = self.width }, TextWidget:new{ text = show_title_txt, face = self.tface, }, self.close_button, }) -- page count and separation line self.page_cnt = FrameContainer:new{ padding = 4, margin = 0, bordersize = 0, background = Blitbuffer.COLOR_WHITE, -- overlap offset x will be updated in setPageCount method overlap_offset = {0, -15}, TextWidget:new{ text = "", -- page count fgcolor = Blitbuffer.COLOR_GREY, face = Font:getFace("ffont", 16), }, } self.title_bottom = OverlapGroup:new{ dimen = { w = self.width, h = Screen:scaleBySize(2) }, LineWidget:new{ dimen = Geom:new{ w = self.width, h = Screen:scaleBySize(2) }, background = Blitbuffer.COLOR_GREY, style = "solid", }, self.page_cnt, } table.insert(self, self.title_bottom) table.insert(self, VerticalSpan:new{ width = Screen:scaleBySize(5) }) end function KeyValueTitle:setPageCount(curr, total) if total == 1 then -- remove page count if there is only one page table.remove(self.title_bottom, 2) return end self.page_cnt[1]:setText(curr .. "/" .. total) self.page_cnt.overlap_offset[1] = (self.width - self.page_cnt:getSize().w - 10) self.title_bottom[2] = self.page_cnt end function KeyValueTitle:onClose() self.kv_page:onClose() return true end local KeyValueItem = InputContainer:new{ key = nil, value = nil, cface = Font:getFace("cfont"), width = nil, height = nil, } function KeyValueItem:init() self.dimen = Geom:new{w = self.width, h = self.height} if self.callback and Device:isTouchDevice() then self.ges_events.Tap = { GestureRange:new{ ges = "tap", range = self.dimen, } } end local key_w = RenderText:sizeUtf8Text(0, self.width, self.cface, self.key).x local value_w = RenderText:sizeUtf8Text(0, self.width, self.cface, self.value).x if key_w + value_w > self.width then -- truncate key or value so they fits in one row if key_w >= value_w then self.show_key = truncateTextByWidth(self.key, self.cface, self.width-value_w) self.show_value = self.value else self.show_value = truncateTextByWidth(self.value, self.cface, self.width-key_w, true) self.show_key = self.key end else self.show_key = self.key self.show_value = self.value end self[1] = FrameContainer:new{ padding = 0, bordersize = 0, OverlapGroup:new{ dimen = self.dimen:copy(), LeftContainer:new{ dimen = self.dimen:copy(), TextWidget:new{ text = self.show_key, face = self.cface, } }, RightContainer:new{ dimen = self.dimen:copy(), TextWidget:new{ text = self.show_value, face = self.cface, } } } } end function KeyValueItem:onTap() self.callback() return true end local KeyValuePage = InputContainer:new{ title = "", width = nil, height = nil, -- index for the first item to show show_page = 1, } function KeyValuePage:init() self.dimen = Geom:new{ w = self.width or Screen:getWidth(), h = self.height or Screen:getHeight(), } if Device:isTouchDevice() then self.ges_events.Swipe = { GestureRange:new{ ges = "swipe", range = self.dimen, } } end local padding = Screen:scaleBySize(10) self.item_width = self.dimen.w - 2 * padding self.item_height = Screen:scaleBySize(30) -- setup title bar self.title_bar = KeyValueTitle:new{ title = self.title, width = self.item_width, height = self.item_height, kv_page = self, } -- setup main content self.item_margin = self.item_height / 4 local line_height = self.item_height + 2 * self.item_margin local content_height = self.dimen.h - self.title_bar:getSize().h self.items_per_page = math.floor(content_height / line_height) self.pages = math.ceil(#self.kv_pairs / self.items_per_page) self.main_content = VerticalGroup:new{} self:_populateItems() -- assemble page self[1] = FrameContainer:new{ height = self.dimen.h, padding = padding, bordersize = 0, background = Blitbuffer.COLOR_WHITE, VerticalGroup:new{ self.title_bar, self.main_content, }, } end function KeyValuePage:nextPage() local new_page = math.min(self.show_page+1, self.pages) if new_page > self.show_page then self.show_page = new_page self:_populateItems() end end function KeyValuePage:prevPage() local new_page = math.max(self.show_page-1, 1) if new_page < self.show_page then self.show_page = new_page self:_populateItems() end end -- make sure self.item_margin and self.item_height are set before calling this function KeyValuePage:_populateItems() self.main_content:clear() local idx_offset = (self.show_page - 1) * self.items_per_page for idx = 1, self.items_per_page do local entry = self.kv_pairs[idx_offset + idx] if entry == nil then break end table.insert(self.main_content, VerticalSpan:new{ width = self.item_margin }) if type(entry) == "table" then table.insert( self.main_content, KeyValueItem:new{ height = self.item_height, width = self.item_width, key = entry[1], value = entry[2], callback = entry.callback, } ) elseif type(entry) == "string" then local c = string.sub(entry, 1, 1) if c == "-" then table.insert(self.main_content, LineWidget:new{ background = Blitbuffer.COLOR_LIGHT_GREY, dimen = Geom:new{ w = self.item_width, h = Screen:scaleBySize(2) }, style = "solid", }) end end table.insert(self.main_content, VerticalSpan:new{ width = self.item_margin }) end self.title_bar:setPageCount(self.show_page, self.pages) UIManager:setDirty(self, function() return "ui", self.dimen end) end function KeyValuePage:onSwipe(arg, ges_ev) if ges_ev.direction == "west" then self:nextPage() return true elseif ges_ev.direction == "east" then self:prevPage() return true end end function KeyValuePage:onClose() UIManager:close(self) return true end return KeyValuePage