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)
pull/5525/head
poire-z 5 years ago committed by GitHub
parent ef22e85469
commit f05e62c1fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -50,10 +50,6 @@ local function restoreScreenMode()
end end
end end
local function truncatePath(text)
return FileChooser:truncatePath(text)
end
local FileManager = InputContainer:extend{ local FileManager = InputContainer:extend{
title = _("KOReader"), title = _("KOReader"),
root_path = lfs.currentdir(), root_path = lfs.currentdir(),
@ -110,7 +106,9 @@ function FileManager:init()
self.path_text = TextWidget:new{ self.path_text = TextWidget:new{
face = Font:getFace("xx_smallinfofont"), face = Font:getFace("xx_smallinfofont"),
text = truncatePath(filemanagerutil.abbreviate(self.root_path)), text = filemanagerutil.abbreviate(self.root_path),
max_width = Screen:getWidth() - 2*Size.padding.small,
truncate_left = true,
} }
self.banner = FrameContainer:new{ self.banner = FrameContainer:new{
@ -177,7 +175,7 @@ function FileManager:init()
self.focused_file = nil -- use it only once self.focused_file = nil -- use it only once
function file_chooser:onPathChanged(path) -- luacheck: ignore function file_chooser:onPathChanged(path) -- luacheck: ignore
FileManager.instance.path_text:setText(truncatePath(filemanagerutil.abbreviate(path))) FileManager.instance.path_text:setText(filemanagerutil.abbreviate(path))
UIManager:setDirty(FileManager.instance, function() UIManager:setDirty(FileManager.instance, function()
return "ui", FileManager.instance.path_text.dimen, FileManager.instance.dithered return "ui", FileManager.instance.path_text.dimen, FileManager.instance.dithered
end) end)

@ -263,8 +263,7 @@ function RenderText:renderUtf8Text(dest_bb, x, baseline, face, text, kerning, bo
return pen_x return pen_x
end end
local ellipsis, space = "", " " local ellipsis = ""
local ellipsis_width, space_width
--- Returns a substring of a given text that meets the maximum width (in pixels) --- Returns a substring of a given text that meets the maximum width (in pixels)
-- restriction with ellipses (…) at the end if required. -- restriction with ellipses (…) at the end if required.
-- --
@ -273,23 +272,13 @@ local ellipsis_width, space_width
-- @int width maximum width in pixels -- @int width maximum width in pixels
-- @bool[opt=false] kerning whether the text should be measured with kerning -- @bool[opt=false] kerning whether the text should be measured with kerning
-- @bool[opt=false] bold whether the text should be measured as bold -- @bool[opt=false] bold whether the text should be measured as bold
-- @bool[opt=false] prepend_space whether a space should be prepended to the text
-- @treturn string -- @treturn string
-- @see getSubTextByWidth -- @see getSubTextByWidth
function RenderText:truncateTextByWidth(text, face, max_width, kerning, bold, prepend_space) function RenderText:truncateTextByWidth(text, face, max_width, kerning, bold)
if not ellipsis_width then local ellipsis_width = self:sizeUtf8Text(0, max_width, face, ellipsis, false, bold).x
ellipsis_width = self:sizeUtf8Text(0, max_width, face, ellipsis).x local new_txt_width = max_width - ellipsis_width
end
if not space_width then
space_width = self:sizeUtf8Text(0, max_width, face, space).x
end
local new_txt_width = max_width - ellipsis_width - space_width
local sub_txt = self:getSubTextByWidth(text, face, new_txt_width, kerning, bold) local sub_txt = self:getSubTextByWidth(text, face, new_txt_width, kerning, bold)
if prepend_space then return sub_txt .. ellipsis
return space.. sub_txt .. ellipsis
else
return sub_txt .. ellipsis .. space
end
end end
return RenderText return RenderText

@ -459,7 +459,6 @@ function Screensaver:addOverlayMessage(widget, text)
local Font = require("ui/font") local Font = require("ui/font")
local FrameContainer = require("ui/widget/container/framecontainer") local FrameContainer = require("ui/widget/container/framecontainer")
local OverlapGroup = require("ui/widget/overlapgroup") local OverlapGroup = require("ui/widget/overlapgroup")
local RenderText = require("ui/rendertext")
local RightContainer = require("ui/widget/container/rightcontainer") local RightContainer = require("ui/widget/container/rightcontainer")
local Size = require("ui/size") local Size = require("ui/size")
local TextBoxWidget = require("ui/widget/textboxwidget") local TextBoxWidget = require("ui/widget/textboxwidget")
@ -468,15 +467,13 @@ function Screensaver:addOverlayMessage(widget, text)
local face = Font:getFace("infofont") local face = Font:getFace("infofont")
local screen_w, screen_h = Screen:getWidth(), Screen:getHeight() local screen_w, screen_h = Screen:getWidth(), Screen:getHeight()
local textw local textw = TextWidget:new{
text = text,
face = face,
}
-- Don't make our message reach full screen width -- Don't make our message reach full screen width
local tsize = RenderText:sizeUtf8Text(0, screen_w, face, text) if textw:getWidth() > screen_w * 0.9 then
if tsize.x < screen_w * 0.9 then -- Text too wide: use TextBoxWidget for multi lines display
textw = TextWidget:new{
text = text,
face = face,
}
else -- if text too wide, use TextBoxWidget for multi lines display
textw = TextBoxWidget:new{ textw = TextBoxWidget:new{
text = text, text = text,
face = face, face = face,

@ -18,7 +18,6 @@ local IconButton = require("ui/widget/iconbutton")
local ImageWidget = require("ui/widget/imagewidget") local ImageWidget = require("ui/widget/imagewidget")
local InputContainer = require("ui/widget/container/inputcontainer") local InputContainer = require("ui/widget/container/inputcontainer")
local LineWidget = require("ui/widget/linewidget") local LineWidget = require("ui/widget/linewidget")
local RenderText = require("ui/rendertext")
local RightContainer = require("ui/widget/container/rightcontainer") local RightContainer = require("ui/widget/container/rightcontainer")
local Size = require("ui/size") local Size = require("ui/size")
local TextWidget = require("ui/widget/textwidget") local TextWidget = require("ui/widget/textwidget")
@ -208,7 +207,11 @@ function ConfigOption:init()
local face = Font:getFace(name_font_face, name_font_size) local face = Font:getFace(name_font_face, name_font_size)
local txt_width = 0 local txt_width = 0
if text ~= nil then if text ~= nil then
txt_width = RenderText:sizeUtf8Text(0, Screen:getWidth(), face, text).x local tmp = TextWidget:new{
text = text,
face = face,
}
txt_width = tmp:getWidth()
end end
max_option_name_width = math.max(max_option_name_width, txt_width) max_option_name_width = math.max(max_option_name_width, txt_width)
end end
@ -265,16 +268,12 @@ function ConfigOption:init()
local name_text_max_width = name_widget_width - default_option_hpadding - 2*padding_small local name_text_max_width = name_widget_width - default_option_hpadding - 2*padding_small
local text = self.options[c].name_text local text = self.options[c].name_text
local face = Font:getFace(name_font_face, name_font_size) local face = Font:getFace(name_font_face, name_font_size)
local width_name_text = RenderText:sizeUtf8Text(0, Screen:getWidth(), face, text).x
if width_name_text > name_text_max_width then
text = RenderText:truncateTextByWidth(text, face, name_text_max_width)
end
local option_name_container = RightContainer:new{ local option_name_container = RightContainer:new{
dimen = Geom:new{ w = name_widget_width, h = option_height}, dimen = Geom:new{ w = name_widget_width, h = option_height},
} }
local option_name = Button:new{ local option_name = Button:new{
text = text, text = text,
max_width = name_text_max_width,
bordersize = 0, bordersize = 0,
face = face, face = face,
enabled = enabled, enabled = enabled,
@ -436,13 +435,10 @@ function ConfigOption:init()
else else
local text = self.options[c].item_text[d] local text = self.options[c].item_text[d]
local face = Font:getFace(item_font_face, item_font_size) local face = Font:getFace(item_font_face, item_font_size)
local width_item_text = RenderText:sizeUtf8Text(0, Screen:getWidth(), face, text).x
if max_item_text_width < width_item_text then
text = RenderText:truncateTextByWidth(text, face, max_item_text_width)
end
option_item = OptionTextItem:new{ option_item = OptionTextItem:new{
TextWidget:new{ TextWidget:new{
text = text, text = text,
max_width = max_item_text_width,
face = face, face = face,
fgcolor = enabled and Blitbuffer.COLOR_BLACK or Blitbuffer.COLOR_DARK_GRAY, fgcolor = enabled and Blitbuffer.COLOR_BLACK or Blitbuffer.COLOR_DARK_GRAY,
}, },

@ -1,29 +1,25 @@
local TextWidget = require("ui/widget/textwidget") local TextWidget = require("ui/widget/textwidget")
local RenderText = require("ui/rendertext")
local Geom = require("ui/geometry") local Geom = require("ui/geometry")
local Screen = require("device").screen
--[[ --[[
FixedTextWidget FixedTextWidget
--]] --]]
local FixedTextWidget = TextWidget:new{} local FixedTextWidget = TextWidget:new{}
function FixedTextWidget:getSize() function FixedTextWidget:updateSize()
local tsize = RenderText:sizeUtf8Text(0, Screen:getWidth(), self.face, self.text, true, self.bold) TextWidget.updateSize(self)
if tsize.x == 0 then -- Only difference from TextWidget:
return Geom:new{} -- no vertical padding, baseline is height
end
self._length = tsize.x
self._height = self.face.size self._height = self.face.size
return Geom:new{ self._baseline_h = self.face.size
w = self._length,
h = self._height,
}
end end
function FixedTextWidget:paintTo(bb, x, y) function FixedTextWidget:getSize()
RenderText:renderUtf8Text(bb, x, y+self._height, self.face, self.text, true, self.bold, self:updateSize()
self.fgcolor) if self._length == 0 then
return Geom:new{}
end
return TextWidget.getSize(self)
end end
return FixedTextWidget return FixedTextWidget

@ -108,7 +108,6 @@ local LineWidget = require("ui/widget/linewidget")
local MovableContainer = require("ui/widget/container/movablecontainer") local MovableContainer = require("ui/widget/container/movablecontainer")
local MultiConfirmBox = require("ui/widget/multiconfirmbox") local MultiConfirmBox = require("ui/widget/multiconfirmbox")
local Notification = require("ui/widget/notification") local Notification = require("ui/widget/notification")
local RenderText = require("ui/rendertext")
local Size = require("ui/size") local Size = require("ui/size")
local TextBoxWidget = require("ui/widget/textboxwidget") local TextBoxWidget = require("ui/widget/textboxwidget")
local TextWidget = require("ui/widget/textwidget") local TextWidget = require("ui/widget/textwidget")
@ -211,15 +210,6 @@ function InputDialog:init()
end end
-- Title & description -- Title & description
local title_width = RenderText:sizeUtf8Text(0, self.width,
self.title_face, self.title, true).x
if title_width > self.width then
local indicator = " >> "
local indicator_w = RenderText:sizeUtf8Text(0, self.width,
self.title_face, indicator, true).x
self.title = RenderText:getSubTextByWidth(self.title, self.title_face,
self.width - indicator_w, true) .. indicator
end
self.title_widget = FrameContainer:new{ self.title_widget = FrameContainer:new{
padding = self.title_padding, padding = self.title_padding,
margin = self.title_margin, margin = self.title_margin,
@ -227,7 +217,7 @@ function InputDialog:init()
TextWidget:new{ TextWidget:new{
text = self.title, text = self.title,
face = self.title_face, face = self.title_face,
width = self.width, max_width = self.width,
} }
} }
self.title_bar = LineWidget:new{ self.title_bar = LineWidget:new{

@ -34,7 +34,6 @@ local InputContainer = require("ui/widget/container/inputcontainer")
local LeftContainer = require("ui/widget/container/leftcontainer") local LeftContainer = require("ui/widget/container/leftcontainer")
local LineWidget = require("ui/widget/linewidget") local LineWidget = require("ui/widget/linewidget")
local OverlapGroup = require("ui/widget/overlapgroup") local OverlapGroup = require("ui/widget/overlapgroup")
local RenderText = require("ui/rendertext")
local Size = require("ui/size") local Size = require("ui/size")
local TextViewer = require("ui/widget/textviewer") local TextViewer = require("ui/widget/textviewer")
local TextWidget = require("ui/widget/textwidget") local TextWidget = require("ui/widget/textwidget")
@ -57,20 +56,12 @@ local KeyValueTitle = VerticalGroup:new{
function KeyValueTitle:init() function KeyValueTitle:init()
self.close_button = CloseButton:new{ window = self } self.close_button = CloseButton:new{ window = self }
local btn_width = self.close_button:getSize().w 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 = RenderText:truncateTextByWidth(
self.title, self.tface, self.width-btn_width)
else
show_title_txt = self.title
end
-- title and close button -- title and close button
table.insert(self, OverlapGroup:new{ table.insert(self, OverlapGroup:new{
dimen = { w = self.width }, dimen = { w = self.width },
TextWidget:new{ TextWidget:new{
text = show_title_txt, text = self.title,
max_width = self.width - btn_width,
face = self.tface, face = self.tface,
}, },
self.close_button, self.close_button,
@ -154,25 +145,54 @@ function KeyValueItem:init()
local frame_padding = Size.padding.default local frame_padding = Size.padding.default
local frame_internal_width = self.width - frame_padding * 2 local frame_internal_width = self.width - frame_padding * 2
-- Default widths (and position of value widget) if each text fits in 1/2 screen width
local key_w = frame_internal_width / 2 local key_w = frame_internal_width / 2
local value_w = frame_internal_width / 2 local value_w = frame_internal_width / 2
local key_w_rendered = RenderText:sizeUtf8Text(0, frame_internal_width, self.tface, self.key).x
local value_w_rendered = RenderText:sizeUtf8Text(0, frame_internal_width, self.cface, tvalue).x local key_widget = TextWidget:new{
local space_w_rendered = RenderText:sizeUtf8Text(0, frame_internal_width, self.cface, " ").x text = self.key,
max_width = frame_internal_width,
face = self.tface,
}
local value_widget = TextWidget:new{
text = tvalue,
max_width = frame_internal_width,
face = self.cface,
}
local key_w_rendered = key_widget:getWidth()
local value_w_rendered = value_widget:getWidth()
-- As both key_widget and value_width will be in a HorizontalGroup,
-- and key is always left aligned, we can just tweak the key width
-- to position the value_widget
local value_prepend_space = false
local value_align_right = false
local fit_right_align = true -- by default, really right align
if key_w_rendered > key_w or value_w_rendered > value_w then if key_w_rendered > key_w or value_w_rendered > value_w then
-- truncate key or value so they fit in one row -- One (or both) does not fit in 1/2 width
if key_w_rendered + value_w_rendered > frame_internal_width then if key_w_rendered + value_w_rendered > frame_internal_width then
-- Both do not fit: one has to be truncated so they fit
if key_w_rendered >= value_w_rendered then if key_w_rendered >= value_w_rendered then
-- Rare case: key larger than value.
-- We should have kept our keys small, smaller than 1/2 width.
-- If it is larger than value, it's that value is kinda small,
-- so keep the whole value, and truncate the key
key_w = frame_internal_width - value_w_rendered key_w = frame_internal_width - value_w_rendered
self.show_key = RenderText:truncateTextByWidth(self.key, self.tface, frame_internal_width - value_w_rendered)
self.show_value = tvalue
else else
key_w = key_w_rendered + space_w_rendered -- Usual case: value larger than key.
self.show_value = RenderText:truncateTextByWidth(tvalue, self.cface, frame_internal_width - key_w_rendered, -- Keep our small key, fit the value in the remaining width,
false, false, true) -- prepend some space to separate them
self.show_key = self.key key_w = key_w_rendered
value_prepend_space = true
end end
-- allow for displaying the non-truncated texts with Hold value_align_right = true -- so the ellipsis touches the screen right border
if self.value_overflow_align ~= "right" and self.value_align ~= "right" then
-- Don't adjust the ellipsis to the screen right border,
-- so the left of text is aligned with other truncated texts
fit_right_align = false
end
-- Allow for displaying the non-truncated texts with Hold
if Device:isTouchDevice() then if Device:isTouchDevice() then
self.ges_events.Hold = { self.ges_events.Hold = {
GestureRange:new{ GestureRange:new{
@ -181,24 +201,43 @@ function KeyValueItem:init()
} }
} }
end end
-- misalign to fit all info
else else
-- Both can fit: break the 1/2 widths
if self.value_overflow_align == "right" or self.value_align == "right" then if self.value_overflow_align == "right" or self.value_align == "right" then
key_w = frame_internal_width - value_w_rendered key_w = frame_internal_width - value_w_rendered
value_align_right = true
else else
key_w = key_w_rendered + space_w_rendered key_w = key_w_rendered
value_prepend_space = true
end end
self.show_key = self.key
self.show_value = tvalue
end end
-- In all the above case, we set the right key_w to include any
-- needed in-between padding: value_w is what's left.
value_w = frame_internal_width - key_w
else else
if self.value_align == "right" then if self.value_align == "right" then
key_w = frame_internal_width - value_w_rendered key_w = frame_internal_width - value_w_rendered
value_w = value_w_rendered
value_align_right = true
end end
self.show_key = self.key
self.show_value = tvalue
end end
-- Adjust widgets' max widths and text as needed
if value_prepend_space then
value_widget:setText(" "..tvalue)
end
value_widget:setMaxWidth(value_w)
if fit_right_align and value_align_right and value_widget:getWidth() < value_w then
-- Because of truncation at glyph boundaries, value_widget
-- may be a tad smaller than the specified value_w:
-- add some padding to key_w so value is pushed to the screen right border
key_w = key_w + ( value_w - value_widget:getWidth() )
end
key_widget:setMaxWidth(key_w)
-- For debugging positioning:
-- value_widget = FrameContainer:new{ padding=0, margin=0, bordersize=1, value_widget }
self[1] = FrameContainer:new{ self[1] = FrameContainer:new{
padding = frame_padding, padding = frame_padding,
bordersize = 0, bordersize = 0,
@ -209,20 +248,14 @@ function KeyValueItem:init()
w = key_w, w = key_w,
h = self.height h = self.height
}, },
TextWidget:new{ key_widget,
text = self.show_key,
face = self.tface,
}
}, },
LeftContainer:new{ LeftContainer:new{
dimen = { dimen = {
w = value_w, w = value_w,
h = self.height h = self.height
}, },
TextWidget:new{ value_widget,
text = self.show_value,
face = self.cface,
}
} }
} }
} }

@ -129,8 +129,6 @@ local MenuItem = InputContainer:new{
text = nil, text = nil,
show_parent = nil, show_parent = nil,
detail = nil, detail = nil,
face = Font:getFace("cfont", 30),
info_face = Font:getFace("infont", 15),
font = "cfont", font = "cfont",
font_size = 24, font_size = 24,
infont = "infont", infont = "infont",
@ -176,6 +174,25 @@ function MenuItem:init()
} }
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{
width = state_button_width,
}
local state_indent = self.state and self.state.indent or ""
local state_container = LeftContainer:new{
dimen = Geom:new{w = self.content_width/2, h = self.dimen.h},
HorizontalGroup:new{
TextWidget:new{
text = state_indent,
face = Font:getFace(self.font, self.font_size),
},
state_button,
}
}
-- "mandatory" is the text on the right: file size, page number...
-- Padding before mandatory
local text_mandatory_padding = 0 local text_mandatory_padding = 0
local text_ellipsis_mandatory_padding = 0 local text_ellipsis_mandatory_padding = 0
if self.mandatory then if self.mandatory then
@ -185,44 +202,39 @@ function MenuItem:init()
end end
local mandatory = self.mandatory and ""..self.mandatory or "" local mandatory = self.mandatory and ""..self.mandatory or ""
local state_button_width = self.state_size.w or 0 -- Font for main text (may have its size decreased to make text fit)
local state_button = self.state or HorizontalSpan:new{ self.face = Font:getFace(self.font, self.font_size)
width = state_button_width, -- Font for "mandatory" on the right
} self.info_face = Font:getFace(self.infont, self.infont_size)
local state_indent = self.state and self.state.indent or ""
local item_name local item_name
local mandatory_widget local mandatory_widget
if self.single_line then -- items only in single line if self.single_line then -- items only in single line
self.info_face = Font:getFace(self.infont, self.infont_size)
self.face = Font:getFace(self.font, self.font_size)
local mandatory_w = RenderText:sizeUtf8Text(0, self.dimen.w, self.info_face, ""..mandatory, true, self.bold).x
local my_text = self.text and ""..self.text or ""
local w = RenderText:sizeUtf8Text(0, self.dimen.w, self.face, my_text, true, self.bold).x
if w + mandatory_w + state_button_width + text_mandatory_padding >= self.content_width then
local indicator = "\226\128\166 " -- an ellipsis
local indicator_w = RenderText:sizeUtf8Text(0, self.dimen.w, self.face,
indicator, true, self.bold).x
self.text = RenderText:getSubTextByWidth(my_text, self.face,
self.content_width - indicator_w - mandatory_w - state_button_width - text_ellipsis_mandatory_padding,
true, self.bold) .. indicator
end
item_name = TextWidget:new{
text = self.text,
face = self.face,
bold = self.bold,
fgcolor = self.dim and Blitbuffer.COLOR_DARK_GRAY or nil,
}
mandatory_widget = TextWidget:new{ mandatory_widget = TextWidget:new{
text = mandatory, text = mandatory,
face = self.info_face, face = self.info_face,
bold = self.bold, bold = self.bold,
fgcolor = self.dim and Blitbuffer.COLOR_DARK_GRAY or nil, fgcolor = self.dim and Blitbuffer.COLOR_DARK_GRAY or nil,
} }
if self.align_baselines then 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
item_name:setMaxWidth(text_max_width_if_ellipsis)
end
if self.align_baselines then -- Align baselines of text and mandatory
local name_baseline = item_name:getBaseline() local name_baseline = item_name:getBaseline()
local mandatory_baseline = mandatory_widget:getBaseline() local mandatory_baseline = mandatory_widget:getBaseline()
local baselines_diff = Math.round(name_baseline - mandatory_baseline) local baselines_diff = Math.round(name_baseline - mandatory_baseline)
@ -313,16 +325,6 @@ function MenuItem:init()
self.face = Font:getFace(self.font, self.font_size) self.face = Font:getFace(self.font, self.font_size)
end end
local state_container = LeftContainer:new{
dimen = Geom:new{w = self.content_width/2, h = self.dimen.h},
HorizontalGroup:new{
HorizontalSpan:new{
width = RenderText:sizeUtf8Text(0, self.dimen.w, self.face,
state_indent, true, self.bold).x,
},
state_button,
}
}
local text_container = LeftContainer:new{ local text_container = LeftContainer:new{
dimen = Geom:new{w = self.content_width, h = self.dimen.h}, dimen = Geom:new{w = self.content_width, h = self.dimen.h},
HorizontalGroup:new{ HorizontalGroup:new{
@ -579,7 +581,9 @@ function Menu:init()
if self.show_path then if self.show_path then
self.path_text = TextWidget:new{ self.path_text = TextWidget:new{
face = Font:getFace("xx_smallinfofont"), face = Font:getFace("xx_smallinfofont"),
text = self:truncatePath(self.path), text = self.path,
max_width = self.dimen.w - 2*Size.padding.small,
truncate_left = true,
} }
path_text_container = CenterContainer:new{ path_text_container = CenterContainer:new{
dimen = Geom:new{ dimen = Geom:new{
@ -865,19 +869,6 @@ function Menu:init()
end end
end end
function Menu:truncatePath(text)
local screen_width = Screen:getWidth()
local face = Font:getFace("xx_smallinfofont")
-- we want to truncate text on the left, so work with the reverse of text (which is fine as we don't use kerning)
local reversed_text = require("util").utf8Reverse(text)
local txt_width = RenderText:sizeUtf8Text(0, screen_width, face, reversed_text, false, false).x
if screen_width - 2 * Size.padding.small < txt_width then
reversed_text = RenderText:truncateTextByWidth(reversed_text, face, screen_width - 2 * Size.padding.small, false, false)
text = require("util").utf8Reverse(reversed_text)
end
return text
end
function Menu:onCloseWidget() function Menu:onCloseWidget()
--- @fixme --- @fixme
-- we cannot refresh regionally using the dimen field -- we cannot refresh regionally using the dimen field
@ -984,7 +975,7 @@ function Menu:updateItems(select_number)
self:updatePageInfo(select_number) self:updatePageInfo(select_number)
if self.show_path then if self.show_path then
self.path_text.text = self:truncatePath(self.path) self.path_text:setText(self.path)
end end
UIManager:setDirty(self.show_parent, function() UIManager:setDirty(self.show_parent, function()
@ -1011,7 +1002,7 @@ end
--]] --]]
function Menu:switchItemTable(new_title, new_item_table, itemnumber, itemmatch) function Menu:switchItemTable(new_title, new_item_table, itemnumber, itemmatch)
if self.menu_title and new_title then if self.menu_title and new_title then
self.menu_title.text = new_title self.menu_title:setText(new_title)
end end
if itemnumber == nil then if itemnumber == nil then

@ -25,7 +25,6 @@ local Font = require("ui/font")
local InfoMessage = require("ui/widget/infomessage") local InfoMessage = require("ui/widget/infomessage")
local InputContainer = require("ui/widget/container/inputcontainer") local InputContainer = require("ui/widget/container/inputcontainer")
local InputDialog = require("ui/widget/inputdialog") local InputDialog = require("ui/widget/inputdialog")
local RenderText = require("ui/rendertext")
local Size = require("ui/size") local Size = require("ui/size")
local UIManager = require("ui/uimanager") local UIManager = require("ui/uimanager")
local VerticalGroup = require("ui/widget/verticalgroup") local VerticalGroup = require("ui/widget/verticalgroup")
@ -120,12 +119,7 @@ function NumberPickerWidget:paintWidget()
width = self.screen_height * 0.01 width = self.screen_height * 0.01
} }
local value = self.value local value = self.value
if self.value_table then if not self.value_table then
local text_width = RenderText:sizeUtf8Text(0, self.width, self.spinner_face, self.value, true, true).x
if self.width < text_width then
value = RenderText:truncateTextByWidth(self.value, self.spinner_face, self.width,true, true)
end
else
value = string.format(self.precision, value) value = string.format(self.precision, value)
end end
@ -185,9 +179,10 @@ function NumberPickerWidget:paintWidget()
text = value, text = value,
bordersize = 0, bordersize = 0,
padding = 0, padding = 0,
text_font_face = self.spinner_face_font, text_font_face = self.spinner_face.font,
text_font_size = self.spinner_face_size, text_font_size = self.spinner_face.orig_size,
width = self.width, width = self.width,
max_width = self.width,
callback = callback_input, callback = callback_input,
} }
return VerticalGroup:new{ return VerticalGroup:new{

@ -12,7 +12,6 @@ local InputContainer = require("ui/widget/container/inputcontainer")
local LeftContainer = require("ui/widget/container/leftcontainer") local LeftContainer = require("ui/widget/container/leftcontainer")
local LineWidget = require("ui/widget/linewidget") local LineWidget = require("ui/widget/linewidget")
local OverlapGroup = require("ui/widget/overlapgroup") local OverlapGroup = require("ui/widget/overlapgroup")
local RenderText = require("ui/rendertext")
local Size = require("ui/size") local Size = require("ui/size")
local TextWidget = require("ui/widget/textwidget") local TextWidget = require("ui/widget/textwidget")
local UIManager = require("ui/uimanager") local UIManager = require("ui/uimanager")
@ -33,20 +32,12 @@ local SortTitleWidget = VerticalGroup:new{
function SortTitleWidget:init() function SortTitleWidget:init()
self.close_button = CloseButton:new{ window = self } self.close_button = CloseButton:new{ window = self }
local btn_width = self.close_button:getSize().w 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 = RenderText:truncateTextByWidth(
self.title, self.tface, self.width-btn_width)
else
show_title_txt = self.title
end
-- title and close button -- title and close button
table.insert(self, OverlapGroup:new{ table.insert(self, OverlapGroup:new{
dimen = { w = self.width }, dimen = { w = self.width },
TextWidget:new{ TextWidget:new{
text = show_title_txt, text = self.title,
max_width = self.width - btn_width,
face = self.tface, face = self.tface,
}, },
self.close_button, self.close_button,
@ -124,10 +115,6 @@ function SortItemWidget:init()
local frame_padding = Size.padding.default local frame_padding = Size.padding.default
local frame_internal_width = self.width - frame_padding * 2 local frame_internal_width = self.width - frame_padding * 2
local text_rendered = RenderText:sizeUtf8Text(0, self.width, self.tface, self.text).x
if text_rendered > frame_internal_width then
self.text = RenderText:truncateTextByWidth(self.text, self.tface, frame_internal_width)
end
self[1] = FrameContainer:new{ self[1] = FrameContainer:new{
padding = 0, padding = 0,
@ -139,6 +126,7 @@ function SortItemWidget:init()
}, },
TextWidget:new{ TextWidget:new{
text = self.text, text = self.text,
max_width = frame_internal_width,
face = self.tface, face = self.tface,
} }
}, },

@ -12,7 +12,6 @@ local InputContainer = require("ui/widget/container/inputcontainer")
local LineWidget = require("ui/widget/linewidget") local LineWidget = require("ui/widget/linewidget")
local NumberPickerWidget = require("ui/widget/numberpickerwidget") local NumberPickerWidget = require("ui/widget/numberpickerwidget")
local OverlapGroup = require("ui/widget/overlapgroup") local OverlapGroup = require("ui/widget/overlapgroup")
local RenderText = require("ui/rendertext")
local Size = require("ui/size") local Size = require("ui/size")
local TextBoxWidget = require("ui/widget/textboxwidget") local TextBoxWidget = require("ui/widget/textboxwidget")
local TextWidget = require("ui/widget/textwidget") local TextWidget = require("ui/widget/textwidget")
@ -90,24 +89,16 @@ function SpinWidget:update()
local close_button = CloseButton:new{ window = self, padding_top = Size.margin.title, } local close_button = CloseButton:new{ window = self, padding_top = Size.margin.title, }
local btn_width = close_button:getSize().w + Size.padding.default * 2 local btn_width = close_button:getSize().w + Size.padding.default * 2
local title_txt_width = RenderText:sizeUtf8Text(
0, self.width, self.title_face, self.title_text).x
local show_title_txt
if self.width < (title_txt_width + btn_width) then
show_title_txt = RenderText:truncateTextByWidth(
self.title_text, self.title_face, self.width - btn_width)
else
show_title_txt = self.title_text
end
local value_title = FrameContainer:new{ local value_title = FrameContainer:new{
padding = Size.padding.default, padding = Size.padding.default,
margin = Size.margin.title, margin = Size.margin.title,
bordersize = 0, bordersize = 0,
TextWidget:new{ TextWidget:new{
text = show_title_txt, text = self.title_text,
max_width = self.width - btn_width,
face = self.title_face, face = self.title_face,
bold = true, bold = true,
width = self.width,
}, },
} }
local value_line = LineWidget:new{ local value_line = LineWidget:new{

@ -19,39 +19,76 @@ local RenderText = require("ui/rendertext")
local Size = require("ui/size") local Size = require("ui/size")
local Widget = require("ui/widget/widget") local Widget = require("ui/widget/widget")
local Screen = require("device").screen local Screen = require("device").screen
local util = require("util")
local TextWidget = Widget:new{ local TextWidget = Widget:new{
text = nil, text = nil,
face = nil, face = nil,
bold = nil, bold = false, -- synthetized/fake bold (use a bold face for nicer bold)
fgcolor = Blitbuffer.COLOR_BLACK, fgcolor = Blitbuffer.COLOR_BLACK,
padding = Size.padding.small, -- should padding be function of face.size ? padding = Size.padding.small, -- vertical padding (should it be function of face.size ?)
-- (no horizontal padding is added)
max_width = nil, max_width = nil,
_bb = nil, truncate_with_ellipsis = true, -- when truncation at max_width needed, add "…"
truncate_left = false, -- truncate on the right by default
_updated = nil,
_text_to_draw = nil,
_length = 0, _length = 0,
_height = 0, _height = 0,
_baseline_h = 0, _baseline_h = 0,
_maxlength = 1200, _maxlength = 1200,
} }
--function TextWidget:_render()
--local h = self.face.size * 1.3
--self._bb = Blitbuffer.new(self._maxlength, h)
--self._bb:fill(Blitbuffer.COLOR_WHITE)
--self._length = RenderText:renderUtf8Text(self._bb, 0, h*0.8, self.face, self.text, true, self.bold)
--end
function TextWidget:updateSize() function TextWidget:updateSize()
local tsize = RenderText:sizeUtf8Text(0, self.max_width and self.max_width or Screen:getWidth(), self.face, self.text, true, self.bold) if self._updated then
if tsize.x == 0 then return
self._length = 0 end
else self._updated = true
-- As text length includes last glyph pen "advance" (for positionning
-- next char), it's best to use math.floor() instead of math.ceil() -- In case we draw truncated text, keep original self.text
-- to get rid of a fraction of it in case this text is to be -- so caller can fetch it again
-- horizontally centered self._text_to_draw = self.text
-- Note: we use kerning=true in all RenderText calls
--- @todo Don't use kerning for monospaced fonts. (houqp)
-- Compute width:
-- We never need to draw/size more than one screen width, so limit computation
-- to that width in case we are given some huge string
local tsize = RenderText:sizeUtf8Text(0, Screen:getWidth(), self.face, self._text_to_draw, true, self.bold)
-- As text length includes last glyph pen "advance" (for positionning
-- next char), it's best to use math.floor() instead of math.ceil()
-- to get rid of a fraction of it in case this text is to be
-- horizontally centered
self._length = math.floor(tsize.x)
-- Ensure max_width, and truncate text if needed
if self.max_width and self._length > self.max_width then
if self.truncate_left then
-- We want to truncate text on the left, so work with the reverse of text.
-- We don't use kerning in this measurement as it might be different
-- on the reversed text. The final text will use kerning, and might get
-- a smaller width than the one found out here.
-- Also, not sure if this is correct when diacritics/clustered glyphs
-- happen at truncation point. But it will do for now.
local reversed_text = util.utf8Reverse(self._text_to_draw)
if self.truncate_with_ellipsis then
reversed_text = RenderText:truncateTextByWidth(reversed_text, self.face, self.max_width, false, self.bold)
else
reversed_text = RenderText:getSubTextByWidth(reversed_text, self.face, self.max_width, false, self.bold)
end
self._text_to_draw = util.utf8Reverse(reversed_text)
elseif self.truncate_with_ellipsis then
self._text_to_draw = RenderText:truncateTextByWidth(self._text_to_draw, self.face, self.max_width, true, self.bold)
end
-- Get the adjusted width when limiting to max_width (it might be
-- smaller than max_width when dropping the truncated glyph).
tsize = RenderText:sizeUtf8Text(0, self.max_width, self.face, self._text_to_draw, true, self.bold)
self._length = math.floor(tsize.x) self._length = math.floor(tsize.x)
end end
-- Compute height:
-- Used to be: -- Used to be:
-- self._height = math.ceil(self.face.size * 1.5) -- self._height = math.ceil(self.face.size * 1.5)
-- self._baseline_h = self._height*0.7 -- self._baseline_h = self._height*0.7
@ -68,10 +105,6 @@ function TextWidget:updateSize()
end end
function TextWidget:getSize() function TextWidget:getSize()
--if not self._bb then
--self:_render()
--end
--return { w = self._length, h = self._bb:getHeight() }
self:updateSize() self:updateSize()
return Geom:new{ return Geom:new{
w = self._length, w = self._length,
@ -79,6 +112,11 @@ function TextWidget:getSize()
} }
end end
function TextWidget:getWidth()
self:updateSize()
return self._length
end
function TextWidget:getBaseline() function TextWidget:getBaseline()
self:updateSize() self:updateSize()
return self._baseline_h return self._baseline_h
@ -86,27 +124,18 @@ end
function TextWidget:setText(text) function TextWidget:setText(text)
self.text = text self.text = text
self:updateSize() self._updated = false
end end
function TextWidget:paintTo(bb, x, y) function TextWidget:setMaxWidth(max_width)
--if not self._bb then self.max_width = max_width
--self:_render() self._updated = false
--end
--bb:blitFrom(self._bb, x, y, 0, 0, self._length, self._bb:getHeight())
--- @todo Don't use kerning for monospaced fonts. (houqp)
if self.max_width and RenderText:sizeUtf8Text(0, Screen:getWidth(), self.face, self.text, true, self.bold).x > self.max_width then
self.text = RenderText:truncateTextByWidth(self.text, self.face, self.max_width, true)
end
RenderText:renderUtf8Text(bb, x, y+self._baseline_h, self.face, self.text, true, self.bold,
self.fgcolor, self.max_width and self.max_width or self.width)
end end
function TextWidget:free() function TextWidget:paintTo(bb, x, y)
if self._bb then self:updateSize()
self._bb:free() RenderText:renderUtf8Text(bb, x, y+self._baseline_h, self.face, self._text_to_draw, true, self.bold,
self._bb = nil self.fgcolor, self._length)
end
end end
return TextWidget return TextWidget

@ -14,7 +14,6 @@ local GestureRange = require("ui/gesturerange")
local HorizontalGroup = require("ui/widget/horizontalgroup") local HorizontalGroup = require("ui/widget/horizontalgroup")
local InputContainer = require("ui/widget/container/inputcontainer") local InputContainer = require("ui/widget/container/inputcontainer")
local FrameContainer = require("ui/widget/container/framecontainer") local FrameContainer = require("ui/widget/container/framecontainer")
local RenderText = require("ui/rendertext")
local Size = require("ui/size") local Size = require("ui/size")
local TextWidget = require("ui/widget/textwidget") local TextWidget = require("ui/widget/textwidget")
local UIManager = require("ui/uimanager") local UIManager = require("ui/uimanager")
@ -28,10 +27,6 @@ local ToggleLabel = TextWidget:new{
fgcolor = Blitbuffer.COLOR_BLACK, fgcolor = Blitbuffer.COLOR_BLACK,
} }
function ToggleLabel:paintTo(bb, x, y)
RenderText:renderUtf8Text(bb, x, y+self._baseline_h, self.face, self.text, true, self.bold, self.fgcolor)
end
local ToggleSwitch = InputContainer:new{ local ToggleSwitch = InputContainer:new{
width = Screen:scaleBySize(216), width = Screen:scaleBySize(216),
height = Size.item.height_default, height = Size.item.height_default,
@ -84,13 +79,10 @@ function ToggleSwitch:init()
end end
local text = self.toggle[i] local text = self.toggle[i]
local face = Font:getFace(self.font_face, self.font_size) local face = Font:getFace(self.font_face, self.font_size)
local txt_width = RenderText:sizeUtf8Text(0, Screen:getWidth(), face, text, true, true).x
if txt_width > real_item_width - item_padding then
text = RenderText:truncateTextByWidth(text, face, real_item_width - item_padding, true, true)
end
local label = ToggleLabel:new{ local label = ToggleLabel:new{
text = text, text = text,
face = face, face = face,
max_width = real_item_width - item_padding,
} }
local content = CenterContainer:new{ local content = CenterContainer:new{
dimen = Geom:new{ dimen = Geom:new{

@ -19,7 +19,6 @@ local InfoMessage = require("ui/widget/infomessage")
local InputContainer = require("ui/widget/container/inputcontainer") local InputContainer = require("ui/widget/container/inputcontainer")
local LeftContainer = require("ui/widget/container/leftcontainer") local LeftContainer = require("ui/widget/container/leftcontainer")
local LineWidget = require("ui/widget/linewidget") local LineWidget = require("ui/widget/linewidget")
local RenderText = require("ui/rendertext")
local RightContainer = require("ui/widget/container/rightcontainer") local RightContainer = require("ui/widget/container/rightcontainer")
local Size = require("ui/size") local Size = require("ui/size")
local TextWidget = require("ui/widget/textwidget") local TextWidget = require("ui/widget/textwidget")
@ -87,9 +86,6 @@ function TouchMenuItem:init()
-- FrameContainer default paddings minus the checked widget width -- FrameContainer default paddings minus the checked widget width
local text_max_width = self.dimen.w - 2*Size.padding.default - checked_widget:getSize().w local text_max_width = self.dimen.w - 2*Size.padding.default - checked_widget:getSize().w
local text = getMenuText(self.item) local text = getMenuText(self.item)
if RenderText:sizeUtf8Text(0, Screen:getWidth(), self.face, text, true).x > text_max_width then
text = RenderText:truncateTextByWidth(text, self.face, text_max_width, true)
end
self.item_frame = FrameContainer:new{ self.item_frame = FrameContainer:new{
width = self.dimen.w, width = self.dimen.w,
bordersize = 0, bordersize = 0,
@ -102,6 +98,7 @@ function TouchMenuItem:init()
}, },
TextWidget:new{ TextWidget:new{
text = text, text = text,
max_width = text_max_width,
fgcolor = item_enabled ~= false and Blitbuffer.COLOR_BLACK or Blitbuffer.COLOR_DARK_GRAY, fgcolor = item_enabled ~= false and Blitbuffer.COLOR_BLACK or Blitbuffer.COLOR_DARK_GRAY,
face = self.face, face = self.face,
}, },

@ -18,7 +18,6 @@ local Geom = require("ui/geometry")
local GestureRange = require("ui/gesturerange") local GestureRange = require("ui/gesturerange")
local InputContainer = require("ui/widget/container/inputcontainer") local InputContainer = require("ui/widget/container/inputcontainer")
local LeftContainer = require("ui/widget/container/leftcontainer") local LeftContainer = require("ui/widget/container/leftcontainer")
local RenderText = require("ui/rendertext")
local Size = require("ui/size") local Size = require("ui/size")
local TextBoxWidget = require("ui/widget/textboxwidget") local TextBoxWidget = require("ui/widget/textboxwidget")
local TextWidget = require("ui/widget/textwidget") local TextWidget = require("ui/widget/textwidget")
@ -59,16 +58,14 @@ function TrapWidget:init()
} }
end end
if self.text then if self.text then
local textw local textw = TextWidget:new{
text = self.text,
face = self.face,
}
-- Don't make our message reach full screen width, so -- Don't make our message reach full screen width, so
-- it looks like popping from bottom left corner -- it looks like popping from bottom left corner
local tsize = RenderText:sizeUtf8Text(0, Screen:getWidth(), self.face, self.text) if textw:getWidth() > Screen:getWidth() * 0.9 then
if tsize.x < Screen:getWidth() * 0.9 then -- Text too wide: use TextBoxWidget for multi lines display
textw = TextWidget:new{
text = self.text,
face = self.face,
}
else -- if text too wide, use TextBoxWidget for multi lines display
textw = TextBoxWidget:new{ textw = TextBoxWidget:new{
text = self.text, text = self.text,
face = self.face, face = self.face,

@ -95,7 +95,7 @@ function CoverMenu:updateItems(select_number)
self:updatePageInfo(select_number) self:updatePageInfo(select_number)
if self.show_path then if self.show_path then
self.path_text.text = self:truncatePath(self.path) self.path_text:setText(self.path)
end end
self.show_parent.dithered = self._has_cover_images self.show_parent.dithered = self._has_cover_images
UIManager:setDirty(self.show_parent, function() UIManager:setDirty(self.show_parent, function()

@ -17,7 +17,6 @@ local LeftContainer = require("ui/widget/container/topcontainer")
local LineWidget = require("ui/widget/linewidget") local LineWidget = require("ui/widget/linewidget")
local LuaSettings = require("luasettings") local LuaSettings = require("luasettings")
local OverlapGroup = require("ui/widget/overlapgroup") local OverlapGroup = require("ui/widget/overlapgroup")
local RenderText = require("ui/rendertext")
local Size = require("ui/size") local Size = require("ui/size")
local TextWidget = require("ui/widget/textwidget") local TextWidget = require("ui/widget/textwidget")
local UIManager = require("ui/uimanager") local UIManager = require("ui/uimanager")
@ -39,20 +38,12 @@ local DoubleKeyValueTitle = VerticalGroup:new{
function DoubleKeyValueTitle:init() function DoubleKeyValueTitle:init()
self.close_button = CloseButton:new{ window = self } self.close_button = CloseButton:new{ window = self }
local btn_width = self.close_button:getSize().w 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 = RenderText:truncateTextByWidth(
self.title, self.tface, self.width - btn_width)
else
show_title_txt = self.title
end
-- title and close button -- title and close button
table.insert(self, OverlapGroup:new{ table.insert(self, OverlapGroup:new{
dimen = { w = self.width }, dimen = { w = self.width },
TextWidget:new{ TextWidget:new{
text = show_title_txt, text = self.title,
max_width = self.width - btn_width,
face = self.tface, face = self.tface,
}, },
self.close_button, self.close_button,
@ -114,7 +105,6 @@ local DoubleKeyValueItem = InputContainer:new{
function DoubleKeyValueItem:init() function DoubleKeyValueItem:init()
self.dimen = Geom:new{align = "left", w = self.width, h = self.height} self.dimen = Geom:new{align = "left", w = self.width, h = self.height}
local padding = Screen:scaleBySize(20)
if self.callback and Device:isTouchDevice() then if self.callback and Device:isTouchDevice() then
self.ges_events.Tap = { self.ges_events.Tap = {
GestureRange:new{ GestureRange:new{
@ -123,18 +113,8 @@ function DoubleKeyValueItem:init()
} }
} }
end end
local key_w = RenderText:sizeUtf8Text(0, self.width, self.cface_down, self.key).x local padding = Screen:scaleBySize(20)
local value_w = RenderText:sizeUtf8Text(0, self.width, self.cface_up, self.value).x local max_width = self.width - 2*padding
if key_w > self.width - 2*padding then
self.show_key = RenderText:truncateTextByWidth(self.key, self.cface_down, self.width - 2*padding)
else
self.show_key = self.key
end
if value_w > self.width - 2*padding then
self.show_value = RenderText:truncateTextByWidth(self.value, self.cface_up, self.width - 2*padding)
else
self.show_value = self.value
end
local h = self.dimen.h / 2 local h = self.dimen.h / 2
local w = self.dimen.w local w = self.dimen.w
self[1] = FrameContainer:new{ self[1] = FrameContainer:new{
@ -147,7 +127,8 @@ function DoubleKeyValueItem:init()
padding = 0, padding = 0,
dimen = Geom:new{ h = h, w = w }, dimen = Geom:new{ h = h, w = w },
TextWidget:new{ TextWidget:new{
text = self.show_value, text = self.value,
max_width = max_width,
face = self.cface_up, face = self.cface_up,
} }
}, },
@ -155,7 +136,8 @@ function DoubleKeyValueItem:init()
padding = 0, padding = 0,
dimen = Geom:new{ h = h, w = w }, dimen = Geom:new{ h = h, w = w },
TextWidget:new{ TextWidget:new{
text = self.show_key, text = self.key,
max_width = max_width,
face = self.cface_down, face = self.cface_down,
} }
} }

Loading…
Cancel
Save