Menu: new algorithm for multi-lines items (#5496)

(used by bookmarks list and classic file browser)
When text is too long or line too tall:
first: we try to decrease the number of lines (eg from 3 to 2 and from 2 to 1 line)
second: when 1 line is too tall, we try to decrease font size (-1)
And at the end we try to add or remove chars to better fit text.
pull/5525/head
Robert 5 years ago committed by poire-z
parent eae105bb72
commit c7ecd08d9d

@ -280,6 +280,11 @@ function MenuItem:init()
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
while true do
-- Free previously made widgets to avoid memory leaks
if item_name then
@ -293,34 +298,125 @@ function MenuItem:init()
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
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 or flag_fit then -- we fit !
if height < max_item_height then -- we fit !
offset = #item_name.charlist
break
end
-- Don't go too low, and then decrease lines
if self.font_size <= 12 then
local line_height = height / #item_name.vertical_string_list -- should be an integer
local lines = math.floor(max_item_height / line_height)
local offset
if item_name.vertical_string_list[lines + 1] then
offset = item_name.vertical_string_list[lines + 1].offset - 2
else -- shouldn't happen, but just in case
offset = #item_name.charlist
-- 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
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
local ellipsis_size = RenderText:sizeUtf8Text(0, self.content_width,
Font:getFace(self.font, self.font_size), "", true, self.bold).x
local removed_char_width= 0
while removed_char_width < ellipsis_size do
-- the width of each char has already been calculated by TextBoxWidget
removed_char_width = removed_char_width + item_name:getCharWidth(offset)
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
self.text = table.concat(item_name.charlist, '', 1, offset) .. ""
flag_fit = true
else
-- If we don't fit, decrease font size
self.font_size = self.font_size - 2
-- 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)
end

Loading…
Cancel
Save