TitleBar: new options, dedicated close button

- Fix hold callback name
- Add options to handle long titles (truncated by default):
  "title_multines" and "title_shrink_font_to_fit" (allowing
  setTitle() with them makes the code a bit more complex).
- Add options to set bottom line color and horizontal padding.
- Use an added close.svg (based on exit.svg, tweaked to have
  similar size and baseline as other icons) for close button.

IconButton:
- handle hold/hold_release similar to Button.
- new option allow_flash to allow disabling flashing.
reviewable/pr8632/r1^2
poire-z 2 years ago
parent 6a5f330b3b
commit 4546d826ee

@ -600,7 +600,7 @@ function BookMapWidget:init()
left_icon = "notice-info",
left_icon_tap_callback = function() self:showHelp() end,
close_callback = function() self:onClose() end,
hold_close_callback = function() self:onClose(true) end,
close_hold_callback = function() self:onClose(true) end,
show_parent = self,
}
self.title_bar_h = self.title_bar:getHeight()
@ -1036,6 +1036,8 @@ function BookMapWidget:onClose(close_all_parents)
-- The last one of these (which has no launcher attribute)
-- will do the cleanup below.
self.launcher:onClose(true)
else
UIManager:setDirty(self.launcher, "ui")
end
else
-- Remove all thumbnails generated for a different target size than

@ -27,6 +27,7 @@ local IconButton = InputContainer:new{
padding_left = nil,
enabled = true,
callback = nil,
allow_flash = true, -- set to false for any IconButton that may close its container
}
function IconButton:init()
@ -85,6 +86,13 @@ function IconButton:initGesListener()
range = self.dimen,
},
doc = "Hold IconButton",
},
HoldReleaseIconButton = {
GestureRange:new{
ges = "hold_release",
range = self.dimen,
},
doc = "Hold Release IconButton",
}
}
end
@ -92,7 +100,7 @@ end
function IconButton:onTapIconButton()
if not self.callback then return end
if G_reader_settings:isFalse("flash_ui") then
if G_reader_settings:isFalse("flash_ui") or not self.allow_flash then
self.callback()
else
-- c.f., ui/widget/button for more gnarly details about the implementation, but the flow of the flash_ui codepath essentially goes like this:
@ -133,6 +141,10 @@ function IconButton:onTapIconButton()
end
function IconButton:onHoldIconButton()
-- If we're going to process this hold, we must make
-- sure to also handle its hold_release below, so it's
-- not propagated up to a MovableContainer
self._hold_handled = nil
if self.enabled and self.hold_callback then
self.hold_callback()
elseif self.hold_input then
@ -140,9 +152,18 @@ function IconButton:onHoldIconButton()
elseif type(self.hold_input_func) == "function" then
self:onInput(self.hold_input_func())
elseif self.hold_callback == nil then return end
self._hold_handled = true
return true
end
function IconButton:onHoldReleaseIconButton()
if self._hold_handled then
self._hold_handled = nil
return true
end
return false
end
function IconButton:onFocus()
--quick and dirty, need better way to show focus
self.image.invert = true

@ -105,7 +105,7 @@ function PageBrowserWidget:init()
left_icon = "notice-info",
left_icon_tap_callback = function() self:showHelp() end,
close_callback = function() self:onClose() end,
hold_close_callback = function() self:onClose(true) end,
close_hold_callback = function() self:onClose(true) end,
show_parent = self,
}
self.title_bar_h = self.title_bar:getHeight()
@ -618,8 +618,8 @@ function PageBrowserWidget:onClose(close_all_parents)
if self.requests_batch_id then
self.ui.thumbnail:cancelPageThumbnailRequests(self.requests_batch_id)
end
logger.dbg("closing PageBrowserWidget")
-- Close this widget
logger.dbg("closing PageBrowserWidget")
UIManager:close(self)
if self.launcher then
-- We were launched by a BookMapWidget, don't do any cleanup.
@ -627,6 +627,8 @@ function PageBrowserWidget:onClose(close_all_parents)
-- The last one of these (which has no launcher attribute)
-- will do the cleanup below.
self.launcher:onClose(true)
else
UIManager:setDirty(self.launcher, "ui")
end
else
-- Remove all thumbnails generated for a different target size than

@ -8,6 +8,7 @@ local LineWidget = require("ui/widget/linewidget")
local Math = require("optmath")
local OverlapGroup = require("ui/widget/overlapgroup")
local Size = require("ui/size")
local TextBoxWidget = require("ui/widget/textboxwidget")
local TextWidget = require("ui/widget/textwidget")
local UIManager = require("ui/uimanager")
local VerticalGroup = require("ui/widget/verticalgroup")
@ -17,14 +18,21 @@ local Screen = Device.screen
local TitleBar = OverlapGroup:extend{
width = nil, -- default to screen width
fullscreen = false, -- larger font and small adjustments if fullscreen
align = "center",
align = "center", -- title & subtitle alignment inside TitleBar
with_bottom_line = false,
bottom_line_color = nil, -- default to black
bottom_line_h_padding = nil, -- default to 0: full width
title = "",
subtitle = nil,
title_face = nil, -- if not provided, one of these will be used:
title_face_fullscreen = Font:getFace("smalltfont"),
title_face_not_fullscreen = Font:getFace("x_smalltfont"),
-- by default: single line, truncated if overflow -- the default could be made dependant on self.fullscreen
title_multilines = false, -- multilines if overflow
title_shrink_font_to_fit = false, -- reduce font size so that single line text fits
subtitle = nil,
subtitle_face = Font:getFace("xx_smallinfofont"),
title_top_padding = nil, -- computed if none provided
@ -46,12 +54,23 @@ local TitleBar = OverlapGroup:extend{
-- If provided, use right_icon="exit" and use this as right_icon_tap_callback
close_callback = nil,
hold_close_callback = nil,
close_hold_callback = nil,
show_parent = nil,
-- Internal: remember first sizes computed when title_shrink_font_to_fit=true,
-- and keep using them after :setTitle() in case a smaller font size is needed,
-- to keep the TitleBar geometry stable.
_initial_title_top_padding = nil,
_initial_title_text_baseline = nil,
_initial_titlebar_height = nil,
_initial_filler_height = nil,
_initial_re_init_needed = nil,
}
function TitleBar:init()
if self.close_callback then
self.right_icon = "exit"
self.right_icon = "close"
self.right_icon_tap_callback = self.close_callback
self.right_icon_allow_flash = false
if self.close_hold_callback then
@ -82,25 +101,86 @@ function TitleBar:init()
end
-- Title, subtitle, and their alignment
if not self.title_face then
self.title_face = self.fullscreen and self.title_face_fullscreen or self.title_face_not_fullscreen
local title_face = self.title_face
if not title_face then
title_face = self.fullscreen and self.title_face_fullscreen or self.title_face_not_fullscreen
end
if not self.title_top_padding then
if self.title_multilines then
self.title_widget = TextBoxWidget:new{
text = self.title,
alignment = self.align,
width = title_max_width,
face = title_face,
}
else
while true do
self.title_widget = TextWidget:new{
text = self.title,
face = title_face,
padding = 0,
max_width = not self.title_shrink_font_to_fit and title_max_width,
-- truncate if not self.title_shrink_font_to_fit
}
if not self.title_shrink_font_to_fit then
break -- truncation allowed, no loop needed
end
if self.title_widget:getWidth() <= title_max_width then
break -- text with normal font fits, no loop needed
end
-- Text doesn't fit
if not self._initial_titlebar_height then
-- We're with title_shrink_font_to_fit and in the first :init():
-- we don't want to go on measuring with this too long text.
-- We want metrics proper for when text fits, so if later :setTitle()
-- is called with a text that fits, this text will look allright.
-- Longer title with a smaller font size should be laid out on the
-- baseline of a fitted text.
-- So, go on computing sizes with an empty title. When all is
-- gathered, we'll re :init() ourselves with the original title,
-- using the metrics we're computing now (self._initial*).
self._initial_re_init_needed = true
self.title_widget:free()
self.title_widget = TextWidget:new{
text = "",
face = title_face,
padding = 0,
}
break
end
-- otherwise, loop and do the same with a smaller font size
self.title_widget:free()
title_face = Font:getFace(title_face.orig_font, title_face.orig_size - 1)
end
end
local title_top_padding = self.title_top_padding
if not title_top_padding then
-- Compute it so baselines of the text and of the icons align.
-- Our icons' baselines looks like they could be at 83% to 90% of their height.
local face_height, face_baseline = self.title_face.ftface:getHeightAndAscender() -- luacheck: no unused
local text_baseline = self.title_widget:getBaseline()
local icon_height = math.max(left_icon_size, right_icon_size)
local icon_baseline = icon_height * 0.85 + self.button_padding
self.title_top_padding = Math.round(math.max(0, icon_baseline - face_baseline))
title_top_padding = Math.round(math.max(0, icon_baseline - text_baseline))
if self.title_shrink_font_to_fit then
-- Use, or store, the first top padding and baseline we have computed,
-- so the text stays vertically stable
if self._initial_title_top_padding then
-- Use this to have baselines aligned:
-- title_top_padding = Math.round(self._initial_title_top_padding + self._initial_title_text_baseline - text_baseline)
-- But then, smaller text is not vertically centered in the title bar.
-- So, go with just half the baseline difference:
title_top_padding = Math.round(self._initial_title_top_padding + (self._initial_title_text_baseline - text_baseline)/2)
else
self._initial_title_top_padding = title_top_padding
self._initial_title_text_baseline = text_baseline
end
end
end
self.title_widget = TextWidget:new{
text = self.title,
face = self.title_face,
max_width = title_max_width,
padding = 0,
}
self.subtitle_widget = nil
if self.subtitle then
-- No specific options for subtitles currently: truncate if too long.
-- We might want to add truncate_left if this gets used for a filepath
-- as in FileManager.
self.subtitle_widget = TextWidget:new{
text = self.subtitle,
face = self.subtitle_face,
@ -115,7 +195,7 @@ function TitleBar:init()
self.title_group = VerticalGroup:new{
align = self.align,
VerticalSpan:new{width = self.title_top_padding},
VerticalSpan:new{width = title_top_padding},
self.title_widget,
self.subtitle_widget and VerticalSpan:new{width = self.title_subtitle_v_padding},
self.subtitle_widget,
@ -139,15 +219,45 @@ function TitleBar:init()
-- the one we set as self.dimen.h.
self.titlebar_height = self.title_group:getSize().h
if self.title_shrink_font_to_fit then
-- Use, or store, the first title_group height we have computed,
-- so the TitleBar geometry and the bottom line position stay stable
-- (face height may have changed, even after we kept the baseline
-- stable, as we did above).
if self._initial_titlebar_height then
self.titlebar_height = self._initial_titlebar_height
else
self._initial_titlebar_height = self.titlebar_height
end
end
if self.with_bottom_line then
-- Be sure we add between the text and the line at least as much padding
-- as above the text, to keep it vertically centered.
local title_bottom_padding = math.max(self.title_top_padding, Size.padding.default)
local title_bottom_padding = math.max(title_top_padding, Size.padding.default)
local filler_height = self.titlebar_height + title_bottom_padding
if self.title_shrink_font_to_fit then
-- Use, or store, the first filler height we have computed,
if self._initial_filler_height then
filler_height = self._initial_filler_height
else
self._initial_filler_height = filler_height
end
end
local line_widget = LineWidget:new{
dimen = Geom:new{ w = self.width, h = Size.line.thick },
background = self.bottom_line_color;
}
if self.bottom_line_h_padding then
line_widget.dimen.w = line_widget.dimen.w - 2 * self.bottom_line_h_padding
line_widget = HorizontalGroup:new{
HorizontalSpan:new{ width = self.bottom_line_h_padding },
line_widget,
}
end
local filler_and_bottom_line = VerticalGroup:new{
VerticalSpan:new{ width = self.titlebar_height + title_bottom_padding },
LineWidget:new{
dimen = Geom:new{ w = self.width, h = Size.line.thick },
},
VerticalSpan:new{ width = filler_height },
line_widget,
}
table.insert(self, filler_and_bottom_line)
self.titlebar_height = filler_and_bottom_line:getSize().h
@ -161,6 +271,14 @@ function TitleBar:init()
end
self.titlebar_height = self.titlebar_height + self.bottom_v_padding
if self._initial_re_init_needed then
-- We have computed all the self._initial_ metrics needed.
self._initial_re_init_needed = nil
self:clear()
self:init()
return
end
self.dimen = Geom:new{
w = self.width,
h = self.titlebar_height, -- buttons can overflow this
@ -177,7 +295,8 @@ function TitleBar:init()
overlap_align = "left",
callback = self.left_icon_tap_callback,
hold_callback = self.left_icon_hold_callback,
allow_flash = self.left_icon_allow_flash
allow_flash = self.left_icon_allow_flash,
show_parent = self.show_parent,
}
table.insert(self, self.left_button)
end
@ -192,7 +311,8 @@ function TitleBar:init()
overlap_align = "right",
callback = self.right_icon_tap_callback,
hold_callback = self.right_icon_hold_callback,
allow_flash = self.right_icon_allow_flash
allow_flash = self.right_icon_allow_flash,
show_parent = self.show_parent,
}
table.insert(self, self.right_button)
end
@ -215,13 +335,40 @@ function TitleBar:getHeight()
return self.titlebar_height
end
function TitleBar:setTitle(title)
self.title_widget:setText(title)
if self.inner_title_group then
self.inner_title_group:resetLayout()
function TitleBar:setTitle(title, no_refresh)
if self.title_multilines or self.title_shrink_font_to_fit then
-- We need to re-init the whole widget as its height or
-- padding may change.
local previous_height = self.titlebar_height
-- Call WidgetContainer:clear() that will call :free() and
-- will remove subwidgets from the OverlapGroup we are.
self:clear()
self.title = title
self:init()
if no_refresh then
-- If caller is sure to handle refresh correctly, it can provides this
return
end
if self.title_multilines and self.titlebar_height ~= previous_height then
-- Title height have changed, and the upper widget may not have
-- hooks to refresh a combination of its previous size and new
-- size: be sure everything is repainted
UIManager:setDirty("all", "ui")
else
UIManager:setDirty(self.show_parent, "ui", self.dimen)
end
else
-- TextWidget with max-width: we can just update its text
self.title_widget:setText(title)
if self.inner_title_group then
self.inner_title_group:resetLayout()
end
self.title_group:resetLayout()
if no_refresh then
return
end
UIManager:setDirty(self.show_parent, "ui", self.dimen)
end
self.title_group:resetLayout()
UIManager:setDirty(self.show_parent, "ui", self.dimen)
end
function TitleBar:setSubTitle(subtitle)

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
width="24"
height="24"
viewBox="2 2 20 20"
enable-background="new 0 0 24.00 24.00"
xml:space="preserve"
id="svg4"
sodipodi:docname="exit.svg"
inkscape:version="1.0.2 (e86c870879, 2021-01-15)"><metadata
id="metadata10"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs
id="defs8" /><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="2560"
inkscape:window-height="1381"
id="namedview6"
showgrid="true"
inkscape:zoom="42"
inkscape:cx="12"
inkscape:cy="12"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg4"
inkscape:document-rotation="0"><inkscape:grid
type="xygrid"
id="grid855" /></sodipodi:namedview>
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
d="M 18,6 5,19"
id="path834" /><path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
d="M 5,6 18,19"
id="path836"
sodipodi:nodetypes="cc" /></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

Loading…
Cancel
Save