mirror of
https://github.com/koreader/koreader.git
synced 2025-08-10 00:52:38 +00:00
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)
488 lines
14 KiB
Lua
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
|