Files
koreader/frontend/ui/widget/sortwidget.lua
poire-z f05e62c1fb TextWidget: small refactoring, better handle max_width (#5503)
Lots of code was doing some renderText calls to get the size
of some text string, and truncate it to some width if needed,
with or without an added ellipsis, before instantiating
a TextWidget with that tweaked text string.

This PR fixes/adds some properties and methods to TextWidget
so all that can be done by it. It makes the calling code
simpler, as they don't need to use RenderText directly.
(Additionally, when we go at using Harfbuzz for text rendering,
we'll just have to update or replace textwidget.lua without
the need to update any higher level code.)

Also:
- RenderText: removed the space added by truncateTextByWidth
  after the ellipsis, as it doesn't feel needed, and break
  right alignment of the ellipsis with other texts.
- KeyValuePage: fix some subtle size and alignment issues.
- NumberPickerWidget: fix font size (provided font size was
  not used)
2019-10-21 15:20:40 +02:00

488 lines
14 KiB
Lua

local Blitbuffer = require("ffi/blitbuffer")
local BottomContainer = require("ui/widget/container/bottomcontainer")
local Button = require("ui/widget/button")
local CloseButton = require("ui/widget/closebutton")
local Device = require("device")
local Font = require("ui/font")
local FrameContainer = require("ui/widget/container/framecontainer")
local Geom = require("ui/geometry")
local GestureRange = require("ui/gesturerange")
local HorizontalGroup = require("ui/widget/horizontalgroup")
local InputContainer = require("ui/widget/container/inputcontainer")
local LeftContainer = require("ui/widget/container/leftcontainer")
local LineWidget = require("ui/widget/linewidget")
local OverlapGroup = require("ui/widget/overlapgroup")
local Size = require("ui/size")
local TextWidget = require("ui/widget/textwidget")
local UIManager = require("ui/uimanager")
local VerticalGroup = require("ui/widget/verticalgroup")
local VerticalSpan = require("ui/widget/verticalspan")
local Screen = Device.screen
local T = require("ffi/util").template
local _ = require("gettext")
local SortTitleWidget = VerticalGroup:new{
sort_page = nil,
title = "",
tface = Font:getFace("tfont"),
align = "left",
use_top_page_count = false,
}
function SortTitleWidget:init()
self.close_button = CloseButton:new{ window = self }
local btn_width = self.close_button:getSize().w
-- title and close button
table.insert(self, OverlapGroup:new{
dimen = { w = self.width },
TextWidget:new{
text = self.title,
max_width = self.width - btn_width,
face = self.tface,
},
self.close_button,
})
-- page count and separation line
self.title_bottom = OverlapGroup:new{
dimen = { w = self.width, h = Size.line.thick },
LineWidget:new{
dimen = Geom:new{ w = self.width, h = Size.line.thick },
background = Blitbuffer.COLOR_DARK_GRAY,
style = "solid",
},
}
if self.use_top_page_count then
self.page_cnt = FrameContainer:new{
padding = Size.padding.default,
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_DARK_GRAY,
face = Font:getFace("smallffont"),
},
}
table.insert(self.title_bottom, self.page_cnt)
end
table.insert(self, self.title_bottom)
table.insert(self, VerticalSpan:new{ width = Size.span.vertical_large })
end
function SortTitleWidget: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 SortTitleWidget:onClose()
self.sort_page:onClose()
return true
end
local SortItemWidget = InputContainer:new{
key = nil,
cface = Font:getFace("smallinfofont"),
tface = Font:getFace("smallinfofontbold"),
width = nil,
height = nil,
}
function SortItemWidget:init()
self.dimen = Geom:new{w = self.width, h = self.height}
if Device:isTouchDevice() then
self.ges_events.Tap = {
GestureRange:new{
ges = "tap",
range = self.dimen,
}
}
self.ges_events.Hold = {
GestureRange:new{
ges = "hold",
range = self.dimen,
}
}
end
local frame_padding = Size.padding.default
local frame_internal_width = self.width - frame_padding * 2
self[1] = FrameContainer:new{
padding = 0,
bordersize = 0,
LeftContainer:new{
dimen = {
w = frame_internal_width,
h = self.height
},
TextWidget:new{
text = self.text,
max_width = frame_internal_width,
face = self.tface,
}
},
}
self[1].invert = self.invert
end
function SortItemWidget:onTap()
if self.show_parent.marked == self.index then
self.show_parent.marked = 0
else
self.show_parent.marked = self.index
end
self.show_parent:_populateItems()
return true
end
function SortItemWidget:onHold()
return true
end
local SortWidget = InputContainer:new{
title = "",
width = nil,
height = nil,
-- index for the first item to show
show_page = 1,
use_top_page_count = false,
-- table of items to sort
item_table = {},
callback = nil,
}
function SortWidget:init()
-- no item is selected on start
self.marked = 0
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 = Size.padding.large
self.width_widget = self.dimen.w - 2 * padding
self.item_width = self.dimen.w - 2 * padding
self.item_height = Size.item.height_big
-- group for footer
self.footer_left = Button:new{
text = "",
width = self.width_widget * 13 / 100,
callback = function() self:prevPage() end,
text_font_size = 28,
bordersize = 0,
padding = 0,
radius = 0,
}
self.footer_right = Button:new{
text = "",
width = self.width_widget * 13 / 100,
callback = function() self:nextPage() end,
text_font_size = 28,
bordersize = 0,
padding = 0,
radius = 0,
}
self.footer_first_up = Button:new{
text = "◀◀",
width = self.width_widget * 13 / 100,
callback = function()
if self.marked > 0 then
self:moveItem(-1)
else
self:goToPage(1)
end
end,
text_font_size = 28,
bordersize = 0,
padding = 0,
radius = 0,
}
self.footer_last_down = Button:new{
text = "▶▶",
width = self.width_widget * 13 / 100,
callback = function()
if self.marked > 0 then
self:moveItem(1)
else
self:goToPage(self.pages)
end
end,
text_font_size = 28,
bordersize = 0,
padding = 0,
radius = 0,
}
self.footer_cancel = Button:new{
text = "",
width = self.width_widget * 13 / 100,
callback = function() self:onClose() end,
bordersize = 0,
text_font_size = 28,
padding = 0,
radius = 0,
}
self.footer_ok = Button:new{
text= "",
width = self.width_widget * 13 / 100,
callback = function() self:onReturn() end,
bordersize = 0,
padding = 0,
radius = 0,
text_font_size = 28,
}
self.footer_page = Button:new{
text = "",
tap_input = {
title = _("Enter page number"),
type = "number",
hint_func = function()
return "(" .. "1 - " .. self.pages .. ")"
end,
callback = function(input)
local page = tonumber(input)
if page and page >= 1 and page <= self.pages then
self:goToPage(page)
end
end,
},
bordersize = 0,
margin = 0,
text_font_face = "pgfont",
text_font_bold = false,
width = self.width_widget * 22 / 100,
}
local button_vertical_line = LineWidget:new{
dimen = Geom:new{ w = Size.line.thick, h = self.item_height * 1.25 },
background = Blitbuffer.COLOR_DARK_GRAY,
style = "solid",
}
self.page_info = HorizontalGroup:new{
self.footer_cancel,
button_vertical_line,
self.footer_first_up,
button_vertical_line,
self.footer_left,
button_vertical_line,
self.footer_page,
button_vertical_line,
self.footer_right,
button_vertical_line,
self.footer_last_down,
button_vertical_line,
self.footer_ok,
}
local bottom_line = LineWidget:new{
dimen = Geom:new{ w = self.item_width, h = Size.line.thick },
background = Blitbuffer.COLOR_DARK_GRAY,
style = "solid",
}
local vertical_footer = VerticalGroup:new{
bottom_line,
self.page_info
}
local footer = BottomContainer:new{
dimen = self.dimen:copy(),
vertical_footer,
}
-- setup title bar
self.title_bar = SortTitleWidget:new{
title = self.title,
width = self.item_width,
height = self.item_height,
use_top_page_count = self.use_top_page_count,
sort_page = self,
}
-- setup main content
self.item_margin = self.item_height / 8
local line_height = self.item_height + self.item_margin
local content_height = self.dimen.h - self.title_bar:getSize().h - vertical_footer:getSize().h - padding
self.items_per_page = math.floor(content_height / line_height)
self.pages = math.ceil(#self.item_table / self.items_per_page)
self.main_content = VerticalGroup:new{}
self:_populateItems()
local frame_content = FrameContainer:new{
height = self.dimen.h,
padding = padding,
bordersize = 0,
background = Blitbuffer.COLOR_WHITE,
VerticalGroup:new{
align = "left",
self.title_bar,
self.main_content,
},
}
local content = OverlapGroup:new{
dimen = self.dimen:copy(),
frame_content,
footer,
}
-- assemble page
self[1] = FrameContainer:new{
height = self.dimen.h,
padding = 0,
bordersize = 0,
background = Blitbuffer.COLOR_WHITE,
content
}
end
function SortWidget:nextPage()
local new_page = math.min(self.show_page+1, self.pages)
if new_page > self.show_page then
self.show_page = new_page
if self.marked > 0 then
self:moveItem(self.items_per_page * (self.show_page - 1) + 1 - self.marked)
end
self:_populateItems()
end
end
function SortWidget:prevPage()
local new_page = math.max(self.show_page-1, 1)
if new_page < self.show_page then
self.show_page = new_page
if self.marked > 0 then
self:moveItem(self.items_per_page * (self.show_page - 1) + 1 - self.marked)
end
self:_populateItems()
end
end
function SortWidget:goToPage(page)
self.show_page = page
self:_populateItems()
end
function SortWidget:moveItem(diff)
local move_to = self.marked + diff
if move_to > 0 and move_to <= #self.item_table then
self.show_page = math.ceil(move_to/self.items_per_page)
self:swapItems(self.marked, move_to)
self:_populateItems()
end
end
-- make sure self.item_margin and self.item_height are set before calling this
function SortWidget:_populateItems()
self.main_content:clear()
local idx_offset = (self.show_page - 1) * self.items_per_page
local page_last
if idx_offset + self.items_per_page <= #self.item_table then
page_last = idx_offset + self.items_per_page
else
page_last = #self.item_table
end
for idx = idx_offset + 1, page_last do
table.insert(self.main_content, VerticalSpan:new{ width = self.item_margin })
local invert_status = false
if idx == self.marked then
invert_status = true
end
table.insert(
self.main_content,
SortItemWidget:new{
height = self.item_height,
width = self.item_width,
text = self.item_table[idx].text,
lable = self.item_table[idx].label,
invert = invert_status,
index = idx,
show_parent = self,
}
)
end
self.footer_page:setText(T(_("%1/%2"), self.show_page, self.pages), self.width_widget * 22 / 100)
if self.marked > 0 then
self.footer_first_up:setText("", self.width_widget * 13 / 100)
self.footer_last_down:setText("", self.width_widget * 13 / 100)
else
self.footer_first_up:setText("◀◀", self.width_widget * 13 / 100)
self.footer_last_down:setText("▶▶", self.width_widget * 13 / 100)
end
self.footer_left:enableDisable(self.show_page > 1)
self.footer_right:enableDisable(self.show_page < self.pages)
self.footer_first_up:enableDisable(self.show_page > 1 or self.marked > 0)
self.footer_last_down:enableDisable(self.show_page < self.pages or (self.marked > 0 and self.marked < #self.item_table))
self.footer_first_up:enableDisable(self.marked > 1)
UIManager:setDirty(self, function()
return "ui", self.dimen
end)
end
function SortWidget:swapItems(pos1, pos2)
if pos1 > 0 or pos2 <= #self.item_table then
local entry = self.item_table[pos1]
self.marked = pos2
self.item_table[pos1] = self.item_table[pos2]
self.item_table[pos2] = entry
end
end
function SortWidget:onNextPage()
self:nextPage()
return true
end
function SortWidget:onPrevPage()
self:prevPage()
return true
end
function SortWidget:onSwipe(arg, ges_ev)
if ges_ev.direction == "west" then
self:onNextPage()
elseif ges_ev.direction == "east" then
self:onPrevPage()
elseif ges_ev.direction == "south" then
-- Allow easier closing with swipe down
self:onClose()
elseif ges_ev.direction == "north" then
-- no use for now
do end -- luacheck: ignore 541
else -- diagonal swipe
-- trigger full refresh
UIManager:setDirty(nil, "full")
-- a long diagonal swipe may also be used for taking a screenshot,
-- so let it propagate
return false
end
end
function SortWidget:onClose()
UIManager:close(self)
UIManager:setDirty(nil, "ui")
return true
end
function SortWidget:onReturn()
UIManager:close(self)
if self.callback then self:callback() end
return true
end
return SortWidget