Skip to content

Commit

Permalink
TextBoxWidget: add new properties, use them in Menu
Browse files Browse the repository at this point in the history
- height_adjust: if true, reduce height to a multiple of
  line_height (for nicer centering)
- height_overflow_show_ellipsis: if height overflow, append
  ellipsis to last shown line
(Implemented in both use_xtext and legacy code path.)

Use them in Menu.lua to clean up/shorten the code used for multiline
menu items by delegating the work to TextBoxWidget, or using
TextWidget when we end up needing only a single line.
  • Loading branch information
poire-z committed Nov 16, 2019
1 parent b1b8f3d commit ffb3908
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 170 deletions.
10 changes: 10 additions & 0 deletions frontend/apps/filemanager/filemanagermenu.lua
Expand Up @@ -179,6 +179,16 @@ function FileManagerMenu:setUpdateItemTable()
}
UIManager:show(items_font)
end
},
{
text = _("Reduce font size to show more text"),
keep_menu_open = true,
checked_func = function()
return G_reader_settings:isTrue("items_multilines_show_more_text")
end,
callback = function()
G_reader_settings:flipNilOrFalse("items_multilines_show_more_text")
end
}
}
}
Expand Down
247 changes: 79 additions & 168 deletions frontend/ui/widget/menu.lua
Expand Up @@ -19,7 +19,6 @@ local InputContainer = require("ui/widget/container/inputcontainer")
local LeftContainer = require("ui/widget/container/leftcontainer")
local Math = require("optmath")
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")
Expand Down Expand Up @@ -174,6 +173,32 @@ function MenuItem:init()
}
end
local max_item_height = self.dimen.h - 2 * self.linesize
-- We want to show at least one line, so cap the provided font sizes
local min_font_size = TextBoxWidget:getFontSizeToFitHeight(max_item_height, 1)
if self.font_size > min_font_size then
self.font_size = min_font_size
end
if self.infont_size > min_font_size then
self.infont_size = min_font_size
end
local multilines_show_more_text = G_reader_settings:isTrue("items_multilines_show_more_text")
if not self.single_line and not multilines_show_more_text then
-- For non single line menus (File browser, Bookmarks), if the
-- user provided font size is large and would not allow showing
-- more than one line in our item height, just switch to single
-- line mode. This allows, when truncating, to take the full
-- width and cut inside a word to add the ellipsis - while in
-- multilines modes, with TextBoxWidget, words are wrapped to
-- follow line breaking rules, and the ellipsis might be placed
-- way earlier than the full width.
local min_font_size_2_lines = TextBoxWidget:getFontSizeToFitHeight(max_item_height, 2)
if self.font_size > min_font_size_2_lines then
self.single_line = true
end
end
-- State button and indentation for tree expand/collapse (for TOC)
local state_button_width = self.state_size.w or 0
local state_button = self.state or HorizontalSpan:new{
Expand All @@ -191,6 +216,11 @@ function MenuItem:init()
}
}
-- Font for main text (may have its size decreased to make text fit)
self.face = Font:getFace(self.font, self.font_size)
-- Font for "mandatory" on the right
self.info_face = Font:getFace(self.infont, self.infont_size)
-- "mandatory" is the text on the right: file size, page number...
-- Padding before mandatory
local text_mandatory_padding = 0
Expand All @@ -201,37 +231,30 @@ function MenuItem:init()
text_ellipsis_mandatory_padding = Size.span.horizontal_small
end
local mandatory = self.mandatory and ""..self.mandatory or ""
local mandatory_widget = TextWidget:new{
text = mandatory,
face = self.info_face,
bold = self.bold,
fgcolor = self.dim and Blitbuffer.COLOR_DARK_GRAY or nil,
}
local mandatory_w = mandatory_widget:getWidth()
-- Font for main text (may have its size decreased to make text fit)
self.face = Font:getFace(self.font, self.font_size)
-- Font for "mandatory" on the right
self.info_face = Font:getFace(self.infont, self.infont_size)
local available_width = self.content_width - state_button_width - text_mandatory_padding - mandatory_w
local item_name
local mandatory_widget
if self.single_line then -- items only in single line
mandatory_widget = TextWidget:new{
text = mandatory,
face = self.info_face,
bold = self.bold,
fgcolor = self.dim and Blitbuffer.COLOR_DARK_GRAY or nil,
}
local mandatory_w = mandatory_widget:getWidth()
-- No font size change: text will be truncated if it overflows
-- (we give it a little more room if truncated for better visual
-- feeling - which might make it no more truncated, but well...)
local text_max_width_base = self.content_width - state_button_width - mandatory_w
local text_max_width = text_max_width_base - text_mandatory_padding
local text_max_width_if_ellipsis = text_max_width_base - text_ellipsis_mandatory_padding
item_name = TextWidget:new{
text = self.text,
face = self.face,
bold = self.bold,
fgcolor = self.dim and Blitbuffer.COLOR_DARK_GRAY or nil,
}
local w = item_name:getWidth()
if w > text_max_width then
if w > available_width then
-- We give it a little more room if truncated for better visual
-- feeling (which might make it no more truncated, but well...)
local text_max_width_if_ellipsis = available_width + text_mandatory_padding - text_ellipsis_mandatory_padding
item_name:setMaxWidth(text_max_width_if_ellipsis)
end
if self.align_baselines then -- Align baselines of text and mandatory
Expand All @@ -250,175 +273,63 @@ function MenuItem:init()
}
end
end
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_DARK_GRAY 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
local max_item_height = self.dimen.h - 2 * self.linesize
local flag_fit = false
local flag_add_ellipsis = false
local num_lines, offset
local item_name_orig = nil
-- first: try to decrease number of lines in TextBoxWidget
-- this loop ends only when text fits or we have only one line of text
elseif multilines_show_more_text then
-- Multi-lines, with font size decrease if neede to show more of the text
-- This might be costly/slow with use_xtext...
--- @todo improve that by being a little more clever:
-- First use font size as returned by TextBoxWidget:getFontSizeToFitHeight(5,4,3,2,1)
-- starting from the number of we'd get with the min font size (12).
-- Stop at the first that doesn't fit. Remember the previous one that fitted.
-- Then increase by 1px the one that fitted up to the one that didn't fit:
-- use the last one that fit.
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,
face = self.face,
width = available_width,
alignment = "left",
bold = self.bold,
fgcolor = self.dim and Blitbuffer.COLOR_DARK_GRAY or nil,
}
if not item_name_orig then
-- remember original item_name
item_name_orig = item_name
offset = #item_name.charlist
end
local height = item_name:getSize().h
if height < max_item_height then -- we fit !
flag_fit = true
break
end
flag_add_ellipsis = true
num_lines = item_name:getAllLineCount()
if num_lines == 1 then -- widget doesn't fit and we have only one line of text
break
end
-- remove last line and try again to fit
offset = item_name.vertical_string_list[num_lines].offset - 1
-- remove ending "\n" (new line) to prevent infinity loop
if item_name.charlist[offset] == "\n" then
offset = offset - 1
end
self.text = table.concat(item_name.charlist, "", 1, offset)
end
-- second: decrease font size (we have now only one line)
while true and not flag_fit do
-- Free previously made widgets to avoid memory leaks
if item_name then
-- Don't go with a font size too low
if self.font_size > 12 then
-- If we don't fit, decrease font size
self.font_size = self.font_size - 1
self.face = Font:getFace(self.font, self.font_size)
else
-- When we can't decrease font size anymore, impose a max height
-- to show only the first lines up to where it fits
item_name:free()
end
self.font_size = self.font_size - 1
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_DARK_GRAY or nil,
}
local height = item_name:getSize().h
if height < max_item_height then -- we fit !
offset = #item_name.charlist
break
end
-- if text is too height with that really small font we don't show text at all
-- this shouldn't happen
if self.font_size <= 8 then
item_name = TextBoxWidget:new{
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_DARK_GRAY or nil,
}
flag_add_ellipsis = false
item_name.height = max_item_height
item_name.height_adjust = true
item_name.height_overflow_show_ellipsis = true
item_name:init()
break
end
end
-- add ellipsis when text was truncated
if flag_add_ellipsis then
local text_last_line
-- when lines is more than 1 we see only for last visible line
if num_lines > 1 then
local offset_prev = item_name_orig.vertical_string_list[num_lines - 1].offset
text_last_line = table.concat(item_name_orig.charlist, "", offset_prev, offset)
else
text_last_line = self.text
end
local text_size = RenderText:sizeUtf8Text(0, self.content_width,
Font:getFace(self.font, self.font_size), text_last_line, true, self.bold).x
local ellipsis_size = RenderText:sizeUtf8Text(0, self.content_width,
Font:getFace(self.font, self.font_size), "", true, self.bold).x
local text_size_increase = text_size
local max_offset = #item_name_orig.charlist
-- try to add chars to better align
while item_name.width > text_size_increase + ellipsis_size and offset < max_offset
and item_name_orig.charlist[offset] ~= "\n" do
text_size_increase = text_size_increase + item_name_orig:getCharWidth(offset + 1)
if text_size_increase + ellipsis_size < item_name.width then
-- add one char to text
offset = offset + 1
end
end
-- remove chars when text is too long
while item_name.width <= text_size + ellipsis_size do
text_size = text_size - item_name:getCharWidth(offset)
-- remove one char from text
offset = offset - 1
end
if offset == max_offset then
-- when finally after manipulation we have all original text we don't need to add ellipsis
self.text = table.concat(item_name_orig.charlist, "", 1, offset)
else
-- remove ending '\n' (new line) to prevent increase number of lines
if item_name_orig.charlist[offset] == "\n" then
offset = offset - 1
end
-- add ellipsis to show that text was truncated
self.text = table.concat(item_name_orig.charlist, "", 1, offset) .. "…"
end
if item_name then
item_name:free()
end
if item_name_orig then
item_name_orig:free()
end
--final item_name that fits
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_DARK_GRAY or nil,
}
end
self.face = Font:getFace(self.font, self.font_size)
else
-- Multi-lines, with fixed user provided font size
item_name = TextBoxWidget:new {
text = self.text,
face = self.face,
width = available_width,
height = max_item_height,
height_adjust = true,
height_overflow_show_ellipsis = true,
alignment = "left",
bold = self.bold,
fgcolor = self.dim and Blitbuffer.COLOR_DARK_GRAY or nil,
}
end
local text_container = LeftContainer:new{
Expand Down

0 comments on commit ffb3908

Please sign in to comment.